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