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