]> git.proxmox.com Git - mirror_edk2.git/blame - MdeModulePkg/Bus/Pci/EhciDxe/EhciSched.c
code cleanup:
[mirror_edk2.git] / MdeModulePkg / Bus / Pci / EhciDxe / EhciSched.c
CommitLineData
913cb9dc 1/** @file\r
2\r
78c2ffb5 3 EHCI transfer scheduling routines.\r
4\r
597f4ee2 5Copyright (c) 2007 - 2009, Intel Corporation\r
913cb9dc 6All rights reserved. This program and the accompanying materials\r
7are licensed and made available under the terms and conditions of the BSD License\r
8which accompanies this distribution. The full text of the license may be found at\r
9http://opensource.org/licenses/bsd-license.php\r
10\r
11THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
12WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
13\r
913cb9dc 14**/\r
15\r
16#include "Ehci.h"\r
17\r
18\r
19/**\r
78c2ffb5 20 Create helper QTD/QH for the EHCI device.\r
913cb9dc 21\r
78c2ffb5 22 @param Ehc The EHCI device.\r
913cb9dc 23\r
78c2ffb5 24 @retval EFI_OUT_OF_RESOURCES Failed to allocate resource for helper QTD/QH.\r
25 @retval EFI_SUCCESS Helper QH/QTD are created.\r
913cb9dc 26\r
27**/\r
913cb9dc 28EFI_STATUS\r
29EhcCreateHelpQ (\r
30 IN USB2_HC_DEV *Ehc\r
31 )\r
32{\r
33 USB_ENDPOINT Ep;\r
34 EHC_QH *Qh;\r
35 QH_HW *QhHw;\r
36 EHC_QTD *Qtd;\r
37\r
38 //\r
39 // Create an inactive Qtd to terminate the short packet read.\r
40 //\r
41 Qtd = EhcCreateQtd (Ehc, NULL, 0, QTD_PID_INPUT, 0, 64);\r
42\r
43 if (Qtd == NULL) {\r
44 return EFI_OUT_OF_RESOURCES;\r
45 }\r
46\r
47 Qtd->QtdHw.Status = QTD_STAT_HALTED;\r
48 Ehc->ShortReadStop = Qtd;\r
49\r
50 //\r
51 // Create a QH to act as the EHC reclamation header.\r
52 // Set the header to loopback to itself.\r
53 //\r
54 Ep.DevAddr = 0;\r
55 Ep.EpAddr = 1;\r
56 Ep.Direction = EfiUsbDataIn;\r
57 Ep.DevSpeed = EFI_USB_SPEED_HIGH;\r
58 Ep.MaxPacket = 64;\r
59 Ep.HubAddr = 0;\r
60 Ep.HubPort = 0;\r
61 Ep.Toggle = 0;\r
62 Ep.Type = EHC_BULK_TRANSFER;\r
63 Ep.PollRate = 1;\r
64\r
65 Qh = EhcCreateQh (Ehc, &Ep);\r
66\r
67 if (Qh == NULL) {\r
68 return EFI_OUT_OF_RESOURCES;\r
69 }\r
70\r
71 QhHw = &Qh->QhHw;\r
72 QhHw->HorizonLink = QH_LINK (QhHw, EHC_TYPE_QH, FALSE);\r
73 QhHw->Status = QTD_STAT_HALTED;\r
74 QhHw->ReclaimHead = 1;\r
75 Ehc->ReclaimHead = Qh;\r
76\r
77 //\r
78 // Create a dummy QH to act as the terminator for periodical schedule\r
79 //\r
80 Ep.EpAddr = 2;\r
81 Ep.Type = EHC_INT_TRANSFER_SYNC;\r
82\r
83 Qh = EhcCreateQh (Ehc, &Ep);\r
84\r
85 if (Qh == NULL) {\r
86 return EFI_OUT_OF_RESOURCES;\r
87 }\r
88\r
89 Qh->QhHw.Status = QTD_STAT_HALTED;\r
90 Ehc->PeriodOne = Qh;\r
91\r
92 return EFI_SUCCESS;\r
93}\r
94\r
95\r
913cb9dc 96/**\r
78c2ffb5 97 Initialize the schedule data structure such as frame list.\r
913cb9dc 98\r
78c2ffb5 99 @param Ehc The EHCI device to init schedule data.\r
913cb9dc 100\r
78c2ffb5 101 @retval EFI_OUT_OF_RESOURCES Failed to allocate resource to init schedule data.\r
102 @retval EFI_SUCCESS The schedule data is initialized.\r
913cb9dc 103\r
104**/\r
105EFI_STATUS\r
106EhcInitSched (\r
107 IN USB2_HC_DEV *Ehc\r
108 )\r
109{\r
110 EFI_PCI_IO_PROTOCOL *PciIo;\r
111 VOID *Buf;\r
112 EFI_PHYSICAL_ADDRESS PhyAddr;\r
113 VOID *Map;\r
114 UINTN Pages;\r
115 UINTN Bytes;\r
116 UINTN Index;\r
117 UINT32 *Desc;\r
118 EFI_STATUS Status;\r
119\r
120 //\r
121 // First initialize the periodical schedule data:\r
122 // 1. Allocate and map the memory for the frame list\r
123 // 2. Create the help QTD/QH\r
124 // 3. Initialize the frame entries\r
125 // 4. Set the frame list register\r
126 //\r
127 PciIo = Ehc->PciIo;\r
128\r
129 Bytes = 4096;\r
130 Pages = EFI_SIZE_TO_PAGES (Bytes);\r
131\r
132 Status = PciIo->AllocateBuffer (\r
133 PciIo,\r
134 AllocateAnyPages,\r
135 EfiBootServicesData,\r
136 Pages,\r
137 &Buf,\r
138 0\r
139 );\r
140\r
141 if (EFI_ERROR (Status)) {\r
142 return EFI_OUT_OF_RESOURCES;\r
143 }\r
144\r
145 Status = PciIo->Map (\r
146 PciIo,\r
147 EfiPciIoOperationBusMasterCommonBuffer,\r
148 Buf,\r
149 &Bytes,\r
150 &PhyAddr,\r
151 &Map\r
152 );\r
153\r
154 if (EFI_ERROR (Status) || (Bytes != 4096)) {\r
155 PciIo->FreeBuffer (PciIo, Pages, Buf);\r
156 return EFI_OUT_OF_RESOURCES;\r
157 }\r
158\r
159 Ehc->PeriodFrameHost = Buf;\r
160 Ehc->PeriodFrame = (VOID *) ((UINTN) PhyAddr);\r
161 Ehc->PeriodFrameMap = Map;\r
162 Ehc->High32bitAddr = EHC_HIGH_32BIT (PhyAddr);\r
163\r
164 //\r
165 // Init memory pool management then create the helper\r
166 // QTD/QH. If failed, previously allocated resources\r
167 // will be freed by EhcFreeSched\r
168 //\r
169 Ehc->MemPool = UsbHcInitMemPool (\r
170 PciIo,\r
171 EHC_BIT_IS_SET (Ehc->HcCapParams, HCCP_64BIT),\r
172 Ehc->High32bitAddr\r
173 );\r
174\r
175 if (Ehc->MemPool == NULL) {\r
176 return EFI_OUT_OF_RESOURCES;\r
177 }\r
178\r
179 Status = EhcCreateHelpQ (Ehc);\r
180\r
181 if (EFI_ERROR (Status)) {\r
182 return Status;\r
183 }\r
184\r
185 //\r
186 // Initialize the frame list entries then set the registers\r
187 //\r
188 Desc = (UINT32 *) Ehc->PeriodFrame;\r
189\r
190 for (Index = 0; Index < EHC_FRAME_LEN; Index++) {\r
191 Desc[Index] = QH_LINK (Ehc->PeriodOne, EHC_TYPE_QH, FALSE);\r
192 }\r
193\r
194 EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, EHC_LOW_32BIT (Ehc->PeriodFrame));\r
195\r
196 //\r
197 // Second initialize the asynchronous schedule:\r
198 // Only need to set the AsynListAddr register to\r
199 // the reclamation header\r
200 //\r
201 EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, EHC_LOW_32BIT (Ehc->ReclaimHead));\r
202 return EFI_SUCCESS;\r
203}\r
204\r
205\r
913cb9dc 206/**\r
207 Free the schedule data. It may be partially initialized.\r
208\r
78c2ffb5 209 @param Ehc The EHCI device.\r
913cb9dc 210\r
913cb9dc 211**/\r
212VOID\r
213EhcFreeSched (\r
214 IN USB2_HC_DEV *Ehc\r
215 )\r
216{\r
217 EFI_PCI_IO_PROTOCOL *PciIo;\r
218\r
219 EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, 0);\r
220 EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, 0);\r
221\r
222 if (Ehc->PeriodOne != NULL) {\r
223 UsbHcFreeMem (Ehc->MemPool, Ehc->PeriodOne, sizeof (EHC_QH));\r
224 Ehc->PeriodOne = NULL;\r
225 }\r
226\r
227 if (Ehc->ReclaimHead != NULL) {\r
228 UsbHcFreeMem (Ehc->MemPool, Ehc->ReclaimHead, sizeof (EHC_QH));\r
229 Ehc->ReclaimHead = NULL;\r
230 }\r
231\r
232 if (Ehc->ShortReadStop != NULL) {\r
233 UsbHcFreeMem (Ehc->MemPool, Ehc->ShortReadStop, sizeof (EHC_QTD));\r
234 Ehc->ShortReadStop = NULL;\r
235 }\r
236\r
237 if (Ehc->MemPool != NULL) {\r
238 UsbHcFreeMemPool (Ehc->MemPool);\r
239 Ehc->MemPool = NULL;\r
240 }\r
241\r
242 if (Ehc->PeriodFrame != NULL) {\r
243 PciIo = Ehc->PciIo;\r
244 ASSERT (PciIo != NULL);\r
245\r
246 PciIo->Unmap (PciIo, Ehc->PeriodFrameMap);\r
247\r
248 PciIo->FreeBuffer (\r
249 PciIo,\r
250 EFI_SIZE_TO_PAGES (EFI_PAGE_SIZE),\r
251 Ehc->PeriodFrameHost\r
252 );\r
253\r
254 Ehc->PeriodFrame = NULL;\r
255 }\r
256}\r
257\r
258\r
913cb9dc 259/**\r
260 Link the queue head to the asynchronous schedule list.\r
261 UEFI only supports one CTRL/BULK transfer at a time\r
262 due to its interfaces. This simplifies the AsynList\r
263 management: A reclamation header is always linked to\r
264 the AsyncListAddr, the only active QH is appended to it.\r
265\r
78c2ffb5 266 @param Ehc The EHCI device.\r
267 @param Qh The queue head to link.\r
913cb9dc 268\r
913cb9dc 269**/\r
270VOID\r
271EhcLinkQhToAsync (\r
272 IN USB2_HC_DEV *Ehc,\r
273 IN EHC_QH *Qh\r
274 )\r
275{\r
276 EHC_QH *Head;\r
277\r
278 //\r
279 // Append the queue head after the reclaim header, then\r
280 // fix the hardware visiable parts (EHCI R1.0 page 72).\r
281 // ReclaimHead is always linked to the EHCI's AsynListAddr.\r
282 //\r
283 Head = Ehc->ReclaimHead;\r
284\r
285 Qh->NextQh = Head->NextQh;\r
286 Head->NextQh = Qh;\r
287\r
288 Qh->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE);;\r
289 Head->QhHw.HorizonLink = QH_LINK (Qh, EHC_TYPE_QH, FALSE);\r
290}\r
291\r
292\r
293/**\r
294 Unlink a queue head from the asynchronous schedule list.\r
78c2ffb5 295 Need to synchronize with hardware.\r
913cb9dc 296\r
78c2ffb5 297 @param Ehc The EHCI device.\r
298 @param Qh The queue head to unlink.\r
913cb9dc 299\r
913cb9dc 300**/\r
301VOID\r
302EhcUnlinkQhFromAsync (\r
303 IN USB2_HC_DEV *Ehc,\r
304 IN EHC_QH *Qh\r
305 )\r
306{\r
307 EHC_QH *Head;\r
308 EFI_STATUS Status;\r
309\r
310 ASSERT (Ehc->ReclaimHead->NextQh == Qh);\r
311\r
312 //\r
313 // Remove the QH from reclamation head, then update the hardware\r
314 // visiable part: Only need to loopback the ReclaimHead. The Qh\r
315 // is pointing to ReclaimHead (which is staill in the list).\r
316 //\r
317 Head = Ehc->ReclaimHead;\r
318\r
319 Head->NextQh = Qh->NextQh;\r
320 Qh->NextQh = NULL;\r
321\r
322 Head->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE);\r
323\r
324 //\r
325 // Set and wait the door bell to synchronize with the hardware\r
326 //\r
41e8ff27 327 Status = EhcSetAndWaitDoorBell (Ehc, EHC_GENERIC_TIMEOUT);\r
913cb9dc 328\r
329 if (EFI_ERROR (Status)) {\r
1c619535 330 DEBUG ((EFI_D_ERROR, "EhcUnlinkQhFromAsync: Failed to synchronize with doorbell\n"));\r
913cb9dc 331 }\r
332}\r
333\r
334\r
335/**\r
336 Link a queue head for interrupt transfer to the periodic\r
337 schedule frame list. This code is very much the same as\r
338 that in UHCI.\r
339\r
78c2ffb5 340 @param Ehc The EHCI device.\r
341 @param Qh The queue head to link.\r
913cb9dc 342\r
913cb9dc 343**/\r
344VOID\r
345EhcLinkQhToPeriod (\r
346 IN USB2_HC_DEV *Ehc,\r
347 IN EHC_QH *Qh\r
348 )\r
349{\r
350 UINT32 *Frames;\r
351 UINTN Index;\r
352 EHC_QH *Prev;\r
353 EHC_QH *Next;\r
354\r
355 Frames = Ehc->PeriodFrame;\r
356\r
357 for (Index = 0; Index < EHC_FRAME_LEN; Index += Qh->Interval) {\r
358 //\r
359 // First QH can't be NULL because we always keep PeriodOne\r
360 // heads on the frame list\r
361 //\r
362 ASSERT (!EHC_LINK_TERMINATED (Frames[Index]));\r
363 Next = EHC_ADDR (Ehc->High32bitAddr, Frames[Index]);\r
364 Prev = NULL;\r
365\r
366 //\r
367 // Now, insert the queue head (Qh) into this frame:\r
368 // 1. Find a queue head with the same poll interval, just insert\r
369 // Qh after this queue head, then we are done.\r
370 //\r
371 // 2. Find the position to insert the queue head into:\r
372 // Previous head's interval is bigger than Qh's\r
373 // Next head's interval is less than Qh's\r
374 // Then, insert the Qh between then\r
375 //\r
376 while (Next->Interval > Qh->Interval) {\r
377 Prev = Next;\r
378 Next = Next->NextQh;\r
379 }\r
380\r
381 ASSERT (Next != NULL);\r
382\r
383 //\r
384 // The entry may have been linked into the frame by early insertation.\r
385 // For example: if insert a Qh with Qh.Interval == 4, and there is a Qh\r
386 // with Qh.Interval == 8 on the frame. If so, we are done with this frame.\r
387 // It isn't necessary to compare all the QH with the same interval to\r
388 // Qh. This is because if there is other QH with the same interval, Qh\r
389 // should has been inserted after that at Frames[0] and at Frames[0] it is\r
390 // impossible for (Next == Qh)\r
391 //\r
392 if (Next == Qh) {\r
393 continue;\r
394 }\r
395\r
396 if (Next->Interval == Qh->Interval) {\r
397 //\r
398 // If there is a QH with the same interval, it locates at\r
399 // Frames[0], and we can simply insert it after this QH. We\r
400 // are all done.\r
401 //\r
402 ASSERT ((Index == 0) && (Qh->NextQh == NULL));\r
403\r
404 Prev = Next;\r
405 Next = Next->NextQh;\r
406\r
407 Qh->NextQh = Next;\r
408 Prev->NextQh = Qh;\r
409\r
410 Qh->QhHw.HorizonLink = Prev->QhHw.HorizonLink;\r
411 Prev->QhHw.HorizonLink = QH_LINK (Qh, EHC_TYPE_QH, FALSE);\r
412 break;\r
413 }\r
414\r
415 //\r
416 // OK, find the right position, insert it in. If Qh's next\r
417 // link has already been set, it is in position. This is\r
418 // guarranted by 2^n polling interval.\r
419 //\r
420 if (Qh->NextQh == NULL) {\r
421 Qh->NextQh = Next;\r
422 Qh->QhHw.HorizonLink = QH_LINK (Next, EHC_TYPE_QH, FALSE);\r
423 }\r
424\r
425 if (Prev == NULL) {\r
426 Frames[Index] = QH_LINK (Qh, EHC_TYPE_QH, FALSE);\r
427 } else {\r
428 Prev->NextQh = Qh;\r
429 Prev->QhHw.HorizonLink = QH_LINK (Qh, EHC_TYPE_QH, FALSE);\r
430 }\r
431 }\r
432}\r
433\r
434\r
435/**\r
436 Unlink an interrupt queue head from the periodic\r
78c2ffb5 437 schedule frame list.\r
913cb9dc 438\r
78c2ffb5 439 @param Ehc The EHCI device.\r
440 @param Qh The queue head to unlink.\r
913cb9dc 441\r
913cb9dc 442**/\r
443VOID\r
444EhcUnlinkQhFromPeriod (\r
445 IN USB2_HC_DEV *Ehc,\r
446 IN EHC_QH *Qh\r
447 )\r
448{\r
449 UINT32 *Frames;\r
450 UINTN Index;\r
451 EHC_QH *Prev;\r
452 EHC_QH *This;\r
453\r
454 Frames = Ehc->PeriodFrame;\r
455\r
456 for (Index = 0; Index < EHC_FRAME_LEN; Index += Qh->Interval) {\r
457 //\r
458 // Frame link can't be NULL because we always keep PeroidOne\r
459 // on the frame list\r
460 //\r
461 ASSERT (!EHC_LINK_TERMINATED (Frames[Index]));\r
462 This = EHC_ADDR (Ehc->High32bitAddr, Frames[Index]);\r
463 Prev = NULL;\r
464\r
465 //\r
466 // Walk through the frame's QH list to find the\r
467 // queue head to remove\r
468 //\r
469 while ((This != NULL) && (This != Qh)) {\r
470 Prev = This;\r
471 This = This->NextQh;\r
472 }\r
473\r
474 //\r
475 // Qh may have already been unlinked from this frame\r
476 // by early action. See the comments in EhcLinkQhToPeriod.\r
477 //\r
478 if (This == NULL) {\r
479 continue;\r
480 }\r
481\r
482 if (Prev == NULL) {\r
483 //\r
484 // Qh is the first entry in the frame\r
485 //\r
486 Frames[Index] = Qh->QhHw.HorizonLink;\r
487 } else {\r
488 Prev->NextQh = Qh->NextQh;\r
489 Prev->QhHw.HorizonLink = Qh->QhHw.HorizonLink;\r
490 }\r
491 }\r
492}\r
493\r
494\r
913cb9dc 495/**\r
496 Check the URB's execution result and update the URB's\r
497 result accordingly.\r
498\r
78c2ffb5 499 @param Ehc The EHCI device.\r
500 @param Urb The URB to check result.\r
913cb9dc 501\r
502 @return Whether the result of URB transfer is finialized.\r
503\r
504**/\r
913cb9dc 505BOOLEAN\r
506EhcCheckUrbResult (\r
507 IN USB2_HC_DEV *Ehc,\r
508 IN URB *Urb\r
509 )\r
510{\r
511 LIST_ENTRY *Entry;\r
512 EHC_QTD *Qtd;\r
513 QTD_HW *QtdHw;\r
514 UINT8 State;\r
515 BOOLEAN Finished;\r
516\r
517 ASSERT ((Ehc != NULL) && (Urb != NULL) && (Urb->Qh != NULL));\r
518\r
519 Finished = TRUE;\r
520 Urb->Completed = 0;\r
521\r
522 Urb->Result = EFI_USB_NOERROR;\r
523\r
524 if (EhcIsHalt (Ehc) || EhcIsSysError (Ehc)) {\r
525 Urb->Result |= EFI_USB_ERR_SYSTEM;\r
526 goto ON_EXIT;\r
527 }\r
528\r
529 EFI_LIST_FOR_EACH (Entry, &Urb->Qh->Qtds) {\r
530 Qtd = EFI_LIST_CONTAINER (Entry, EHC_QTD, QtdList);\r
531 QtdHw = &Qtd->QtdHw;\r
532 State = (UINT8) QtdHw->Status;\r
533\r
534 if (EHC_BIT_IS_SET (State, QTD_STAT_HALTED)) {\r
535 //\r
536 // EHCI will halt the queue head when met some error.\r
537 // If it is halted, the result of URB is finialized.\r
538 //\r
539 if ((State & QTD_STAT_ERR_MASK) == 0) {\r
540 Urb->Result |= EFI_USB_ERR_STALL;\r
541 }\r
542\r
543 if (EHC_BIT_IS_SET (State, QTD_STAT_BABBLE_ERR)) {\r
544 Urb->Result |= EFI_USB_ERR_BABBLE;\r
545 }\r
546\r
547 if (EHC_BIT_IS_SET (State, QTD_STAT_BUFF_ERR)) {\r
548 Urb->Result |= EFI_USB_ERR_BUFFER;\r
549 }\r
550\r
551 if (EHC_BIT_IS_SET (State, QTD_STAT_TRANS_ERR) && (QtdHw->ErrCnt == 0)) {\r
552 Urb->Result |= EFI_USB_ERR_TIMEOUT;\r
553 }\r
554\r
555 Finished = TRUE;\r
556 goto ON_EXIT;\r
557\r
558 } else if (EHC_BIT_IS_SET (State, QTD_STAT_ACTIVE)) {\r
559 //\r
560 // The QTD is still active, no need to check furthur.\r
561 //\r
562 Urb->Result |= EFI_USB_ERR_NOTEXECUTE;\r
563\r
564 Finished = FALSE;\r
565 goto ON_EXIT;\r
566\r
567 } else {\r
568 //\r
569 // This QTD is finished OK or met short packet read. Update the\r
570 // transfer length if it isn't a setup.\r
571 //\r
572 if (QtdHw->Pid != QTD_PID_SETUP) {\r
573 Urb->Completed += Qtd->DataLen - QtdHw->TotalBytes;\r
574 }\r
575\r
576 if ((QtdHw->TotalBytes != 0) && (QtdHw->Pid == QTD_PID_INPUT)) {\r
1c619535 577 EhcDumpQh (Urb->Qh, "Short packet read", FALSE);\r
913cb9dc 578\r
579 //\r
580 // Short packet read condition. If it isn't a setup transfer,\r
581 // no need to check furthur: the queue head will halt at the\r
582 // ShortReadStop. If it is a setup transfer, need to check the\r
583 // Status Stage of the setup transfer to get the finial result\r
584 //\r
585 if (QtdHw->AltNext == QTD_LINK (Ehc->ShortReadStop, FALSE)) {\r
1c619535 586 DEBUG ((EFI_D_INFO, "EhcCheckUrbResult: Short packet read, break\n"));\r
913cb9dc 587\r
588 Finished = TRUE;\r
589 goto ON_EXIT;\r
590 }\r
591\r
1c619535 592 DEBUG ((EFI_D_INFO, "EhcCheckUrbResult: Short packet read, continue\n"));\r
913cb9dc 593 }\r
594 }\r
595 }\r
596\r
597ON_EXIT:\r
598 //\r
599 // Return the data toggle set by EHCI hardware, bulk and interrupt\r
600 // transfer will use this to initialize the next transaction. For\r
601 // Control transfer, it always start a new data toggle sequence for\r
602 // new transfer.\r
603 //\r
604 // NOTICE: don't move DT update before the loop, otherwise there is\r
605 // a race condition that DT is wrong.\r
606 //\r
607 Urb->DataToggle = (UINT8) Urb->Qh->QhHw.DataToggle;\r
608\r
609 return Finished;\r
610}\r
611\r
612\r
613/**\r
614 Execute the transfer by polling the URB. This is a synchronous operation.\r
615\r
78c2ffb5 616 @param Ehc The EHCI device.\r
617 @param Urb The URB to execute.\r
618 @param TimeOut The time to wait before abort, in millisecond.\r
913cb9dc 619\r
78c2ffb5 620 @return EFI_DEVICE_ERROR The transfer failed due to transfer error.\r
621 @return EFI_TIMEOUT The transfer failed due to time out.\r
622 @return EFI_SUCCESS The transfer finished OK.\r
913cb9dc 623\r
624**/\r
625EFI_STATUS\r
626EhcExecTransfer (\r
627 IN USB2_HC_DEV *Ehc,\r
628 IN URB *Urb,\r
629 IN UINTN TimeOut\r
630 )\r
631{\r
632 EFI_STATUS Status;\r
633 UINTN Index;\r
634 UINTN Loop;\r
635 BOOLEAN Finished;\r
636\r
637 Status = EFI_SUCCESS;\r
41e8ff27 638 Loop = (TimeOut * EHC_1_MILLISECOND / EHC_SYNC_POLL_INTERVAL) + 1;\r
913cb9dc 639 Finished = FALSE;\r
640\r
641 for (Index = 0; Index < Loop; Index++) {\r
642 Finished = EhcCheckUrbResult (Ehc, Urb);\r
643\r
644 if (Finished) {\r
645 break;\r
646 }\r
647\r
41e8ff27 648 gBS->Stall (EHC_SYNC_POLL_INTERVAL);\r
913cb9dc 649 }\r
650\r
651 if (!Finished) {\r
7df7393f 652 DEBUG ((EFI_D_ERROR, "EhcExecTransfer: transfer not finished in %dms\n", (UINT32)TimeOut));\r
1c619535 653 EhcDumpQh (Urb->Qh, NULL, FALSE);\r
913cb9dc 654\r
655 Status = EFI_TIMEOUT;\r
656\r
657 } else if (Urb->Result != EFI_USB_NOERROR) {\r
1c619535 658 DEBUG ((EFI_D_ERROR, "EhcExecTransfer: transfer failed with %x\n", Urb->Result));\r
659 EhcDumpQh (Urb->Qh, NULL, FALSE);\r
913cb9dc 660\r
661 Status = EFI_DEVICE_ERROR;\r
662 }\r
663\r
664 return Status;\r
665}\r
666\r
667\r
668/**\r
669 Delete a single asynchronous interrupt transfer for\r
78c2ffb5 670 the device and endpoint.\r
913cb9dc 671\r
78c2ffb5 672 @param Ehc The EHCI device.\r
673 @param DevAddr The address of the target device.\r
674 @param EpNum The endpoint of the target.\r
675 @param DataToggle Return the next data toggle to use.\r
913cb9dc 676\r
78c2ffb5 677 @retval EFI_SUCCESS An asynchronous transfer is removed.\r
678 @retval EFI_NOT_FOUND No transfer for the device is found.\r
913cb9dc 679\r
680**/\r
681EFI_STATUS\r
682EhciDelAsyncIntTransfer (\r
683 IN USB2_HC_DEV *Ehc,\r
684 IN UINT8 DevAddr,\r
685 IN UINT8 EpNum,\r
686 OUT UINT8 *DataToggle\r
687 )\r
688{\r
689 LIST_ENTRY *Entry;\r
690 LIST_ENTRY *Next;\r
691 URB *Urb;\r
692 EFI_USB_DATA_DIRECTION Direction;\r
693\r
78c2ffb5 694 Direction = (((EpNum & 0x80) != 0) ? EfiUsbDataIn : EfiUsbDataOut);\r
913cb9dc 695 EpNum &= 0x0F;\r
696\r
697 EFI_LIST_FOR_EACH_SAFE (Entry, Next, &Ehc->AsyncIntTransfers) {\r
698 Urb = EFI_LIST_CONTAINER (Entry, URB, UrbList);\r
699\r
700 if ((Urb->Ep.DevAddr == DevAddr) && (Urb->Ep.EpAddr == EpNum) &&\r
701 (Urb->Ep.Direction == Direction)) {\r
702 //\r
703 // Check the URB status to retrieve the next data toggle\r
704 // from the associated queue head.\r
705 //\r
706 EhcCheckUrbResult (Ehc, Urb);\r
707 *DataToggle = Urb->DataToggle;\r
708\r
709 EhcUnlinkQhFromPeriod (Ehc, Urb->Qh);\r
710 RemoveEntryList (&Urb->UrbList);\r
711\r
712 gBS->FreePool (Urb->Data);\r
713 EhcFreeUrb (Ehc, Urb);\r
714 return EFI_SUCCESS;\r
715 }\r
716 }\r
717\r
718 return EFI_NOT_FOUND;\r
719}\r
720\r
721\r
722/**\r
78c2ffb5 723 Remove all the asynchronous interrutp transfers.\r
913cb9dc 724\r
78c2ffb5 725 @param Ehc The EHCI device.\r
913cb9dc 726\r
913cb9dc 727**/\r
728VOID\r
729EhciDelAllAsyncIntTransfers (\r
730 IN USB2_HC_DEV *Ehc\r
731 )\r
732{\r
733 LIST_ENTRY *Entry;\r
734 LIST_ENTRY *Next;\r
735 URB *Urb;\r
736\r
737 EFI_LIST_FOR_EACH_SAFE (Entry, Next, &Ehc->AsyncIntTransfers) {\r
738 Urb = EFI_LIST_CONTAINER (Entry, URB, UrbList);\r
739\r
740 EhcUnlinkQhFromPeriod (Ehc, Urb->Qh);\r
741 RemoveEntryList (&Urb->UrbList);\r
742\r
743 gBS->FreePool (Urb->Data);\r
744 EhcFreeUrb (Ehc, Urb);\r
745 }\r
746}\r
747\r
50fa1b3a 748\r
78c2ffb5 749/**\r
1c619535 750 Flush data from PCI controller specific address to mapped system\r
50fa1b3a 751 memory address.\r
752\r
78c2ffb5 753 @param Ehc The EHCI device.\r
754 @param Urb The URB to unmap.\r
50fa1b3a 755\r
78c2ffb5 756 @retval EFI_SUCCESS Success to flush data to mapped system memory.\r
757 @retval EFI_DEVICE_ERROR Fail to flush data to mapped system memory.\r
1c619535 758\r
78c2ffb5 759**/\r
760EFI_STATUS\r
761EhcFlushAsyncIntMap (\r
762 IN USB2_HC_DEV *Ehc,\r
763 IN URB *Urb\r
764 )\r
50fa1b3a 765{\r
766 EFI_STATUS Status;\r
767 EFI_PHYSICAL_ADDRESS PhyAddr;\r
768 EFI_PCI_IO_PROTOCOL_OPERATION MapOp;\r
769 EFI_PCI_IO_PROTOCOL *PciIo;\r
770 UINTN Len;\r
771 VOID *Map;\r
772\r
773 PciIo = Ehc->PciIo;\r
774 Len = Urb->DataLen;\r
775\r
776 if (Urb->Ep.Direction == EfiUsbDataIn) {\r
777 MapOp = EfiPciIoOperationBusMasterWrite;\r
778 } else {\r
779 MapOp = EfiPciIoOperationBusMasterRead;\r
780 }\r
1c619535 781\r
50fa1b3a 782 Status = PciIo->Unmap (PciIo, Urb->DataMap);\r
783 if (EFI_ERROR (Status)) {\r
784 goto ON_ERROR;\r
785 }\r
786\r
787 Urb->DataMap = NULL;\r
788\r
789 Status = PciIo->Map (PciIo, MapOp, Urb->Data, &Len, &PhyAddr, &Map);\r
790 if (EFI_ERROR (Status) || (Len != Urb->DataLen)) {\r
791 goto ON_ERROR;\r
792 }\r
793\r
794 Urb->DataPhy = (VOID *) ((UINTN) PhyAddr);\r
795 Urb->DataMap = Map;\r
796 return EFI_SUCCESS;\r
797\r
798ON_ERROR:\r
799 return EFI_DEVICE_ERROR;\r
800}\r
801\r
913cb9dc 802\r
913cb9dc 803/**\r
78c2ffb5 804 Update the queue head for next round of asynchronous transfer.\r
913cb9dc 805\r
78c2ffb5 806 @param Urb The URB to update.\r
913cb9dc 807\r
913cb9dc 808**/\r
913cb9dc 809VOID\r
810EhcUpdateAsyncRequest (\r
811 IN URB *Urb\r
812 )\r
813{\r
814 LIST_ENTRY *Entry;\r
815 EHC_QTD *FirstQtd;\r
816 QH_HW *QhHw;\r
817 EHC_QTD *Qtd;\r
818 QTD_HW *QtdHw;\r
819 UINTN Index;\r
820\r
821 Qtd = NULL;\r
822\r
823 if (Urb->Result == EFI_USB_NOERROR) {\r
824 FirstQtd = NULL;\r
825\r
826 EFI_LIST_FOR_EACH (Entry, &Urb->Qh->Qtds) {\r
827 Qtd = EFI_LIST_CONTAINER (Entry, EHC_QTD, QtdList);\r
828\r
829 if (FirstQtd == NULL) {\r
830 FirstQtd = Qtd;\r
831 }\r
832\r
833 //\r
834 // Update the QTD for next round of transfer. Host control\r
835 // may change dt/Total Bytes to Transfer/C_Page/Cerr/Status/\r
836 // Current Offset. These fields need to be updated. DT isn't\r
837 // used by interrupt transfer. It uses DT in queue head.\r
838 // Current Offset is in Page[0], only need to reset Page[0]\r
839 // to initial data buffer.\r
840 //\r
841 QtdHw = &Qtd->QtdHw;\r
842 QtdHw->Status = QTD_STAT_ACTIVE;\r
843 QtdHw->ErrCnt = QTD_MAX_ERR;\r
844 QtdHw->CurPage = 0;\r
845 QtdHw->TotalBytes = (UINT32) Qtd->DataLen;\r
846 QtdHw->Page[0] = EHC_LOW_32BIT (Qtd->Data);\r
847 }\r
848\r
849 //\r
850 // Update QH for next round of transfer. Host control only\r
851 // touch the fields in transfer overlay area. Only need to\r
852 // zero out the overlay area and set NextQtd to the first\r
853 // QTD. DateToggle bit is left untouched.\r
854 //\r
855 QhHw = &Urb->Qh->QhHw;\r
856 QhHw->CurQtd = QTD_LINK (0, TRUE);\r
857 QhHw->AltQtd = 0;\r
858\r
859 QhHw->Status = 0;\r
860 QhHw->Pid = 0;\r
861 QhHw->ErrCnt = 0;\r
862 QhHw->CurPage = 0;\r
863 QhHw->IOC = 0;\r
864 QhHw->TotalBytes = 0;\r
865\r
866 for (Index = 0; Index < 5; Index++) {\r
867 QhHw->Page[Index] = 0;\r
868 QhHw->PageHigh[Index] = 0;\r
869 }\r
870\r
871 QhHw->NextQtd = QTD_LINK (FirstQtd, FALSE);\r
872 }\r
873\r
874 return ;\r
875}\r
876\r
877\r
878/**\r
78c2ffb5 879 Interrupt transfer periodic check handler.\r
913cb9dc 880\r
78c2ffb5 881 @param Event Interrupt event.\r
882 @param Context Pointer to USB2_HC_DEV.\r
913cb9dc 883\r
913cb9dc 884**/\r
885VOID\r
6d3ea23f 886EFIAPI\r
597f4ee2 887EhcMonitorAsyncRequests (\r
913cb9dc 888 IN EFI_EVENT Event,\r
889 IN VOID *Context\r
890 )\r
891{\r
892 USB2_HC_DEV *Ehc;\r
893 EFI_TPL OldTpl;\r
894 LIST_ENTRY *Entry;\r
895 LIST_ENTRY *Next;\r
896 BOOLEAN Finished;\r
897 UINT8 *ProcBuf;\r
898 URB *Urb;\r
50fa1b3a 899 EFI_STATUS Status;\r
913cb9dc 900\r
901 OldTpl = gBS->RaiseTPL (EHC_TPL);\r
902 Ehc = (USB2_HC_DEV *) Context;\r
903\r
904 EFI_LIST_FOR_EACH_SAFE (Entry, Next, &Ehc->AsyncIntTransfers) {\r
905 Urb = EFI_LIST_CONTAINER (Entry, URB, UrbList);\r
906\r
907 //\r
908 // Check the result of URB execution. If it is still\r
909 // active, check the next one.\r
910 //\r
911 Finished = EhcCheckUrbResult (Ehc, Urb);\r
912\r
913 if (!Finished) {\r
914 continue;\r
915 }\r
916\r
50fa1b3a 917 //\r
1c619535 918 // Flush any PCI posted write transactions from a PCI host\r
50fa1b3a 919 // bridge to system memory.\r
920 //\r
921 Status = EhcFlushAsyncIntMap (Ehc, Urb);\r
922 if (EFI_ERROR (Status)) {\r
597f4ee2 923 DEBUG ((EFI_D_ERROR, "EhcMonitorAsyncRequests: Fail to Flush AsyncInt Mapped Memeory\n"));\r
50fa1b3a 924 }\r
1c619535 925\r
913cb9dc 926 //\r
927 // Allocate a buffer then copy the transferred data for user.\r
928 // If failed to allocate the buffer, update the URB for next\r
929 // round of transfer. Ignore the data of this round.\r
930 //\r
931 ProcBuf = NULL;\r
932\r
933 if (Urb->Result == EFI_USB_NOERROR) {\r
934 ASSERT (Urb->Completed <= Urb->DataLen);\r
935\r
936 ProcBuf = AllocatePool (Urb->Completed);\r
937\r
938 if (ProcBuf == NULL) {\r
939 EhcUpdateAsyncRequest (Urb);\r
940 continue;\r
941 }\r
942\r
943 CopyMem (ProcBuf, Urb->Data, Urb->Completed);\r
944 }\r
945\r
946 EhcUpdateAsyncRequest (Urb);\r
947\r
948 //\r
949 // Leave error recovery to its related device driver. A\r
950 // common case of the error recovery is to re-submit the\r
951 // interrupt transfer which is linked to the head of the\r
952 // list. This function scans from head to tail. So the\r
953 // re-submitted interrupt transfer's callback function\r
954 // will not be called again in this round. Don't touch this\r
955 // URB after the callback, it may have been removed by the\r
956 // callback.\r
957 //\r
958 if (Urb->Callback != NULL) {\r
959 //\r
960 // Restore the old TPL, USB bus maybe connect device in\r
961 // his callback. Some drivers may has a lower TPL restriction.\r
962 //\r
963 gBS->RestoreTPL (OldTpl);\r
964 (Urb->Callback) (ProcBuf, Urb->Completed, Urb->Context, Urb->Result);\r
965 OldTpl = gBS->RaiseTPL (EHC_TPL);\r
966 }\r
967\r
968 if (ProcBuf != NULL) {\r
7351070b 969 FreePool (ProcBuf);\r
913cb9dc 970 }\r
971 }\r
972\r
973 gBS->RestoreTPL (OldTpl);\r
974}\r