Sync with trunk r63502.
[reactos.git] / drivers / usb / usbuhci / usb_queue.cpp
1 /*
2 * PROJECT: ReactOS Universal Serial Bus Host Controller Interface
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: drivers/usb/usbuhci/usb_queue.cpp
5 * PURPOSE: USB UHCI device driver.
6 * PROGRAMMERS:
7 * Michael Martin (michael.martin@reactos.org)
8 * Johannes Anderwald (johannes.anderwald@reactos.org)
9 */
10
11 #include "usbuhci.h"
12
13 #define NDEBUG
14 #include <debug.h>
15
16 class CUSBQueue : public IUHCIQueue
17 {
18 public:
19 STDMETHODIMP QueryInterface( REFIID InterfaceId, PVOID* Interface);
20
21 STDMETHODIMP_(ULONG) AddRef()
22 {
23 InterlockedIncrement(&m_Ref);
24 return m_Ref;
25 }
26 STDMETHODIMP_(ULONG) Release()
27 {
28 InterlockedDecrement(&m_Ref);
29
30 if (!m_Ref)
31 {
32 delete this;
33 return 0;
34 }
35 return m_Ref;
36 }
37
38 // com
39 IMP_IUSBQUEUE
40 IMP_IUHCIQUEUE
41
42 // local
43 VOID LinkQueueHead(PUHCI_QUEUE_HEAD QueueHead, PUHCI_QUEUE_HEAD NextQueueHead);
44 VOID UnLinkQueueHead(PUHCI_QUEUE_HEAD PreviousQueueHead, PUHCI_QUEUE_HEAD NextQueueHead);
45 BOOLEAN IsQueueHeadComplete(PUHCI_QUEUE_HEAD QueueHead);
46 NTSTATUS AddQueueHead(PUHCI_QUEUE_HEAD NewQueueHead);
47 VOID QueueHeadCleanup(IN PUHCI_QUEUE_HEAD QueueHead, IN PUHCI_QUEUE_HEAD PreviousQueueHead, OUT PUHCI_QUEUE_HEAD *NextQueueHead);
48
49
50 // constructor / destructor
51 CUSBQueue(IUnknown *OuterUnknown){}
52 virtual ~CUSBQueue(){}
53
54 protected:
55 LONG m_Ref; // reference count
56 KSPIN_LOCK m_Lock; // list lock
57 PUHCIHARDWAREDEVICE m_Hardware; // hardware
58
59 };
60
61 //=================================================================================================
62 // COM
63 //
64 NTSTATUS
65 STDMETHODCALLTYPE
66 CUSBQueue::QueryInterface(
67 IN REFIID refiid,
68 OUT PVOID* Output)
69 {
70 if (IsEqualGUIDAligned(refiid, IID_IUnknown))
71 {
72 *Output = PVOID(PUNKNOWN(this));
73 PUNKNOWN(*Output)->AddRef();
74 return STATUS_SUCCESS;
75 }
76
77 return STATUS_UNSUCCESSFUL;
78 }
79
80 NTSTATUS
81 CUSBQueue::Initialize(
82 IN PUSBHARDWAREDEVICE Hardware,
83 IN PDMA_ADAPTER AdapterObject,
84 IN PDMAMEMORYMANAGER MemManager,
85 IN OPTIONAL PKSPIN_LOCK Lock)
86 {
87
88 //
89 // store hardware
90 //
91 m_Hardware = PUHCIHARDWAREDEVICE(Hardware);
92
93 //
94 // initialize spinlock
95 //
96 KeInitializeSpinLock(&m_Lock);
97 return STATUS_SUCCESS;
98 }
99
100 NTSTATUS
101 CUSBQueue::AddQueueHead(
102 PUHCI_QUEUE_HEAD NewQueueHead)
103 {
104 PUHCIREQUEST Request;
105 PUHCI_QUEUE_HEAD QueueHead = NULL;
106
107
108 //
109 // get request
110 //
111 Request = (PUHCIREQUEST)NewQueueHead->Request;
112 if (!Request)
113 {
114 //
115 // no request
116 //
117 return STATUS_INVALID_PARAMETER;
118 }
119
120 if (Request->GetTransferType() == USB_ENDPOINT_TYPE_CONTROL)
121 {
122 //
123 // get device speed
124 //
125 if (Request->GetDeviceSpeed() == UsbLowSpeed)
126 {
127 //
128 // use low speed queue
129 //
130 m_Hardware->GetQueueHead(UHCI_LOW_SPEED_CONTROL_QUEUE, &QueueHead);
131 }
132 else
133 {
134 //
135 // use full speed queue
136 //
137 m_Hardware->GetQueueHead(UHCI_FULL_SPEED_CONTROL_QUEUE, &QueueHead);
138 }
139 }
140 else if (Request->GetTransferType() == USB_ENDPOINT_TYPE_BULK)
141 {
142 //
143 // use full speed queue
144 //
145 m_Hardware->GetQueueHead(UHCI_BULK_QUEUE, &QueueHead);
146 }
147 else if (Request->GetTransferType() == USB_ENDPOINT_TYPE_INTERRUPT)
148 {
149 //
150 // use full speed queue
151 //
152 m_Hardware->GetQueueHead(UHCI_INTERRUPT_QUEUE, &QueueHead);
153 }
154 else if (Request->GetTransferType() == USB_ENDPOINT_TYPE_INTERRUPT)
155 {
156 //
157 // use full speed queue
158 //
159 m_Hardware->GetQueueHead(UHCI_INTERRUPT_QUEUE, &QueueHead);
160 }
161
162 //
163 // FIXME support isochronous
164 //
165 ASSERT(QueueHead);
166
167 //
168 // add reference
169 //
170 Request->AddRef();
171
172 //
173 // now link the new queue head
174 //
175 LinkQueueHead(QueueHead, NewQueueHead);
176 return STATUS_SUCCESS;
177
178 }
179
180 NTSTATUS
181 CUSBQueue::AddUSBRequest(
182 IUSBRequest * Req)
183 {
184 PUHCI_QUEUE_HEAD NewQueueHead;
185 NTSTATUS Status;
186 PUHCIREQUEST Request;
187
188 // get request
189 Request = (PUHCIREQUEST)Req;
190
191 //
192 // get queue head
193 //
194 Status = Request->GetEndpointDescriptor(&NewQueueHead);
195 if (!NT_SUCCESS(Status))
196 {
197 //
198 // failed to create queue head
199 //
200 DPRINT1("[USBUHCI] Failed to create queue head %x\n", Status);
201 return Status;
202 }
203
204 //
205 // sanity check
206 //
207 ASSERT(PVOID(Request) == NewQueueHead->Request);
208
209 //
210 // add queue head
211 //
212 DPRINT("AddUSBRequest Request %p\n", Request);
213 DPRINT("NewQueueHead %p\n", NewQueueHead);
214 return AddQueueHead(NewQueueHead);
215 }
216
217 VOID
218 CUSBQueue::LinkQueueHead(
219 IN PUHCI_QUEUE_HEAD QueueHead,
220 IN PUHCI_QUEUE_HEAD NextQueueHead)
221 {
222 NextQueueHead->LinkPhysical = QueueHead->LinkPhysical;
223 NextQueueHead->NextLogicalDescriptor = QueueHead->NextLogicalDescriptor;
224
225 QueueHead->LinkPhysical = NextQueueHead->PhysicalAddress | QH_NEXT_IS_QH;
226 QueueHead->NextLogicalDescriptor = (PVOID)NextQueueHead;
227 }
228
229
230 VOID
231 CUSBQueue::UnLinkQueueHead(
232 PUHCI_QUEUE_HEAD QueueHeadToRemove,
233 PUHCI_QUEUE_HEAD PreviousQueueHead)
234 {
235 PreviousQueueHead->LinkPhysical = QueueHeadToRemove->LinkPhysical;
236 PreviousQueueHead->NextLogicalDescriptor = QueueHeadToRemove->NextLogicalDescriptor;
237 }
238
239 NTSTATUS
240 CUSBQueue::AbortDevicePipe(
241 IN UCHAR DeviceAddress,
242 IN PUSB_ENDPOINT_DESCRIPTOR EndDescriptor)
243 {
244 KIRQL OldLevel;
245 PUHCI_TRANSFER_DESCRIPTOR Descriptor;
246 PUHCI_QUEUE_HEAD QueueHead, PreviousQueueHead = NULL;
247 UCHAR EndpointAddress, EndpointDeviceAddress;
248 PUSB_ENDPOINT EndpointDescriptor;
249
250 // get descriptor
251 EndpointDescriptor = (PUSB_ENDPOINT)EndDescriptor;
252
253 // acquire lock
254 KeAcquireSpinLock(&m_Lock, &OldLevel);
255
256 // get queue head
257 m_Hardware->GetQueueHead(UHCI_INTERRUPT_QUEUE, &QueueHead);
258
259 while(QueueHead)
260 {
261 // get descriptor
262 Descriptor = (PUHCI_TRANSFER_DESCRIPTOR)QueueHead->NextElementDescriptor;
263
264 if (Descriptor)
265 {
266 // extract endpoint address
267 EndpointAddress = (Descriptor->Token >> TD_TOKEN_ENDPTADDR_SHIFT) & 0x0F;
268
269 // extract device address
270 EndpointDeviceAddress = (Descriptor->Token >> TD_TOKEN_DEVADDR_SHIFT) & 0x7F;
271
272 // check if they match
273 if (EndpointAddress == (EndpointDescriptor->EndPointDescriptor.bEndpointAddress & 0x0F) &&
274 DeviceAddress == EndpointDeviceAddress)
275 {
276 // cleanup queue head
277 QueueHeadCleanup(QueueHead, PreviousQueueHead, &QueueHead);
278 continue;
279 }
280 }
281
282 // move to next queue head
283 PreviousQueueHead = QueueHead;
284 QueueHead = (PUHCI_QUEUE_HEAD)QueueHead->NextLogicalDescriptor;
285 }
286
287 // release lock
288 KeReleaseSpinLock(&m_Lock, OldLevel);
289 return STATUS_SUCCESS;
290 }
291
292 NTSTATUS
293 CUSBQueue::CreateUSBRequest(
294 IUSBRequest **OutRequest)
295 {
296 PUSBREQUEST UsbRequest;
297 NTSTATUS Status;
298
299 *OutRequest = NULL;
300 Status = InternalCreateUSBRequest(&UsbRequest);
301
302 if (NT_SUCCESS(Status))
303 {
304 *OutRequest = UsbRequest;
305 }
306
307 return Status;
308 }
309
310 BOOLEAN
311 CUSBQueue::IsQueueHeadComplete(
312 IN PUHCI_QUEUE_HEAD QueueHead)
313 {
314 PUHCI_TRANSFER_DESCRIPTOR Descriptor;
315 ULONG ErrorCount;
316
317 if (QueueHead->NextElementDescriptor == NULL)
318 {
319 //
320 // empty queue head
321 //
322 DPRINT("QueueHead %p empty element physical\n", QueueHead);
323 return FALSE;
324 }
325
326 //
327 // check all descriptors
328 //
329 Descriptor = (PUHCI_TRANSFER_DESCRIPTOR)QueueHead->NextElementDescriptor;
330 while(Descriptor)
331 {
332 if (Descriptor->Status & TD_STATUS_ACTIVE)
333 {
334 //
335 // descriptor is still active
336 //
337 DPRINT("Descriptor %p is active Status %x BufferSize %lu\n", Descriptor, Descriptor->Status, Descriptor->BufferSize);
338 return FALSE;
339 }
340
341 if (Descriptor->Status & TD_ERROR_MASK)
342 {
343 //
344 // error happened
345 //
346 DPRINT1("[USBUHCI] Error detected at descriptor %p Physical %x\n", Descriptor, Descriptor->PhysicalAddress);
347
348 //
349 // get error count
350 //
351 ErrorCount = (Descriptor->Status >> TD_ERROR_COUNT_SHIFT) & TD_ERROR_COUNT_MASK;
352 if (ErrorCount == 0)
353 {
354 //
355 // error retry count elapsed
356 //
357 DPRINT1("[USBUHCI] ErrorBuffer %x TimeOut %x Nak %x BitStuff %x\n",
358 Descriptor->Status & TD_STATUS_ERROR_BUFFER,
359 Descriptor->Status & TD_STATUS_ERROR_TIMEOUT,
360 Descriptor->Status & TD_STATUS_ERROR_NAK,
361 Descriptor->Status & TD_STATUS_ERROR_BITSTUFF);
362 return TRUE;
363 }
364 else if (Descriptor->Status & TD_STATUS_ERROR_BABBLE)
365 {
366 //
367 // babble error
368 //
369 DPRINT1("[USBUHCI] Babble detected\n");
370 return TRUE;
371 }
372 else
373 {
374 //
375 // stall detected
376 //
377 DPRINT1("[USBUHCI] Stall detected\n");
378 }
379 }
380
381 //
382 // move to next descriptor
383 //
384 Descriptor = (PUHCI_TRANSFER_DESCRIPTOR)Descriptor->NextLogicalDescriptor;
385 }
386
387 //
388 // request is complete
389 //
390 return TRUE;
391 }
392
393 VOID
394 CUSBQueue::QueueHeadCleanup(
395 IN PUHCI_QUEUE_HEAD QueueHead,
396 IN PUHCI_QUEUE_HEAD PreviousQueueHead,
397 OUT PUHCI_QUEUE_HEAD *NextQueueHead)
398 {
399 PUHCIREQUEST Request;
400 PUHCI_QUEUE_HEAD NewQueueHead;
401 NTSTATUS Status;
402
403 //
404 // unlink queue head
405 //
406 UnLinkQueueHead(QueueHead, PreviousQueueHead);
407
408 //
409 // get next queue head
410 //
411 *NextQueueHead = (PUHCI_QUEUE_HEAD)PreviousQueueHead->NextLogicalDescriptor;
412 ASSERT(*NextQueueHead != QueueHead);
413
414 //
415 // the queue head is complete, is the transfer now completed?
416 //
417 Request = (PUHCIREQUEST)QueueHead->Request;
418 ASSERT(Request);
419
420 //
421 // free queue head
422 //
423 DPRINT("Request %p\n", Request);
424 Request->FreeEndpointDescriptor(QueueHead);
425
426 //
427 // check if transfer is complete
428 //
429 if (Request->IsRequestComplete())
430 {
431 //
432 // the transfer is complete
433 //
434 Request->CompletionCallback();
435 Request->Release();
436 return;
437 }
438
439 //
440 // grab new queue head
441 //
442 Status = Request->GetEndpointDescriptor(&NewQueueHead);
443 if (!NT_SUCCESS(Status))
444 {
445 //
446 // failed to get new queue head
447 //
448 DPRINT1("[USBUHCI] Failed to get new queue head with %x\n", Status);
449 Request->CompletionCallback();
450 Request->Release();
451 return;
452 }
453
454 //
455 // Link queue head
456 //
457 Status = AddQueueHead(NewQueueHead);
458 if (!NT_SUCCESS(Status))
459 {
460 //
461 // failed to get new queue head
462 //
463 DPRINT1("[USBUHCI] Failed to add queue head with %x\n", Status);
464 Request->CompletionCallback();
465 Request->Release();
466 return;
467 }
468
469 }
470
471 VOID
472 CUSBQueue::TransferInterrupt(
473 UCHAR ErrorInterrupt)
474 {
475 KIRQL OldLevel;
476 PUHCI_QUEUE_HEAD QueueHead, PreviousQueueHead = NULL;
477 BOOLEAN IsComplete;
478
479 //
480 // acquire lock
481 //
482 KeAcquireSpinLock(&m_Lock, &OldLevel);
483
484 //
485 // get queue head
486 //
487 m_Hardware->GetQueueHead(UHCI_INTERRUPT_QUEUE, &QueueHead);
488
489 while(QueueHead)
490 {
491 //
492 // is queue head complete
493 //
494 DPRINT("QueueHead %p\n", QueueHead);
495 IsComplete = IsQueueHeadComplete(QueueHead);
496 if (IsComplete)
497 {
498 //
499 // cleanup queue head
500 //
501 QueueHeadCleanup(QueueHead, PreviousQueueHead, &QueueHead);
502 continue;
503 }
504
505 //
506 // backup previous queue head
507 //
508 PreviousQueueHead = QueueHead;
509
510 //
511 // get next queue head
512 //
513 QueueHead = (PUHCI_QUEUE_HEAD)QueueHead->NextLogicalDescriptor;
514 }
515
516 //
517 // release lock
518 //
519 KeReleaseSpinLock(&m_Lock, OldLevel);
520 }
521
522 NTSTATUS
523 NTAPI
524 CreateUSBQueue(
525 PUSBQUEUE *OutUsbQueue)
526 {
527 PUSBQUEUE This;
528
529 //
530 // allocate controller
531 //
532 This = new(NonPagedPool, TAG_USBUHCI) CUSBQueue(0);
533 if (!This)
534 {
535 //
536 // failed to allocate
537 //
538 return STATUS_INSUFFICIENT_RESOURCES;
539 }
540
541 //
542 // add reference count
543 //
544 This->AddRef();
545
546 //
547 // return result
548 //
549 *OutUsbQueue = (PUSBQUEUE)This;
550
551 //
552 // done
553 //
554 return STATUS_SUCCESS;
555 }