]> git.proxmox.com Git - mirror_edk2.git/blame - MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c
UefiCpuPkg: Move AsmRelocateApLoopStart from Mpfuncs.nasm to AmdSev.nasm
[mirror_edk2.git] / MdeModulePkg / Bus / Pci / EhciPei / EhciSched.c
CommitLineData
4b1bf81c 1/** @file\r
2PEIM to produce gPeiUsb2HostControllerPpiGuid based on gPeiUsbControllerPpiGuid\r
3which is used to enable recovery function from USB Drivers.\r
4\r
d1102dba 5Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.<BR>\r
30980945 6Copyright (c) Microsoft Corporation.<BR>\r
d1102dba 7\r
9d510e61 8SPDX-License-Identifier: BSD-2-Clause-Patent\r
4b1bf81c 9\r
10**/\r
11\r
12#include "EhcPeim.h"\r
13\r
14/**\r
15 Create helper QTD/QH for the EHCI device.\r
d1102dba 16\r
4b1bf81c 17 @param Ehc The EHCI device.\r
18\r
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
21\r
22**/\r
23EFI_STATUS\r
24EhcCreateHelpQ (\r
1436aea4 25 IN PEI_USB2_HC_DEV *Ehc\r
4b1bf81c 26 )\r
27{\r
1436aea4
MK
28 USB_ENDPOINT Ep;\r
29 PEI_EHC_QH *Qh;\r
30 QH_HW *QhHw;\r
31 PEI_EHC_QTD *Qtd;\r
4b1bf81c 32\r
33 //\r
34 // Create an inactive Qtd to terminate the short packet read.\r
35 //\r
36 Qtd = EhcCreateQtd (Ehc, NULL, 0, QTD_PID_INPUT, 0, 64);\r
37\r
38 if (Qtd == NULL) {\r
39 return EFI_OUT_OF_RESOURCES;\r
40 }\r
41\r
1436aea4
MK
42 Qtd->QtdHw.Status = QTD_STAT_HALTED;\r
43 Ehc->ShortReadStop = Qtd;\r
4b1bf81c 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
1436aea4
MK
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
4b1bf81c 61\r
62 if (Qh == NULL) {\r
63 return EFI_OUT_OF_RESOURCES;\r
64 }\r
65\r
66 QhHw = &Qh->QhHw;\r
67 QhHw->HorizonLink = QH_LINK (QhHw, EHC_TYPE_QH, FALSE);\r
68 QhHw->Status = QTD_STAT_HALTED;\r
69 QhHw->ReclaimHead = 1;\r
70 Ehc->ReclaimHead = Qh;\r
71\r
72 //\r
73 // Create a dummy QH to act as the terminator for periodical schedule\r
74 //\r
1436aea4
MK
75 Ep.EpAddr = 2;\r
76 Ep.Type = EHC_INT_TRANSFER_SYNC;\r
4b1bf81c 77\r
1436aea4 78 Qh = EhcCreateQh (Ehc, &Ep);\r
4b1bf81c 79\r
80 if (Qh == NULL) {\r
81 return EFI_OUT_OF_RESOURCES;\r
82 }\r
83\r
84 Qh->QhHw.Status = QTD_STAT_HALTED;\r
85 Ehc->PeriodOne = Qh;\r
86\r
87 return EFI_SUCCESS;\r
88}\r
89\r
90/**\r
91 Initialize the schedule data structure such as frame list.\r
d1102dba 92\r
4b1bf81c 93 @param Ehc The EHCI device to init schedule data for.\r
94\r
95 @retval EFI_OUT_OF_RESOURCES Failed to allocate resource to init schedule data.\r
96 @retval EFI_SUCCESS The schedule data is initialized.\r
97\r
98**/\r
99EFI_STATUS\r
100EhcInitSched (\r
1436aea4 101 IN PEI_USB2_HC_DEV *Ehc\r
4b1bf81c 102 )\r
103{\r
2c656af0 104 VOID *Buf;\r
4b1bf81c 105 EFI_PHYSICAL_ADDRESS PhyAddr;\r
106 VOID *Map;\r
107 UINTN Index;\r
108 UINT32 *Desc;\r
109 EFI_STATUS Status;\r
2c656af0 110 EFI_PHYSICAL_ADDRESS PciAddr;\r
4b1bf81c 111\r
112 //\r
113 // First initialize the periodical schedule data:\r
114 // 1. Allocate and map the memory for the frame list\r
115 // 2. Create the help QTD/QH\r
116 // 3. Initialize the frame entries\r
117 // 4. Set the frame list register\r
118 //\r
119 //\r
120 // The Frame List ocupies 4K bytes,\r
121 // and must be aligned on 4-Kbyte boundaries.\r
122 //\r
2c656af0
SZ
123 Status = IoMmuAllocateBuffer (\r
124 Ehc->IoMmu,\r
4b1bf81c 125 1,\r
2c656af0
SZ
126 &Buf,\r
127 &PhyAddr,\r
128 &Map\r
4b1bf81c 129 );\r
130\r
a89b923e 131 if (EFI_ERROR (Status) || (Buf == NULL)) {\r
2c656af0
SZ
132 return EFI_OUT_OF_RESOURCES;\r
133 }\r
134\r
1436aea4
MK
135 Ehc->PeriodFrame = Buf;\r
136 Ehc->PeriodFrameMap = Map;\r
137 Ehc->High32bitAddr = EHC_HIGH_32BIT (PhyAddr);\r
4b1bf81c 138\r
139 //\r
140 // Init memory pool management then create the helper\r
141 // QTD/QH. If failed, previously allocated resources\r
142 // will be freed by EhcFreeSched\r
143 //\r
144 Ehc->MemPool = UsbHcInitMemPool (\r
145 Ehc,\r
146 EHC_BIT_IS_SET (Ehc->HcCapParams, HCCP_64BIT),\r
147 Ehc->High32bitAddr\r
148 );\r
149\r
150 if (Ehc->MemPool == NULL) {\r
151 return EFI_OUT_OF_RESOURCES;\r
152 }\r
153\r
154 Status = EhcCreateHelpQ (Ehc);\r
155\r
156 if (EFI_ERROR (Status)) {\r
157 return Status;\r
158 }\r
d1102dba 159\r
4b1bf81c 160 //\r
161 // Initialize the frame list entries then set the registers\r
162 //\r
1436aea4
MK
163 Desc = (UINT32 *)Ehc->PeriodFrame;\r
164 PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Ehc->PeriodOne, sizeof (PEI_EHC_QH));\r
4b1bf81c 165 for (Index = 0; Index < EHC_FRAME_LEN; Index++) {\r
2c656af0 166 Desc[Index] = QH_LINK (PciAddr, EHC_TYPE_QH, FALSE);\r
4b1bf81c 167 }\r
168\r
2c656af0 169 EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, EHC_LOW_32BIT (PhyAddr));\r
4b1bf81c 170\r
171 //\r
172 // Second initialize the asynchronous schedule:\r
173 // Only need to set the AsynListAddr register to\r
174 // the reclamation header\r
175 //\r
1436aea4 176 PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Ehc->ReclaimHead, sizeof (PEI_EHC_QH));\r
2c656af0 177 EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, EHC_LOW_32BIT (PciAddr));\r
4b1bf81c 178 return EFI_SUCCESS;\r
179}\r
180\r
181/**\r
182 Free the schedule data. It may be partially initialized.\r
d1102dba
LG
183\r
184 @param Ehc The EHCI device.\r
4b1bf81c 185\r
186**/\r
187VOID\r
188EhcFreeSched (\r
1436aea4 189 IN PEI_USB2_HC_DEV *Ehc\r
4b1bf81c 190 )\r
191{\r
192 EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, 0);\r
193 EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, 0);\r
194\r
195 if (Ehc->PeriodOne != NULL) {\r
2c656af0 196 UsbHcFreeMem (Ehc, Ehc->MemPool, Ehc->PeriodOne, sizeof (PEI_EHC_QH));\r
4b1bf81c 197 Ehc->PeriodOne = NULL;\r
198 }\r
199\r
200 if (Ehc->ReclaimHead != NULL) {\r
2c656af0 201 UsbHcFreeMem (Ehc, Ehc->MemPool, Ehc->ReclaimHead, sizeof (PEI_EHC_QH));\r
4b1bf81c 202 Ehc->ReclaimHead = NULL;\r
203 }\r
204\r
205 if (Ehc->ShortReadStop != NULL) {\r
2c656af0 206 UsbHcFreeMem (Ehc, Ehc->MemPool, Ehc->ShortReadStop, sizeof (PEI_EHC_QTD));\r
4b1bf81c 207 Ehc->ShortReadStop = NULL;\r
208 }\r
209\r
210 if (Ehc->MemPool != NULL) {\r
2c656af0 211 UsbHcFreeMemPool (Ehc, Ehc->MemPool);\r
4b1bf81c 212 Ehc->MemPool = NULL;\r
213 }\r
214\r
215 if (Ehc->PeriodFrame != NULL) {\r
2c656af0 216 IoMmuFreeBuffer (Ehc->IoMmu, 1, Ehc->PeriodFrame, Ehc->PeriodFrameMap);\r
4b1bf81c 217 Ehc->PeriodFrame = NULL;\r
218 }\r
219}\r
220\r
221/**\r
222 Link the queue head to the asynchronous schedule list.\r
223 UEFI only supports one CTRL/BULK transfer at a time\r
224 due to its interfaces. This simplifies the AsynList\r
225 management: A reclamation header is always linked to\r
226 the AsyncListAddr, the only active QH is appended to it.\r
d1102dba 227\r
4b1bf81c 228 @param Ehc The EHCI device.\r
229 @param Qh The queue head to link.\r
230\r
231**/\r
232VOID\r
233EhcLinkQhToAsync (\r
1436aea4
MK
234 IN PEI_USB2_HC_DEV *Ehc,\r
235 IN PEI_EHC_QH *Qh\r
4b1bf81c 236 )\r
237{\r
1436aea4 238 PEI_EHC_QH *Head;\r
4b1bf81c 239\r
240 //\r
241 // Append the queue head after the reclaim header, then\r
242 // fix the hardware visiable parts (EHCI R1.0 page 72).\r
243 // ReclaimHead is always linked to the EHCI's AsynListAddr.\r
244 //\r
1436aea4 245 Head = Ehc->ReclaimHead;\r
4b1bf81c 246\r
1436aea4
MK
247 Qh->NextQh = Head->NextQh;\r
248 Head->NextQh = Qh;\r
4b1bf81c 249\r
1436aea4
MK
250 Qh->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE);\r
251 Head->QhHw.HorizonLink = QH_LINK (Qh, EHC_TYPE_QH, FALSE);\r
4b1bf81c 252}\r
253\r
254/**\r
255 Unlink a queue head from the asynchronous schedule list.\r
256 Need to synchronize with hardware.\r
d1102dba 257\r
4b1bf81c 258 @param Ehc The EHCI device.\r
259 @param Qh The queue head to unlink.\r
260\r
261**/\r
262VOID\r
263EhcUnlinkQhFromAsync (\r
1436aea4
MK
264 IN PEI_USB2_HC_DEV *Ehc,\r
265 IN PEI_EHC_QH *Qh\r
4b1bf81c 266 )\r
267{\r
1436aea4 268 PEI_EHC_QH *Head;\r
4b1bf81c 269\r
270 ASSERT (Ehc->ReclaimHead->NextQh == Qh);\r
271\r
272 //\r
273 // Remove the QH from reclamation head, then update the hardware\r
274 // visiable part: Only need to loopback the ReclaimHead. The Qh\r
275 // is pointing to ReclaimHead (which is staill in the list).\r
276 //\r
1436aea4 277 Head = Ehc->ReclaimHead;\r
4b1bf81c 278\r
1436aea4
MK
279 Head->NextQh = Qh->NextQh;\r
280 Qh->NextQh = NULL;\r
4b1bf81c 281\r
1436aea4 282 Head->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE);\r
4b1bf81c 283\r
284 //\r
285 // Set and wait the door bell to synchronize with the hardware\r
286 //\r
7538d536 287 EhcSetAndWaitDoorBell (Ehc, EHC_GENERIC_TIMEOUT);\r
d1102dba 288\r
4b1bf81c 289 return;\r
290}\r
291\r
4b1bf81c 292/**\r
293 Check the URB's execution result and update the URB's\r
d1102dba 294 result accordingly.\r
4b1bf81c 295\r
296 @param Ehc The EHCI device.\r
297 @param Urb The URB to check result.\r
298\r
299 @retval TRUE URB transfer is finialized.\r
300 @retval FALSE URB transfer is not finialized.\r
301\r
302**/\r
303BOOLEAN\r
304EhcCheckUrbResult (\r
1436aea4
MK
305 IN PEI_USB2_HC_DEV *Ehc,\r
306 IN PEI_URB *Urb\r
4b1bf81c 307 )\r
308{\r
1436aea4
MK
309 EFI_LIST_ENTRY *Entry;\r
310 PEI_EHC_QTD *Qtd;\r
311 QTD_HW *QtdHw;\r
312 UINT8 State;\r
313 BOOLEAN Finished;\r
4b1bf81c 314\r
315 ASSERT ((Ehc != NULL) && (Urb != NULL) && (Urb->Qh != NULL));\r
316\r
1436aea4
MK
317 Finished = TRUE;\r
318 Urb->Completed = 0;\r
4b1bf81c 319\r
1436aea4 320 Urb->Result = EFI_USB_NOERROR;\r
4b1bf81c 321\r
322 if (EhcIsHalt (Ehc) || EhcIsSysError (Ehc)) {\r
323 Urb->Result |= EFI_USB_ERR_SYSTEM;\r
324 goto ON_EXIT;\r
325 }\r
326\r
30980945 327 BASE_LIST_FOR_EACH (Entry, &Urb->Qh->Qtds) {\r
4b1bf81c 328 Qtd = EFI_LIST_CONTAINER (Entry, PEI_EHC_QTD, QtdList);\r
329 QtdHw = &Qtd->QtdHw;\r
1436aea4 330 State = (UINT8)QtdHw->Status;\r
4b1bf81c 331\r
332 if (EHC_BIT_IS_SET (State, QTD_STAT_HALTED)) {\r
333 //\r
334 // EHCI will halt the queue head when met some error.\r
335 // If it is halted, the result of URB is finialized.\r
336 //\r
337 if ((State & QTD_STAT_ERR_MASK) == 0) {\r
338 Urb->Result |= EFI_USB_ERR_STALL;\r
339 }\r
340\r
341 if (EHC_BIT_IS_SET (State, QTD_STAT_BABBLE_ERR)) {\r
342 Urb->Result |= EFI_USB_ERR_BABBLE;\r
343 }\r
344\r
345 if (EHC_BIT_IS_SET (State, QTD_STAT_BUFF_ERR)) {\r
346 Urb->Result |= EFI_USB_ERR_BUFFER;\r
347 }\r
348\r
349 if (EHC_BIT_IS_SET (State, QTD_STAT_TRANS_ERR) && (QtdHw->ErrCnt == 0)) {\r
350 Urb->Result |= EFI_USB_ERR_TIMEOUT;\r
351 }\r
352\r
353 Finished = TRUE;\r
354 goto ON_EXIT;\r
4b1bf81c 355 } else if (EHC_BIT_IS_SET (State, QTD_STAT_ACTIVE)) {\r
356 //\r
357 // The QTD is still active, no need to check furthur.\r
358 //\r
359 Urb->Result |= EFI_USB_ERR_NOTEXECUTE;\r
d1102dba 360\r
4b1bf81c 361 Finished = FALSE;\r
362 goto ON_EXIT;\r
4b1bf81c 363 } else {\r
364 //\r
365 // This QTD is finished OK or met short packet read. Update the\r
366 // transfer length if it isn't a setup.\r
367 //\r
368 if (QtdHw->Pid != QTD_PID_SETUP) {\r
369 Urb->Completed += Qtd->DataLen - QtdHw->TotalBytes;\r
370 }\r
371\r
372 if ((QtdHw->TotalBytes != 0) && (QtdHw->Pid == QTD_PID_INPUT)) {\r
1436aea4 373 // EHC_DUMP_QH ((Urb->Qh, "Short packet read", FALSE));\r
4b1bf81c 374\r
375 //\r
376 // Short packet read condition. If it isn't a setup transfer,\r
377 // no need to check furthur: the queue head will halt at the\r
378 // ShortReadStop. If it is a setup transfer, need to check the\r
379 // Status Stage of the setup transfer to get the finial result\r
380 //\r
381 if (QtdHw->AltNext == QTD_LINK (Ehc->ShortReadStop, FALSE)) {\r
4b1bf81c 382 Finished = TRUE;\r
383 goto ON_EXIT;\r
384 }\r
385 }\r
386 }\r
387 }\r
388\r
389ON_EXIT:\r
390 //\r
391 // Return the data toggle set by EHCI hardware, bulk and interrupt\r
392 // transfer will use this to initialize the next transaction. For\r
393 // Control transfer, it always start a new data toggle sequence for\r
394 // new transfer.\r
395 //\r
396 // NOTICE: don't move DT update before the loop, otherwise there is\r
397 // a race condition that DT is wrong.\r
398 //\r
1436aea4 399 Urb->DataToggle = (UINT8)Urb->Qh->QhHw.DataToggle;\r
4b1bf81c 400\r
401 return Finished;\r
402}\r
403\r
404/**\r
405 Execute the transfer by polling the URB. This is a synchronous operation.\r
d1102dba 406\r
4b1bf81c 407 @param Ehc The EHCI device.\r
408 @param Urb The URB to execute.\r
409 @param TimeOut The time to wait before abort, in millisecond.\r
410\r
411 @retval EFI_DEVICE_ERROR The transfer failed due to transfer error.\r
412 @retval EFI_TIMEOUT The transfer failed due to time out.\r
413 @retval EFI_SUCCESS The transfer finished OK.\r
414\r
415**/\r
416EFI_STATUS\r
417EhcExecTransfer (\r
1436aea4
MK
418 IN PEI_USB2_HC_DEV *Ehc,\r
419 IN PEI_URB *Urb,\r
420 IN UINTN TimeOut\r
4b1bf81c 421 )\r
422{\r
1436aea4
MK
423 EFI_STATUS Status;\r
424 UINTN Index;\r
425 UINTN Loop;\r
426 BOOLEAN Finished;\r
427 BOOLEAN InfiniteLoop;\r
428\r
429 Status = EFI_SUCCESS;\r
430 Loop = TimeOut * EHC_1_MILLISECOND;\r
ca243131
FT
431 Finished = FALSE;\r
432 InfiniteLoop = FALSE;\r
4b1bf81c 433\r
ca243131
FT
434 //\r
435 // If Timeout is 0, then the caller must wait for the function to be completed\r
436 // until EFI_SUCCESS or EFI_DEVICE_ERROR is returned.\r
437 //\r
438 if (TimeOut == 0) {\r
439 InfiniteLoop = TRUE;\r
440 }\r
441\r
442 for (Index = 0; InfiniteLoop || (Index < Loop); Index++) {\r
4b1bf81c 443 Finished = EhcCheckUrbResult (Ehc, Urb);\r
444\r
445 if (Finished) {\r
446 break;\r
447 }\r
448\r
ca243131 449 MicroSecondDelay (EHC_1_MICROSECOND);\r
4b1bf81c 450 }\r
451\r
452 if (!Finished) {\r
453 Status = EFI_TIMEOUT;\r
454 } else if (Urb->Result != EFI_USB_NOERROR) {\r
455 Status = EFI_DEVICE_ERROR;\r
456 }\r
457\r
458 return Status;\r
459}\r