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