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 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(){}
52 PDMA_ADAPTER m_Adapter
;
53 PQUEUE_HEAD AsyncListQueueHead
;
54 LIST_ENTRY m_CompletedRequestAsyncList
;
55 LIST_ENTRY m_PendingRequestAsyncList
;
58 // queue head manipulation functions
59 VOID
LinkQueueHead(PQUEUE_HEAD HeadQueueHead
, PQUEUE_HEAD NewQueueHead
);
60 VOID
UnlinkQueueHead(PQUEUE_HEAD QueueHead
);
61 VOID
LinkQueueHeadChain(PQUEUE_HEAD HeadQueueHead
, PQUEUE_HEAD NewQueueHead
);
62 PQUEUE_HEAD
UnlinkQueueHeadChain(PQUEUE_HEAD HeadQueueHead
, ULONG Count
);
64 // processes the async list
65 VOID
ProcessAsyncList(IN NTSTATUS Status
, OUT PULONG ShouldRingDoorBell
);
67 // called for each completed queue head
68 VOID
QueueHeadCompletion(PQUEUE_HEAD QueueHead
, NTSTATUS Status
);
70 // called when the completion queue is cleaned up
71 VOID
QueueHeadCleanup(PQUEUE_HEAD QueueHead
);
74 //=================================================================================================
79 CUSBQueue::QueryInterface(
83 if (IsEqualGUIDAligned(refiid
, IID_IUnknown
))
85 *Output
= PVOID(PUNKNOWN(this));
86 PUNKNOWN(*Output
)->AddRef();
87 return STATUS_SUCCESS
;
90 return STATUS_UNSUCCESSFUL
;
94 CUSBQueue::Initialize(
95 IN PUSBHARDWAREDEVICE Hardware
,
96 PDMA_ADAPTER AdapterObject
,
97 IN OPTIONAL PKSPIN_LOCK Lock
)
99 NTSTATUS Status
= STATUS_SUCCESS
;
101 DPRINT("CUSBQueue::Initialize()\n");
106 // initialize device lock
108 KeInitializeSpinLock(&m_Lock
);
111 // Get the AsyncQueueHead
113 AsyncListQueueHead
= (PQUEUE_HEAD
)Hardware
->GetAsyncListQueueHead();
116 // Initialize the List Head
118 InitializeListHead(&AsyncListQueueHead
->LinkedQueueHeads
);
121 // Initialize completed async list head
123 InitializeListHead(&m_CompletedRequestAsyncList
);
126 // Initialize pending async list head
128 InitializeListHead(&m_PendingRequestAsyncList
);
134 CUSBQueue::GetPendingRequestCount()
137 // Loop through the pending list and iterrate one for each QueueHead that
138 // has a IRP to complete.
146 CUSBQueue::AddUSBRequest(
147 IUSBRequest
* Request
)
149 PQUEUE_HEAD QueueHead
;
156 ASSERT(Request
!= NULL
);
161 Type
= Request
->GetTransferType();
164 // check if supported
168 case USB_ENDPOINT_TYPE_ISOCHRONOUS
:
169 case USB_ENDPOINT_TYPE_INTERRUPT
:
170 /* NOT IMPLEMENTED IN QUEUE */
171 Status
= STATUS_NOT_SUPPORTED
;
173 case USB_ENDPOINT_TYPE_BULK
:
174 case USB_ENDPOINT_TYPE_CONTROL
:
175 Status
= STATUS_SUCCESS
;
185 if (!NT_SUCCESS(Status
))
188 // request not supported, please try later
193 if (Type
== USB_ENDPOINT_TYPE_BULK
|| Type
== USB_ENDPOINT_TYPE_CONTROL
)
198 Status
= Request
->GetQueueHead(&QueueHead
);
203 if (!NT_SUCCESS(Status
))
206 // failed to get queue head
211 DPRINT("Request %p QueueHead %p inserted into AsyncQueue\n", Request
, QueueHead
);
214 // Add it to the pending list
216 LinkQueueHead(AsyncListQueueHead
, QueueHead
);
221 // add extra reference which is released when the request is completed
226 return STATUS_SUCCESS
;
230 CUSBQueue::AddUSBRequest(
234 return STATUS_NOT_IMPLEMENTED
;
238 CUSBQueue::CancelRequests()
241 return STATUS_NOT_IMPLEMENTED
;
245 CUSBQueue::CreateUSBRequest(
246 IUSBRequest
**OutRequest
)
248 PUSBREQUEST UsbRequest
;
252 Status
= InternalCreateUSBRequest(&UsbRequest
);
254 if (NT_SUCCESS(Status
))
256 *OutRequest
= UsbRequest
;
263 // LinkQueueHead - Links one QueueHead to the end of HeadQueueHead list, updating HorizontalLinkPointer.
266 CUSBQueue::LinkQueueHead(
267 PQUEUE_HEAD HeadQueueHead
,
268 PQUEUE_HEAD NewQueueHead
)
270 PQUEUE_HEAD LastQueueHead
, NextQueueHead
;
272 ASSERT(HeadQueueHead
);
273 ASSERT(NewQueueHead
);
276 // Link the LIST_ENTRYs
278 InsertTailList(&HeadQueueHead
->LinkedQueueHeads
, &NewQueueHead
->LinkedQueueHeads
);
281 // Update HLP for Previous QueueHead, which should be the last in list.
283 Entry
= NewQueueHead
->LinkedQueueHeads
.Blink
;
284 LastQueueHead
= CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
285 LastQueueHead
->HorizontalLinkPointer
= (NewQueueHead
->PhysicalAddr
| QH_TYPE_QH
);
288 // Update HLP for NewQueueHead to point to next, which should be the HeadQueueHead
290 Entry
= NewQueueHead
->LinkedQueueHeads
.Flink
;
291 NextQueueHead
= CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
292 ASSERT(NextQueueHead
== HeadQueueHead
);
293 NewQueueHead
->HorizontalLinkPointer
= (NextQueueHead
->PhysicalAddr
| QH_TYPE_QH
);
297 // UnlinkQueueHead - Unlinks one QueueHead, updating HorizontalLinkPointer.
300 CUSBQueue::UnlinkQueueHead(
301 PQUEUE_HEAD QueueHead
)
303 PQUEUE_HEAD PreviousQH
, NextQH
;
307 // sanity check: there must be at least one queue head with halted bit set
309 PC_ASSERT(QueueHead
->Token
.Bits
.Halted
== 0);
314 Entry
= QueueHead
->LinkedQueueHeads
.Blink
;
317 // get queue head structure
319 PreviousQH
= CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
324 Entry
= QueueHead
->LinkedQueueHeads
.Flink
;
327 // get queue head structure
329 NextQH
= CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
334 ASSERT(QueueHead
->HorizontalLinkPointer
== (NextQH
->PhysicalAddr
| QH_TYPE_QH
));
337 // remove queue head from linked list
339 PreviousQH
->HorizontalLinkPointer
= NextQH
->PhysicalAddr
| QH_TYPE_QH
;
342 // remove software link
344 RemoveEntryList(&QueueHead
->LinkedQueueHeads
);
348 // LinkQueueHeadChain - Links a list of QueueHeads to the HeadQueueHead list, updating HorizontalLinkPointer.
351 CUSBQueue::LinkQueueHeadChain(
352 PQUEUE_HEAD HeadQueueHead
,
353 PQUEUE_HEAD NewQueueHead
)
355 PQUEUE_HEAD LastQueueHead
;
357 ASSERT(HeadQueueHead
);
358 ASSERT(NewQueueHead
);
361 // Find the last QueueHead in NewQueueHead
363 Entry
= NewQueueHead
->LinkedQueueHeads
.Blink
;
364 ASSERT(Entry
!= NewQueueHead
->LinkedQueueHeads
.Flink
);
365 LastQueueHead
= CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
368 // Set the LinkPointer and Flink
370 LastQueueHead
->HorizontalLinkPointer
= HeadQueueHead
->PhysicalAddr
| QH_TYPE_QH
;
371 LastQueueHead
->LinkedQueueHeads
.Flink
= &HeadQueueHead
->LinkedQueueHeads
;
374 // Fine the last QueueHead in HeadQueueHead
376 Entry
= HeadQueueHead
->LinkedQueueHeads
.Blink
;
377 HeadQueueHead
->LinkedQueueHeads
.Blink
= &LastQueueHead
->LinkedQueueHeads
;
378 LastQueueHead
= CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
379 LastQueueHead
->LinkedQueueHeads
.Flink
= &NewQueueHead
->LinkedQueueHeads
;
380 LastQueueHead
->HorizontalLinkPointer
= NewQueueHead
->PhysicalAddr
| QH_TYPE_QH
;
384 // UnlinkQueueHeadChain - Unlinks a list number of QueueHeads from HeadQueueHead list, updating HorizontalLinkPointer.
385 // returns the chain of QueueHeads removed from HeadQueueHead.
388 CUSBQueue::UnlinkQueueHeadChain(
389 PQUEUE_HEAD HeadQueueHead
,
392 PQUEUE_HEAD LastQueueHead
, FirstQueueHead
;
397 // Find the last QueueHead in NewQueueHead
399 Entry
= &HeadQueueHead
->LinkedQueueHeads
;
400 FirstQueueHead
= CONTAINING_RECORD(Entry
->Flink
, QUEUE_HEAD
, LinkedQueueHeads
);
402 for (Index
= 0; Index
< Count
; Index
++)
404 Entry
= Entry
->Flink
;
406 if (Entry
== &HeadQueueHead
->LinkedQueueHeads
)
408 DPRINT1("Warnnig; Only %d QueueHeads in HeadQueueHead\n", Index
);
414 LastQueueHead
= CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
415 HeadQueueHead
->LinkedQueueHeads
.Flink
= LastQueueHead
->LinkedQueueHeads
.Flink
;
416 if (Count
+ 1 == Index
)
418 HeadQueueHead
->LinkedQueueHeads
.Blink
= &HeadQueueHead
->LinkedQueueHeads
;
421 HeadQueueHead
->LinkedQueueHeads
.Blink
= LastQueueHead
->LinkedQueueHeads
.Flink
;
423 FirstQueueHead
->LinkedQueueHeads
.Blink
= &LastQueueHead
->LinkedQueueHeads
;
424 LastQueueHead
->LinkedQueueHeads
.Flink
= &FirstQueueHead
->LinkedQueueHeads
;
425 LastQueueHead
->HorizontalLinkPointer
= TERMINATE_POINTER
;
426 return FirstQueueHead
;
430 CUSBQueue::QueueHeadCompletion(
431 PQUEUE_HEAD CurrentQH
,
434 IUSBRequest
*Request
;
435 PQUEUE_HEAD NewQueueHead
;
438 // now unlink the queue head
439 // FIXME: implement chained queue heads
441 UnlinkQueueHead(CurrentQH
);
444 // get contained usb request
446 Request
= (IUSBRequest
*)CurrentQH
->Request
;
449 // check if the request is complete
451 if (Request
->IsRequestComplete() == FALSE
)
454 // request is still in complete
455 // get new queue head
457 Status
= Request
->GetQueueHead(&NewQueueHead
);
460 // add to pending list
462 InsertTailList(&m_PendingRequestAsyncList
, &NewQueueHead
->LinkedQueueHeads
);
466 // put queue head into completed queue head list
468 InsertTailList(&m_CompletedRequestAsyncList
, &CurrentQH
->LinkedQueueHeads
);
473 CUSBQueue::ProcessAsyncList(
475 OUT PULONG ShouldRingDoorBell
)
479 PQUEUE_HEAD QueueHead
;
480 IUSBRequest
* Request
;
481 BOOLEAN IsQueueHeadComplete
;
484 // lock completed async list
486 KeAcquireSpinLock(&m_Lock
, &OldLevel
);
491 Entry
= AsyncListQueueHead
->LinkedQueueHeads
.Flink
;
493 while(Entry
!= &AsyncListQueueHead
->LinkedQueueHeads
)
496 // get queue head structure
498 QueueHead
= (PQUEUE_HEAD
)CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
503 PC_ASSERT(QueueHead
->Request
);
506 // get IUSBRequest interface
508 Request
= (IUSBRequest
*)QueueHead
->Request
;
512 // move to next entry
514 Entry
= Entry
->Flink
;
517 // check if queue head is complete
519 IsQueueHeadComplete
= Request
->IsQueueHeadComplete(QueueHead
);
521 DPRINT("Request %p QueueHead %p Complete %d\n", Request
, QueueHead
, IsQueueHeadComplete
);
524 // check if queue head is complete
526 if (IsQueueHeadComplete
)
529 // current queue head is complete
531 QueueHeadCompletion(QueueHead
, Status
);
534 // ring door bell is going to be necessary
536 *ShouldRingDoorBell
= TRUE
;
543 KeReleaseSpinLock(&m_Lock
, OldLevel
);
548 CUSBQueue::InterruptCallback(
550 OUT PULONG ShouldRingDoorBell
)
553 DPRINT("CUSBQueue::InterruptCallback\n");
556 // iterate asynchronous list
558 *ShouldRingDoorBell
= FALSE
;
559 ProcessAsyncList(Status
, ShouldRingDoorBell
);
562 // TODO: implement periodic schedule processing
567 CUSBQueue::QueueHeadCleanup(
568 PQUEUE_HEAD CurrentQH
)
570 IUSBRequest
* Request
;
571 BOOLEAN ShouldReleaseWhenDone
;
572 USBD_STATUS UrbStatus
;
577 PC_ASSERT(CurrentQH
->Token
.Bits
.Active
== 0);
578 PC_ASSERT(CurrentQH
->Request
);
584 Request
= (IUSBRequest
*)CurrentQH
->Request
;
592 // check if the queue head was completed with errors
594 if (CurrentQH
->Token
.Bits
.Halted
)
596 if (CurrentQH
->Token
.Bits
.DataBufferError
)
601 UrbStatus
= USBD_STATUS_DATA_BUFFER_ERROR
;
603 else if (CurrentQH
->Token
.Bits
.BabbleDetected
)
608 UrbStatus
= USBD_STATUS_BABBLE_DETECTED
;
615 UrbStatus
= USBD_STATUS_STALL_PID
;
623 UrbStatus
= USBD_STATUS_SUCCESS
;
627 // notify request that a queue head has been completed
629 Request
->CompletionCallback(STATUS_SUCCESS
/*FIXME*/, UrbStatus
, CurrentQH
);
632 // let IUSBRequest free the queue head
634 Request
->FreeQueueHead(CurrentQH
);
637 // check if we should release request when done
639 ShouldReleaseWhenDone
= Request
->ShouldReleaseRequestAfterCompletion();
642 // release reference when the request was added
647 // check if the operation was asynchronous
649 if (ShouldReleaseWhenDone
)
652 // release outstanding reference count
658 // request is now released
663 CUSBQueue::CompleteAsyncRequests()
667 PQUEUE_HEAD CurrentQH
;
669 DPRINT("CUSBQueue::CompleteAsyncRequests\n");
672 // first acquire request lock
674 KeAcquireSpinLock(&m_Lock
, &OldLevel
);
677 // the list should not be empty
679 PC_ASSERT(!IsListEmpty(&m_CompletedRequestAsyncList
));
681 while(!IsListEmpty(&m_CompletedRequestAsyncList
))
684 // remove first entry
686 Entry
= RemoveHeadList(&m_CompletedRequestAsyncList
);
689 // get queue head structure
691 CurrentQH
= (PQUEUE_HEAD
)CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
694 // complete request now
696 QueueHeadCleanup(CurrentQH
);
700 // is there a pending async entry
702 if (!IsListEmpty(&m_PendingRequestAsyncList
))
705 // remove first entry
707 Entry
= RemoveHeadList(&m_CompletedRequestAsyncList
);
710 // get queue head structure
712 CurrentQH
= (PQUEUE_HEAD
)CONTAINING_RECORD(Entry
, QUEUE_HEAD
, LinkedQueueHeads
);
715 // Add it to the pending list
717 LinkQueueHead(AsyncListQueueHead
, CurrentQH
);
723 KeReleaseSpinLock(&m_Lock
, OldLevel
);
728 PUSBQUEUE
*OutUsbQueue
)
733 // allocate controller
735 This
= new(NonPagedPool
, TAG_USBEHCI
) CUSBQueue(0);
739 // failed to allocate
741 return STATUS_INSUFFICIENT_RESOURCES
;
745 // add reference count
752 *OutUsbQueue
= (PUSBQUEUE
)This
;
757 return STATUS_SUCCESS
;