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