[USBEHCI_NEW]
[reactos.git] / drivers / usb / usbehci_new / usb_queue.cpp
1 /*
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.
6 * PROGRAMMERS:
7 * Michael Martin (michael.martin@reactos.org)
8 * Johannes Anderwald (johannes.anderwald@reactos.org)
9 */
10
11 #include "usbehci.h"
12 #include "hardware.h"
13
14 class CUSBQueue : public IUSBQueue
15 {
16 public:
17 STDMETHODIMP QueryInterface( REFIID InterfaceId, PVOID* Interface);
18
19 STDMETHODIMP_(ULONG) AddRef()
20 {
21 InterlockedIncrement(&m_Ref);
22 return m_Ref;
23 }
24 STDMETHODIMP_(ULONG) Release()
25 {
26 InterlockedDecrement(&m_Ref);
27
28 if (!m_Ref)
29 {
30 delete this;
31 return 0;
32 }
33 return m_Ref;
34 }
35
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();
44
45 // constructor / destructor
46 CUSBQueue(IUnknown *OuterUnknown){}
47 virtual ~CUSBQueue(){}
48
49 protected:
50 LONG m_Ref;
51 KSPIN_LOCK m_Lock;
52 PDMA_ADAPTER m_Adapter;
53 PQUEUE_HEAD AsyncListQueueHead;
54 LIST_ENTRY m_CompletedRequestAsyncList;
55 LIST_ENTRY m_PendingRequestAsyncList;
56
57
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);
63
64 // processes the async list
65 VOID ProcessAsyncList(IN NTSTATUS Status, OUT PULONG ShouldRingDoorBell);
66
67 // called for each completed queue head
68 VOID QueueHeadCompletion(PQUEUE_HEAD QueueHead, NTSTATUS Status);
69
70 // called when the completion queue is cleaned up
71 VOID QueueHeadCleanup(PQUEUE_HEAD QueueHead);
72 };
73
74 //=================================================================================================
75 // COM
76 //
77 NTSTATUS
78 STDMETHODCALLTYPE
79 CUSBQueue::QueryInterface(
80 IN REFIID refiid,
81 OUT PVOID* Output)
82 {
83 if (IsEqualGUIDAligned(refiid, IID_IUnknown))
84 {
85 *Output = PVOID(PUNKNOWN(this));
86 PUNKNOWN(*Output)->AddRef();
87 return STATUS_SUCCESS;
88 }
89
90 return STATUS_UNSUCCESSFUL;
91 }
92
93 NTSTATUS
94 CUSBQueue::Initialize(
95 IN PUSBHARDWAREDEVICE Hardware,
96 PDMA_ADAPTER AdapterObject,
97 IN OPTIONAL PKSPIN_LOCK Lock)
98 {
99 NTSTATUS Status = STATUS_SUCCESS;
100
101 DPRINT("CUSBQueue::Initialize()\n");
102
103 ASSERT(Hardware);
104
105 //
106 // initialize device lock
107 //
108 KeInitializeSpinLock(&m_Lock);
109
110 //
111 // Get the AsyncQueueHead
112 //
113 AsyncListQueueHead = (PQUEUE_HEAD)Hardware->GetAsyncListQueueHead();
114
115 //
116 // Initialize the List Head
117 //
118 InitializeListHead(&AsyncListQueueHead->LinkedQueueHeads);
119
120 //
121 // Initialize completed async list head
122 //
123 InitializeListHead(&m_CompletedRequestAsyncList);
124
125 //
126 // Initialize pending async list head
127 //
128 InitializeListHead(&m_PendingRequestAsyncList);
129
130 return Status;
131 }
132
133 ULONG
134 CUSBQueue::GetPendingRequestCount()
135 {
136 //
137 // Loop through the pending list and iterrate one for each QueueHead that
138 // has a IRP to complete.
139 //
140
141
142 return 0;
143 }
144
145 NTSTATUS
146 CUSBQueue::AddUSBRequest(
147 IUSBRequest * Request)
148 {
149 PQUEUE_HEAD QueueHead;
150 NTSTATUS Status;
151 ULONG Type;
152
153 //
154 // sanity check
155 //
156 ASSERT(Request != NULL);
157
158 //
159 // get request type
160 //
161 Type = Request->GetTransferType();
162
163 //
164 // check if supported
165 //
166 switch(Type)
167 {
168 case USB_ENDPOINT_TYPE_ISOCHRONOUS:
169 case USB_ENDPOINT_TYPE_INTERRUPT:
170 /* NOT IMPLEMENTED IN QUEUE */
171 Status = STATUS_NOT_SUPPORTED;
172 break;
173 case USB_ENDPOINT_TYPE_BULK:
174 case USB_ENDPOINT_TYPE_CONTROL:
175 Status = STATUS_SUCCESS;
176 break;
177 default:
178 /* BUG */
179 PC_ASSERT(FALSE);
180 }
181
182 //
183 // check for success
184 //
185 if (!NT_SUCCESS(Status))
186 {
187 //
188 // request not supported, please try later
189 //
190 return Status;
191 }
192
193 if (Type == USB_ENDPOINT_TYPE_BULK || Type == USB_ENDPOINT_TYPE_CONTROL)
194 {
195 //
196 // get queue head
197 //
198 Status = Request->GetQueueHead(&QueueHead);
199
200 //
201 // check for success
202 //
203 if (!NT_SUCCESS(Status))
204 {
205 //
206 // failed to get queue head
207 //
208 return Status;
209 }
210
211 DPRINT("Request %p QueueHead %p inserted into AsyncQueue\n", Request, QueueHead);
212
213 //
214 // Add it to the pending list
215 //
216 LinkQueueHead(AsyncListQueueHead, QueueHead);
217 }
218
219
220 //
221 // add extra reference which is released when the request is completed
222 //
223 Request->AddRef();
224
225
226 return STATUS_SUCCESS;
227 }
228
229 NTSTATUS
230 CUSBQueue::AddUSBRequest(
231 PURB Urb)
232 {
233 UNIMPLEMENTED
234 return STATUS_NOT_IMPLEMENTED;
235 }
236
237 NTSTATUS
238 CUSBQueue::CancelRequests()
239 {
240 UNIMPLEMENTED
241 return STATUS_NOT_IMPLEMENTED;
242 }
243
244 NTSTATUS
245 CUSBQueue::CreateUSBRequest(
246 IUSBRequest **OutRequest)
247 {
248 PUSBREQUEST UsbRequest;
249 NTSTATUS Status;
250
251 *OutRequest = NULL;
252 Status = InternalCreateUSBRequest(&UsbRequest);
253
254 if (NT_SUCCESS(Status))
255 {
256 *OutRequest = UsbRequest;
257 }
258
259 return Status;
260 }
261
262 //
263 // LinkQueueHead - Links one QueueHead to the end of HeadQueueHead list, updating HorizontalLinkPointer.
264 //
265 VOID
266 CUSBQueue::LinkQueueHead(
267 PQUEUE_HEAD HeadQueueHead,
268 PQUEUE_HEAD NewQueueHead)
269 {
270 PQUEUE_HEAD LastQueueHead, NextQueueHead;
271 PLIST_ENTRY Entry;
272 ASSERT(HeadQueueHead);
273 ASSERT(NewQueueHead);
274
275 //
276 // Link the LIST_ENTRYs
277 //
278 InsertTailList(&HeadQueueHead->LinkedQueueHeads, &NewQueueHead->LinkedQueueHeads);
279
280 //
281 // Update HLP for Previous QueueHead, which should be the last in list.
282 //
283 Entry = NewQueueHead->LinkedQueueHeads.Blink;
284 LastQueueHead = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
285 LastQueueHead->HorizontalLinkPointer = (NewQueueHead->PhysicalAddr | QH_TYPE_QH);
286
287 //
288 // Update HLP for NewQueueHead to point to next, which should be the HeadQueueHead
289 //
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);
294 }
295
296 //
297 // UnlinkQueueHead - Unlinks one QueueHead, updating HorizontalLinkPointer.
298 //
299 VOID
300 CUSBQueue::UnlinkQueueHead(
301 PQUEUE_HEAD QueueHead)
302 {
303 PQUEUE_HEAD PreviousQH, NextQH;
304 PLIST_ENTRY Entry;
305
306 //
307 // sanity check: there must be at least one queue head with halted bit set
308 //
309 PC_ASSERT(QueueHead->Token.Bits.Halted == 0);
310
311 //
312 // get previous link
313 //
314 Entry = QueueHead->LinkedQueueHeads.Blink;
315
316 //
317 // get queue head structure
318 //
319 PreviousQH = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
320
321 //
322 // get next link
323 //
324 Entry = QueueHead->LinkedQueueHeads.Flink;
325
326 //
327 // get queue head structure
328 //
329 NextQH = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
330
331 //
332 // sanity check
333 //
334 ASSERT(QueueHead->HorizontalLinkPointer == (NextQH->PhysicalAddr | QH_TYPE_QH));
335
336 //
337 // remove queue head from linked list
338 //
339 PreviousQH->HorizontalLinkPointer = NextQH->PhysicalAddr | QH_TYPE_QH;
340
341 //
342 // remove software link
343 //
344 RemoveEntryList(&QueueHead->LinkedQueueHeads);
345 }
346
347 //
348 // LinkQueueHeadChain - Links a list of QueueHeads to the HeadQueueHead list, updating HorizontalLinkPointer.
349 //
350 VOID
351 CUSBQueue::LinkQueueHeadChain(
352 PQUEUE_HEAD HeadQueueHead,
353 PQUEUE_HEAD NewQueueHead)
354 {
355 PQUEUE_HEAD LastQueueHead;
356 PLIST_ENTRY Entry;
357 ASSERT(HeadQueueHead);
358 ASSERT(NewQueueHead);
359
360 //
361 // Find the last QueueHead in NewQueueHead
362 //
363 Entry = NewQueueHead->LinkedQueueHeads.Blink;
364 ASSERT(Entry != NewQueueHead->LinkedQueueHeads.Flink);
365 LastQueueHead = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
366
367 //
368 // Set the LinkPointer and Flink
369 //
370 LastQueueHead->HorizontalLinkPointer = HeadQueueHead->PhysicalAddr | QH_TYPE_QH;
371 LastQueueHead->LinkedQueueHeads.Flink = &HeadQueueHead->LinkedQueueHeads;
372
373 //
374 // Fine the last QueueHead in HeadQueueHead
375 //
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;
381 }
382
383 //
384 // UnlinkQueueHeadChain - Unlinks a list number of QueueHeads from HeadQueueHead list, updating HorizontalLinkPointer.
385 // returns the chain of QueueHeads removed from HeadQueueHead.
386 //
387 PQUEUE_HEAD
388 CUSBQueue::UnlinkQueueHeadChain(
389 PQUEUE_HEAD HeadQueueHead,
390 ULONG Count)
391 {
392 PQUEUE_HEAD LastQueueHead, FirstQueueHead;
393 PLIST_ENTRY Entry;
394 ULONG Index;
395
396 //
397 // Find the last QueueHead in NewQueueHead
398 //
399 Entry = &HeadQueueHead->LinkedQueueHeads;
400 FirstQueueHead = CONTAINING_RECORD(Entry->Flink, QUEUE_HEAD, LinkedQueueHeads);
401
402 for (Index = 0; Index < Count; Index++)
403 {
404 Entry = Entry->Flink;
405
406 if (Entry == &HeadQueueHead->LinkedQueueHeads)
407 {
408 DPRINT1("Warnnig; Only %d QueueHeads in HeadQueueHead\n", Index);
409 Count = Index + 1;
410 break;
411 }
412 }
413
414 LastQueueHead = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
415 HeadQueueHead->LinkedQueueHeads.Flink = LastQueueHead->LinkedQueueHeads.Flink;
416 if (Count + 1 == Index)
417 {
418 HeadQueueHead->LinkedQueueHeads.Blink = &HeadQueueHead->LinkedQueueHeads;
419 }
420 else
421 HeadQueueHead->LinkedQueueHeads.Blink = LastQueueHead->LinkedQueueHeads.Flink;
422
423 FirstQueueHead->LinkedQueueHeads.Blink = &LastQueueHead->LinkedQueueHeads;
424 LastQueueHead->LinkedQueueHeads.Flink = &FirstQueueHead->LinkedQueueHeads;
425 LastQueueHead->HorizontalLinkPointer = TERMINATE_POINTER;
426 return FirstQueueHead;
427 }
428
429 VOID
430 CUSBQueue::QueueHeadCompletion(
431 PQUEUE_HEAD CurrentQH,
432 NTSTATUS Status)
433 {
434 IUSBRequest *Request;
435 PQUEUE_HEAD NewQueueHead;
436
437 //
438 // now unlink the queue head
439 // FIXME: implement chained queue heads
440 //
441 UnlinkQueueHead(CurrentQH);
442
443 //
444 // get contained usb request
445 //
446 Request = (IUSBRequest*)CurrentQH->Request;
447
448 //
449 // check if the request is complete
450 //
451 if (Request->IsRequestComplete() == FALSE)
452 {
453 //
454 // request is still in complete
455 // get new queue head
456 //
457 Status = Request->GetQueueHead(&NewQueueHead);
458
459 //
460 // add to pending list
461 //
462 InsertTailList(&m_PendingRequestAsyncList, &NewQueueHead->LinkedQueueHeads);
463 }
464
465 //
466 // put queue head into completed queue head list
467 //
468 InsertTailList(&m_CompletedRequestAsyncList, &CurrentQH->LinkedQueueHeads);
469
470 }
471
472 VOID
473 CUSBQueue::ProcessAsyncList(
474 IN NTSTATUS Status,
475 OUT PULONG ShouldRingDoorBell)
476 {
477 KIRQL OldLevel;
478 PLIST_ENTRY Entry;
479 PQUEUE_HEAD QueueHead;
480 IUSBRequest * Request;
481 BOOLEAN IsQueueHeadComplete;
482
483 //
484 // lock completed async list
485 //
486 KeAcquireSpinLock(&m_Lock, &OldLevel);
487
488 //
489 // walk async list
490 //
491 Entry = AsyncListQueueHead->LinkedQueueHeads.Flink;
492
493 while(Entry != &AsyncListQueueHead->LinkedQueueHeads)
494 {
495 //
496 // get queue head structure
497 //
498 QueueHead = (PQUEUE_HEAD)CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
499
500 //
501 // sanity check
502 //
503 PC_ASSERT(QueueHead->Request);
504
505 //
506 // get IUSBRequest interface
507 //
508 Request = (IUSBRequest*)QueueHead->Request;
509
510
511 //
512 // move to next entry
513 //
514 Entry = Entry->Flink;
515
516 //
517 // check if queue head is complete
518 //
519 IsQueueHeadComplete = Request->IsQueueHeadComplete(QueueHead);
520
521 DPRINT("Request %p QueueHead %p Complete %d\n", Request, QueueHead, IsQueueHeadComplete);
522
523 //
524 // check if queue head is complete
525 //
526 if (IsQueueHeadComplete)
527 {
528 //
529 // current queue head is complete
530 //
531 QueueHeadCompletion(QueueHead, Status);
532
533 //
534 // ring door bell is going to be necessary
535 //
536 *ShouldRingDoorBell = TRUE;
537 }
538 }
539
540 //
541 // release lock
542 //
543 KeReleaseSpinLock(&m_Lock, OldLevel);
544 }
545
546
547 VOID
548 CUSBQueue::InterruptCallback(
549 IN NTSTATUS Status,
550 OUT PULONG ShouldRingDoorBell)
551 {
552
553 DPRINT("CUSBQueue::InterruptCallback\n");
554
555 //
556 // iterate asynchronous list
557 //
558 *ShouldRingDoorBell = FALSE;
559 ProcessAsyncList(Status, ShouldRingDoorBell);
560
561 //
562 // TODO: implement periodic schedule processing
563 //
564 }
565
566 VOID
567 CUSBQueue::QueueHeadCleanup(
568 PQUEUE_HEAD CurrentQH)
569 {
570 IUSBRequest * Request;
571 BOOLEAN ShouldReleaseWhenDone;
572 USBD_STATUS UrbStatus;
573
574 //
575 // sanity checks
576 //
577 PC_ASSERT(CurrentQH->Token.Bits.Active == 0);
578 PC_ASSERT(CurrentQH->Request);
579
580
581 //
582 // get request
583 //
584 Request = (IUSBRequest*)CurrentQH->Request;
585
586 //
587 // sanity check
588 //
589 PC_ASSERT(Request);
590
591 //
592 // check if the queue head was completed with errors
593 //
594 if (CurrentQH->Token.Bits.Halted)
595 {
596 if (CurrentQH->Token.Bits.DataBufferError)
597 {
598 //
599 // data buffer error
600 //
601 UrbStatus = USBD_STATUS_DATA_BUFFER_ERROR;
602 }
603 else if (CurrentQH->Token.Bits.BabbleDetected)
604 {
605 //
606 // babble detected
607 //
608 UrbStatus = USBD_STATUS_BABBLE_DETECTED;
609 }
610 else
611 {
612 //
613 // stall pid
614 //
615 UrbStatus = USBD_STATUS_STALL_PID;
616 }
617 }
618 else
619 {
620 //
621 // well done ;)
622 //
623 UrbStatus = USBD_STATUS_SUCCESS;
624 }
625
626 //
627 // notify request that a queue head has been completed
628 //
629 Request->CompletionCallback(STATUS_SUCCESS /*FIXME*/, UrbStatus, CurrentQH);
630
631 //
632 // let IUSBRequest free the queue head
633 //
634 Request->FreeQueueHead(CurrentQH);
635
636 //
637 // check if we should release request when done
638 //
639 ShouldReleaseWhenDone = Request->ShouldReleaseRequestAfterCompletion();
640
641 //
642 // release reference when the request was added
643 //
644 Request->Release();
645
646 //
647 // check if the operation was asynchronous
648 //
649 if (ShouldReleaseWhenDone)
650 {
651 //
652 // release outstanding reference count
653 //
654 Request->Release();
655 }
656
657 //
658 // request is now released
659 //
660 }
661
662 VOID
663 CUSBQueue::CompleteAsyncRequests()
664 {
665 KIRQL OldLevel;
666 PLIST_ENTRY Entry;
667 PQUEUE_HEAD CurrentQH;
668
669 DPRINT("CUSBQueue::CompleteAsyncRequests\n");
670
671 //
672 // first acquire request lock
673 //
674 KeAcquireSpinLock(&m_Lock, &OldLevel);
675
676 //
677 // the list should not be empty
678 //
679 PC_ASSERT(!IsListEmpty(&m_CompletedRequestAsyncList));
680
681 while(!IsListEmpty(&m_CompletedRequestAsyncList))
682 {
683 //
684 // remove first entry
685 //
686 Entry = RemoveHeadList(&m_CompletedRequestAsyncList);
687
688 //
689 // get queue head structure
690 //
691 CurrentQH = (PQUEUE_HEAD)CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
692
693 //
694 // complete request now
695 //
696 QueueHeadCleanup(CurrentQH);
697 }
698
699 //
700 // is there a pending async entry
701 //
702 if (!IsListEmpty(&m_PendingRequestAsyncList))
703 {
704 //
705 // remove first entry
706 //
707 Entry = RemoveHeadList(&m_CompletedRequestAsyncList);
708
709 //
710 // get queue head structure
711 //
712 CurrentQH = (PQUEUE_HEAD)CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
713
714 //
715 // Add it to the pending list
716 //
717 LinkQueueHead(AsyncListQueueHead, CurrentQH);
718 }
719
720 //
721 // release lock
722 //
723 KeReleaseSpinLock(&m_Lock, OldLevel);
724 }
725
726 NTSTATUS
727 CreateUSBQueue(
728 PUSBQUEUE *OutUsbQueue)
729 {
730 PUSBQUEUE This;
731
732 //
733 // allocate controller
734 //
735 This = new(NonPagedPool, TAG_USBEHCI) CUSBQueue(0);
736 if (!This)
737 {
738 //
739 // failed to allocate
740 //
741 return STATUS_INSUFFICIENT_RESOURCES;
742 }
743
744 //
745 // add reference count
746 //
747 This->AddRef();
748
749 //
750 // return result
751 //
752 *OutUsbQueue = (PUSBQUEUE)This;
753
754 //
755 // done
756 //
757 return STATUS_SUCCESS;
758 }