[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 PQUEUE_HEAD PendingListQueueHead;
55 LIST_ENTRY m_CompletedRequestAsyncList;
56
57 // queue head manipulation functions
58 VOID LinkQueueHead(PQUEUE_HEAD HeadQueueHead, PQUEUE_HEAD NewQueueHead);
59 VOID UnlinkQueueHead(PQUEUE_HEAD QueueHead);
60 VOID LinkQueueHeadChain(PQUEUE_HEAD HeadQueueHead, PQUEUE_HEAD NewQueueHead);
61 PQUEUE_HEAD UnlinkQueueHeadChain(PQUEUE_HEAD HeadQueueHead, ULONG Count);
62
63 // processes the async list
64 VOID ProcessAsyncList(IN NTSTATUS Status, OUT PULONG ShouldRingDoorBell);
65
66 // called for each completed queue head
67 VOID QueueHeadCompletion(PQUEUE_HEAD QueueHead, NTSTATUS Status);
68
69 // called when the completion queue is cleaned up
70 VOID QueueHeadCleanup(PQUEUE_HEAD QueueHead);
71 };
72
73 //=================================================================================================
74 // COM
75 //
76 NTSTATUS
77 STDMETHODCALLTYPE
78 CUSBQueue::QueryInterface(
79 IN REFIID refiid,
80 OUT PVOID* Output)
81 {
82 if (IsEqualGUIDAligned(refiid, IID_IUnknown))
83 {
84 *Output = PVOID(PUNKNOWN(this));
85 PUNKNOWN(*Output)->AddRef();
86 return STATUS_SUCCESS;
87 }
88
89 return STATUS_UNSUCCESSFUL;
90 }
91
92 NTSTATUS
93 CUSBQueue::Initialize(
94 IN PUSBHARDWAREDEVICE Hardware,
95 PDMA_ADAPTER AdapterObject,
96 IN OPTIONAL PKSPIN_LOCK Lock)
97 {
98 NTSTATUS Status = STATUS_SUCCESS;
99
100 DPRINT1("CUSBQueue::Initialize()\n");
101
102 ASSERT(Hardware);
103
104 //
105 // initialize device lock
106 //
107 KeInitializeSpinLock(&m_Lock);
108
109 //
110 // Get the AsyncQueueHead
111 //
112 AsyncListQueueHead = (PQUEUE_HEAD)Hardware->GetAsyncListRegister();
113
114 //
115 // Create the PendingListQueueHead from NONPAGEDPOOL. It will never be linked into the Asynclist Schedule
116 //
117 PendingListQueueHead = (PQUEUE_HEAD)ExAllocatePoolWithTag(NonPagedPool, sizeof(QUEUE_HEAD), TAG_USBEHCI);
118 if (!PendingListQueueHead)
119 {
120 DPRINT1("Pool Allocation failed!\n");
121 return STATUS_INSUFFICIENT_RESOURCES;
122 }
123
124 //
125 // Initialize the List Head
126 //
127 InitializeListHead(&PendingListQueueHead->LinkedQueueHeads);
128
129 //
130 // fake the queue head as the first queue head
131 //
132 PendingListQueueHead->PhysicalAddr = ((ULONG_PTR)AsyncListQueueHead | QH_TYPE_QH);
133
134 //
135 // Initialize completed async list head
136 //
137 InitializeListHead(&m_CompletedRequestAsyncList);
138
139
140 return Status;
141 }
142
143 ULONG
144 CUSBQueue::GetPendingRequestCount()
145 {
146 //
147 // Loop through the pending list and iterrate one for each QueueHead that
148 // has a IRP to complete.
149 //
150
151
152 return 0;
153 }
154
155 NTSTATUS
156 CUSBQueue::AddUSBRequest(
157 IUSBRequest * Request)
158 {
159 PQUEUE_HEAD QueueHead;
160 ASSERT(Request != NULL);
161
162 Request->GetQueueHead(&QueueHead);
163
164 //
165 // Add it to the pending list
166 //
167 LinkQueueHead(PendingListQueueHead, QueueHead);
168
169 //
170 // add extra reference which is released when the request is completed
171 //
172 Request->AddRef();
173
174 return STATUS_SUCCESS;
175 }
176
177 NTSTATUS
178 CUSBQueue::AddUSBRequest(
179 PURB Urb)
180 {
181 UNIMPLEMENTED
182 return STATUS_NOT_IMPLEMENTED;
183 }
184
185 NTSTATUS
186 CUSBQueue::CancelRequests()
187 {
188 UNIMPLEMENTED
189 return STATUS_NOT_IMPLEMENTED;
190 }
191
192 NTSTATUS
193 CUSBQueue::CreateUSBRequest(
194 IUSBRequest **OutRequest)
195 {
196 PUSBREQUEST UsbRequest;
197 NTSTATUS Status;
198
199 *OutRequest = NULL;
200 Status = InternalCreateUSBRequest(&UsbRequest);
201
202 if (NT_SUCCESS(Status))
203 {
204 *OutRequest = UsbRequest;
205 }
206
207 return Status;
208 }
209
210 //
211 // LinkQueueHead - Links one QueueHead to the end of HeadQueueHead list, updating HorizontalLinkPointer.
212 //
213 VOID
214 CUSBQueue::LinkQueueHead(
215 PQUEUE_HEAD HeadQueueHead,
216 PQUEUE_HEAD NewQueueHead)
217 {
218 PQUEUE_HEAD LastQueueHead, NextQueueHead;
219 PLIST_ENTRY Entry;
220 ASSERT(HeadQueueHead);
221 ASSERT(NewQueueHead);
222
223 //
224 // Link the LIST_ENTRYs
225 //
226 InsertTailList(&HeadQueueHead->LinkedQueueHeads, &NewQueueHead->LinkedQueueHeads);
227
228 //
229 // Update HLP for Previous QueueHead, which should be the last in list.
230 //
231 Entry = NewQueueHead->LinkedQueueHeads.Blink;
232 LastQueueHead = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
233 LastQueueHead->HorizontalLinkPointer = (NewQueueHead->PhysicalAddr | QH_TYPE_QH);
234
235 //
236 // Update HLP for NewQueueHead to point to next, which should be the HeadQueueHead
237 //
238 Entry = NewQueueHead->LinkedQueueHeads.Flink;
239 NextQueueHead = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
240 ASSERT(NextQueueHead == HeadQueueHead);
241 NewQueueHead->HorizontalLinkPointer = NextQueueHead->PhysicalAddr;
242 }
243
244 //
245 // UnlinkQueueHead - Unlinks one QueueHead, updating HorizontalLinkPointer.
246 //
247 VOID
248 CUSBQueue::UnlinkQueueHead(
249 PQUEUE_HEAD QueueHead)
250 {
251 PQUEUE_HEAD PreviousQH, NextQH;
252 PLIST_ENTRY Entry;
253
254 //
255 // sanity check: there must be at least one queue head with halted bit set
256 //
257 PC_ASSERT(QueueHead->Token.Bits.Halted == 0);
258
259 //
260 // get previous link
261 //
262 Entry = QueueHead->LinkedQueueHeads.Blink;
263
264 //
265 // get queue head structure
266 //
267 PreviousQH = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
268
269 //
270 // get next link
271 //
272 Entry = QueueHead->LinkedQueueHeads.Flink;
273
274 //
275 // get queue head structure
276 //
277 NextQH = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
278
279 //
280 // sanity check
281 //
282 ASSERT(QueueHead->HorizontalLinkPointer == (NextQH->PhysicalAddr | QH_TYPE_QH));
283
284 //
285 // remove queue head from linked list
286 //
287 PreviousQH->HorizontalLinkPointer = NextQH->PhysicalAddr | QH_TYPE_QH;
288
289 //
290 // remove software link
291 //
292 RemoveEntryList(&QueueHead->LinkedQueueHeads);
293 }
294
295 //
296 // LinkQueueHeadChain - Links a list of QueueHeads to the HeadQueueHead list, updating HorizontalLinkPointer.
297 //
298 VOID
299 CUSBQueue::LinkQueueHeadChain(
300 PQUEUE_HEAD HeadQueueHead,
301 PQUEUE_HEAD NewQueueHead)
302 {
303 PQUEUE_HEAD LastQueueHead;
304 PLIST_ENTRY Entry;
305 ASSERT(HeadQueueHead);
306 ASSERT(NewQueueHead);
307
308 //
309 // Find the last QueueHead in NewQueueHead
310 //
311 Entry = NewQueueHead->LinkedQueueHeads.Blink;
312 ASSERT(Entry != NewQueueHead->LinkedQueueHeads.Flink);
313 LastQueueHead = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
314
315 //
316 // Set the LinkPointer and Flink
317 //
318 LastQueueHead->HorizontalLinkPointer = HeadQueueHead->PhysicalAddr | QH_TYPE_QH;
319 LastQueueHead->LinkedQueueHeads.Flink = &HeadQueueHead->LinkedQueueHeads;
320
321 //
322 // Fine the last QueueHead in HeadQueueHead
323 //
324 Entry = HeadQueueHead->LinkedQueueHeads.Blink;
325 HeadQueueHead->LinkedQueueHeads.Blink = &LastQueueHead->LinkedQueueHeads;
326 LastQueueHead = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
327 LastQueueHead->LinkedQueueHeads.Flink = &NewQueueHead->LinkedQueueHeads;
328 LastQueueHead->HorizontalLinkPointer = NewQueueHead->PhysicalAddr | QH_TYPE_QH;
329 }
330
331 //
332 // UnlinkQueueHeadChain - Unlinks a list number of QueueHeads from HeadQueueHead list, updating HorizontalLinkPointer.
333 // returns the chain of QueueHeads removed from HeadQueueHead.
334 //
335 PQUEUE_HEAD
336 CUSBQueue::UnlinkQueueHeadChain(
337 PQUEUE_HEAD HeadQueueHead,
338 ULONG Count)
339 {
340 PQUEUE_HEAD LastQueueHead, FirstQueueHead;
341 PLIST_ENTRY Entry;
342 ULONG Index;
343
344 //
345 // Find the last QueueHead in NewQueueHead
346 //
347 Entry = &HeadQueueHead->LinkedQueueHeads;
348 FirstQueueHead = CONTAINING_RECORD(Entry->Flink, QUEUE_HEAD, LinkedQueueHeads);
349
350 for (Index = 0; Index < Count; Index++)
351 {
352 Entry = Entry->Flink;
353
354 if (Entry == &HeadQueueHead->LinkedQueueHeads)
355 {
356 DPRINT1("Warnnig; Only %d QueueHeads in HeadQueueHead\n", Index);
357 Count = Index + 1;
358 break;
359 }
360 }
361
362 LastQueueHead = CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
363 HeadQueueHead->LinkedQueueHeads.Flink = LastQueueHead->LinkedQueueHeads.Flink;
364 if (Count + 1 == Index)
365 {
366 HeadQueueHead->LinkedQueueHeads.Blink = &HeadQueueHead->LinkedQueueHeads;
367 }
368 else
369 HeadQueueHead->LinkedQueueHeads.Blink = LastQueueHead->LinkedQueueHeads.Flink;
370
371 FirstQueueHead->LinkedQueueHeads.Blink = &LastQueueHead->LinkedQueueHeads;
372 LastQueueHead->LinkedQueueHeads.Flink = &FirstQueueHead->LinkedQueueHeads;
373 LastQueueHead->HorizontalLinkPointer = TERMINATE_POINTER;
374 return FirstQueueHead;
375 }
376
377 VOID
378 CUSBQueue::QueueHeadCompletion(
379 PQUEUE_HEAD CurrentQH,
380 NTSTATUS Status)
381 {
382 IUSBRequest *Request;
383 USBD_STATUS UrbStatus;
384 PQUEUE_HEAD NewQueueHead;
385
386 //
387 // this function is called when a queue head has been completed
388 //
389 PC_ASSERT(CurrentQH->Token.Bits.Active == 0);
390
391 //
392 // get contained usb request
393 //
394 Request = (IUSBRequest*)CurrentQH->Request;
395
396 //
397 // sanity check
398 //
399 PC_ASSERT(Request);
400
401 //
402 // check if the queue head was completed with errors
403 //
404 if (CurrentQH->Token.Bits.Halted)
405 {
406 if (CurrentQH->Token.Bits.DataBufferError)
407 {
408 //
409 // data buffer error
410 //
411 UrbStatus = USBD_STATUS_DATA_BUFFER_ERROR;
412 }
413 else if (CurrentQH->Token.Bits.BabbleDetected)
414 {
415 //
416 // babble detected
417 //
418 UrbStatus = USBD_STATUS_BABBLE_DETECTED;
419 }
420 else
421 {
422 //
423 // stall pid
424 //
425 UrbStatus = USBD_STATUS_STALL_PID;
426 }
427 }
428 else
429 {
430 //
431 // well done ;)
432 //
433 UrbStatus = USBD_STATUS_SUCCESS;
434 }
435
436 //
437 // notify request that a queue head has been completed
438 //
439 Request->CompletionCallback(Status, UrbStatus, CurrentQH);
440
441 //
442 // now unlink the queue head
443 // FIXME: implement chained queue heads
444 //
445 UnlinkQueueHead(CurrentQH);
446
447 //
448 // check if the request is complete
449 //
450 if (Request->IsRequestComplete() == FALSE)
451 {
452 //
453 // request is still in complete
454 // get new queue head
455 //
456 Status = Request->GetQueueHead(&NewQueueHead);
457
458 //
459 // add to pending list
460 //
461 LinkQueueHead(PendingListQueueHead, NewQueueHead);
462 }
463 else
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
482 //
483 // lock completed async list
484 //
485 KeAcquireSpinLock(&m_Lock, &OldLevel);
486
487 //
488 // walk async list
489 //
490 Entry = PendingListQueueHead->LinkedQueueHeads.Flink;
491
492 while(Entry != &PendingListQueueHead->LinkedQueueHeads)
493 {
494 //
495 // get queue head structure
496 //
497 QueueHead = (PQUEUE_HEAD)CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
498
499 //
500 // sanity check
501 //
502 PC_ASSERT(QueueHead->Request);
503
504 //
505 // get IUSBRequest interface
506 //
507 Request = (IUSBRequest*)QueueHead->Request;
508
509
510 //
511 // move to next entry
512 //
513 Entry = Entry->Flink;
514
515 //
516 // check if queue head is complete
517 //
518 if (Request->IsQueueHeadComplete(QueueHead))
519 {
520 //
521 // current queue head is complete
522 //
523 QueueHeadCompletion(QueueHead, Status);
524
525 //
526 // ring door bell is going to be necessary
527 //
528 *ShouldRingDoorBell = TRUE;
529 }
530 }
531
532 //
533 // release lock
534 //
535 KeReleaseSpinLock(&m_Lock, OldLevel);
536
537 }
538
539
540 VOID
541 CUSBQueue::InterruptCallback(
542 IN NTSTATUS Status,
543 OUT PULONG ShouldRingDoorBell)
544 {
545 //
546 // iterate asynchronous list
547 //
548 *ShouldRingDoorBell = FALSE;
549 ProcessAsyncList(Status, ShouldRingDoorBell);
550
551 //
552 // TODO: implement periodic schedule processing
553 //
554 }
555
556 VOID
557 CUSBQueue::QueueHeadCleanup(
558 PQUEUE_HEAD CurrentQH)
559 {
560 IUSBRequest * Request;
561 BOOLEAN ShouldReleaseWhenDone;
562
563 //
564 // sanity checks
565 //
566 PC_ASSERT(CurrentQH->Token.Bits.Active == 0);
567 PC_ASSERT(CurrentQH->Request);
568
569 //
570 // get request
571 //
572 Request = (IUSBRequest*)CurrentQH->Request;
573
574 //
575 // let IUSBRequest free the queue head
576 //
577 Request->FreeQueueHead(CurrentQH);
578
579 //
580 // check if we should release request when done
581 //
582 ShouldReleaseWhenDone = Request->ShouldReleaseRequestAfterCompletion();
583
584 //
585 // release reference when the request was added
586 //
587 Request->Release();
588
589 //
590 // check if the operation was asynchronous
591 //
592 if (ShouldReleaseWhenDone)
593 {
594 //
595 // release outstanding reference count
596 //
597 Request->Release();
598 }
599
600 //
601 // request is now released
602 //
603 }
604
605 VOID
606 CUSBQueue::CompleteAsyncRequests()
607 {
608 KIRQL OldLevel;
609 PLIST_ENTRY Entry;
610 PQUEUE_HEAD CurrentQH;
611
612 //
613 // first acquire request lock
614 //
615 KeAcquireSpinLock(&m_Lock, &OldLevel);
616
617 //
618 // the list should not be empty
619 //
620 PC_ASSERT(!IsListEmpty(&m_CompletedRequestAsyncList));
621
622 while(!IsListEmpty(&m_CompletedRequestAsyncList))
623 {
624 //
625 // remove first entry
626 //
627 Entry = RemoveHeadList(&m_CompletedRequestAsyncList);
628
629 //
630 // get queue head structure
631 //
632 CurrentQH = (PQUEUE_HEAD)CONTAINING_RECORD(Entry, QUEUE_HEAD, LinkedQueueHeads);
633
634 //
635 // complete request now
636 //
637 QueueHeadCleanup(CurrentQH);
638 }
639
640 //
641 // release lock
642 //
643 KeReleaseSpinLock(&m_Lock, OldLevel);
644 }
645
646 NTSTATUS
647 CreateUSBQueue(
648 PUSBQUEUE *OutUsbQueue)
649 {
650 PUSBQUEUE This;
651
652 //
653 // allocate controller
654 //
655 This = new(NonPagedPool, TAG_USBEHCI) CUSBQueue(0);
656 if (!This)
657 {
658 //
659 // failed to allocate
660 //
661 return STATUS_INSUFFICIENT_RESOURCES;
662 }
663
664 //
665 // add reference count
666 //
667 This->AddRef();
668
669 //
670 // return result
671 //
672 *OutUsbQueue = (PUSBQUEUE)This;
673
674 //
675 // done
676 //
677 return STATUS_SUCCESS;
678 }