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