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