]> git.proxmox.com Git - mirror_edk2.git/blame - MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c
MdeModulePkg/EhciPei: Use BaseLib linked list iteration macros
[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
25 IN PEI_USB2_HC_DEV *Ehc\r
26 )\r
27{\r
28 USB_ENDPOINT Ep;\r
29 PEI_EHC_QH *Qh;\r
30 QH_HW *QhHw;\r
31 PEI_EHC_QTD *Qtd;\r
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
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
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
75 Ep.EpAddr = 2;\r
76 Ep.Type = EHC_INT_TRANSFER_SYNC;\r
77\r
78 Qh = EhcCreateQh (Ehc, &Ep);\r
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
101 IN PEI_USB2_HC_DEV *Ehc\r
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
135 Ehc->PeriodFrame = Buf;\r
4b1bf81c 136 Ehc->PeriodFrameMap = Map;\r
137 Ehc->High32bitAddr = EHC_HIGH_32BIT (PhyAddr);\r
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
163 Desc = (UINT32 *) Ehc->PeriodFrame;\r
2c656af0 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
2c656af0
SZ
176 PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Ehc->ReclaimHead, sizeof (PEI_EHC_QH));\r
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
189 IN PEI_USB2_HC_DEV *Ehc\r
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
234 IN PEI_USB2_HC_DEV *Ehc,\r
235 IN PEI_EHC_QH *Qh\r
236 )\r
237{\r
238 PEI_EHC_QH *Head;\r
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
245 Head = Ehc->ReclaimHead;\r
246\r
247 Qh->NextQh = Head->NextQh;\r
248 Head->NextQh = Qh;\r
249\r
250 Qh->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE);;\r
251 Head->QhHw.HorizonLink = QH_LINK (Qh, EHC_TYPE_QH, FALSE);\r
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
264 IN PEI_USB2_HC_DEV *Ehc,\r
265 IN PEI_EHC_QH *Qh\r
266 )\r
267{\r
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
277 Head = Ehc->ReclaimHead;\r
278\r
279 Head->NextQh = Qh->NextQh;\r
280 Qh->NextQh = NULL;\r
281\r
282 Head->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE);\r
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
305 IN PEI_USB2_HC_DEV *Ehc,\r
306 IN PEI_URB *Urb\r
307 )\r
308{\r
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
314\r
315 ASSERT ((Ehc != NULL) && (Urb != NULL) && (Urb->Qh != NULL));\r
316\r
317 Finished = TRUE;\r
318 Urb->Completed = 0;\r
319\r
320 Urb->Result = EFI_USB_NOERROR;\r
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
330 State = (UINT8) QtdHw->Status;\r
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
d1102dba 355\r
4b1bf81c 356 } else if (EHC_BIT_IS_SET (State, QTD_STAT_ACTIVE)) {\r
357 //\r
358 // The QTD is still active, no need to check furthur.\r
359 //\r
360 Urb->Result |= EFI_USB_ERR_NOTEXECUTE;\r
d1102dba 361\r
4b1bf81c 362 Finished = FALSE;\r
363 goto ON_EXIT;\r
364\r
365 } else {\r
366 //\r
367 // This QTD is finished OK or met short packet read. Update the\r
368 // transfer length if it isn't a setup.\r
369 //\r
370 if (QtdHw->Pid != QTD_PID_SETUP) {\r
371 Urb->Completed += Qtd->DataLen - QtdHw->TotalBytes;\r
372 }\r
373\r
374 if ((QtdHw->TotalBytes != 0) && (QtdHw->Pid == QTD_PID_INPUT)) {\r
375 //EHC_DUMP_QH ((Urb->Qh, "Short packet read", FALSE));\r
376\r
377 //\r
378 // Short packet read condition. If it isn't a setup transfer,\r
379 // no need to check furthur: the queue head will halt at the\r
380 // ShortReadStop. If it is a setup transfer, need to check the\r
381 // Status Stage of the setup transfer to get the finial result\r
382 //\r
383 if (QtdHw->AltNext == QTD_LINK (Ehc->ShortReadStop, FALSE)) {\r
d1102dba 384\r
4b1bf81c 385 Finished = TRUE;\r
386 goto ON_EXIT;\r
387 }\r
388 }\r
389 }\r
390 }\r
391\r
392ON_EXIT:\r
393 //\r
394 // Return the data toggle set by EHCI hardware, bulk and interrupt\r
395 // transfer will use this to initialize the next transaction. For\r
396 // Control transfer, it always start a new data toggle sequence for\r
397 // new transfer.\r
398 //\r
399 // NOTICE: don't move DT update before the loop, otherwise there is\r
400 // a race condition that DT is wrong.\r
401 //\r
402 Urb->DataToggle = (UINT8) Urb->Qh->QhHw.DataToggle;\r
403\r
404 return Finished;\r
405}\r
406\r
407/**\r
408 Execute the transfer by polling the URB. This is a synchronous operation.\r
d1102dba 409\r
4b1bf81c 410 @param Ehc The EHCI device.\r
411 @param Urb The URB to execute.\r
412 @param TimeOut The time to wait before abort, in millisecond.\r
413\r
414 @retval EFI_DEVICE_ERROR The transfer failed due to transfer error.\r
415 @retval EFI_TIMEOUT The transfer failed due to time out.\r
416 @retval EFI_SUCCESS The transfer finished OK.\r
417\r
418**/\r
419EFI_STATUS\r
420EhcExecTransfer (\r
421 IN PEI_USB2_HC_DEV *Ehc,\r
422 IN PEI_URB *Urb,\r
423 IN UINTN TimeOut\r
424 )\r
425{\r
426 EFI_STATUS Status;\r
427 UINTN Index;\r
428 UINTN Loop;\r
429 BOOLEAN Finished;\r
ca243131 430 BOOLEAN InfiniteLoop;\r
4b1bf81c 431\r
432 Status = EFI_SUCCESS;\r
ca243131
FT
433 Loop = TimeOut * EHC_1_MILLISECOND;\r
434 Finished = FALSE;\r
435 InfiniteLoop = FALSE;\r
4b1bf81c 436\r
ca243131
FT
437 //\r
438 // If Timeout is 0, then the caller must wait for the function to be completed\r
439 // until EFI_SUCCESS or EFI_DEVICE_ERROR is returned.\r
440 //\r
441 if (TimeOut == 0) {\r
442 InfiniteLoop = TRUE;\r
443 }\r
444\r
445 for (Index = 0; InfiniteLoop || (Index < Loop); Index++) {\r
4b1bf81c 446 Finished = EhcCheckUrbResult (Ehc, Urb);\r
447\r
448 if (Finished) {\r
449 break;\r
450 }\r
451\r
ca243131 452 MicroSecondDelay (EHC_1_MICROSECOND);\r
4b1bf81c 453 }\r
454\r
455 if (!Finished) {\r
456 Status = EFI_TIMEOUT;\r
457 } else if (Urb->Result != EFI_USB_NOERROR) {\r
458 Status = EFI_DEVICE_ERROR;\r
459 }\r
460\r
461 return Status;\r
462}\r
463\r