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 - 2013, 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
EhcCreateHelpQ(IN PEI_USB2_HC_DEV * Ehc)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
EhcInitSched(IN PEI_USB2_HC_DEV * Ehc)106 EhcInitSched (
107 IN PEI_USB2_HC_DEV *Ehc
108 )
109 {
110 EFI_PHYSICAL_ADDRESS PhyAddr;
111 VOID *Map;
112 UINTN Index;
113 UINT32 *Desc;
114 EFI_STATUS Status;
115
116 //
117 // First initialize the periodical schedule data:
118 // 1. Allocate and map the memory for the frame list
119 // 2. Create the help QTD/QH
120 // 3. Initialize the frame entries
121 // 4. Set the frame list register
122 //
123 //
124 // The Frame List ocupies 4K bytes,
125 // and must be aligned on 4-Kbyte boundaries.
126 //
127 Status = PeiServicesAllocatePages (
128 EfiBootServicesCode,
129 1,
130 &PhyAddr
131 );
132
133 Map = NULL;
134 Ehc->PeriodFrameHost = (VOID *)(UINTN)PhyAddr;
135 Ehc->PeriodFrame = (VOID *)(UINTN)PhyAddr;
136 Ehc->PeriodFrameMap = Map;
137 Ehc->High32bitAddr = EHC_HIGH_32BIT (PhyAddr);
138
139 //
140 // Init memory pool management then create the helper
141 // QTD/QH. If failed, previously allocated resources
142 // will be freed by EhcFreeSched
143 //
144 Ehc->MemPool = UsbHcInitMemPool (
145 Ehc,
146 EHC_BIT_IS_SET (Ehc->HcCapParams, HCCP_64BIT),
147 Ehc->High32bitAddr
148 );
149
150 if (Ehc->MemPool == NULL) {
151 return EFI_OUT_OF_RESOURCES;
152 }
153
154 Status = EhcCreateHelpQ (Ehc);
155
156 if (EFI_ERROR (Status)) {
157 return Status;
158 }
159
160 //
161 // Initialize the frame list entries then set the registers
162 //
163 Desc = (UINT32 *) Ehc->PeriodFrame;
164
165 for (Index = 0; Index < EHC_FRAME_LEN; Index++) {
166 Desc[Index] = QH_LINK (Ehc->PeriodOne, EHC_TYPE_QH, FALSE);
167 }
168
169 EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, EHC_LOW_32BIT (Ehc->PeriodFrame));
170
171 //
172 // Second initialize the asynchronous schedule:
173 // Only need to set the AsynListAddr register to
174 // the reclamation header
175 //
176 EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, EHC_LOW_32BIT (Ehc->ReclaimHead));
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
EhcFreeSched(IN PEI_USB2_HC_DEV * Ehc)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->MemPool, Ehc->PeriodOne, sizeof (PEI_EHC_QH));
196 Ehc->PeriodOne = NULL;
197 }
198
199 if (Ehc->ReclaimHead != NULL) {
200 UsbHcFreeMem (Ehc->MemPool, Ehc->ReclaimHead, sizeof (PEI_EHC_QH));
201 Ehc->ReclaimHead = NULL;
202 }
203
204 if (Ehc->ShortReadStop != NULL) {
205 UsbHcFreeMem (Ehc->MemPool, Ehc->ShortReadStop, sizeof (PEI_EHC_QTD));
206 Ehc->ShortReadStop = NULL;
207 }
208
209 if (Ehc->MemPool != NULL) {
210 UsbHcFreeMemPool (Ehc->MemPool);
211 Ehc->MemPool = NULL;
212 }
213
214 if (Ehc->PeriodFrame != NULL) {
215 Ehc->PeriodFrame = NULL;
216 }
217 }
218
219 /**
220 Link the queue head to the asynchronous schedule list.
221 UEFI only supports one CTRL/BULK transfer at a time
222 due to its interfaces. This simplifies the AsynList
223 management: A reclamation header is always linked to
224 the AsyncListAddr, the only active QH is appended to it.
225
226 @param Ehc The EHCI device.
227 @param Qh The queue head to link.
228
229 **/
230 VOID
EhcLinkQhToAsync(IN PEI_USB2_HC_DEV * Ehc,IN PEI_EHC_QH * Qh)231 EhcLinkQhToAsync (
232 IN PEI_USB2_HC_DEV *Ehc,
233 IN PEI_EHC_QH *Qh
234 )
235 {
236 PEI_EHC_QH *Head;
237
238 //
239 // Append the queue head after the reclaim header, then
240 // fix the hardware visiable parts (EHCI R1.0 page 72).
241 // ReclaimHead is always linked to the EHCI's AsynListAddr.
242 //
243 Head = Ehc->ReclaimHead;
244
245 Qh->NextQh = Head->NextQh;
246 Head->NextQh = Qh;
247
248 Qh->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE);;
249 Head->QhHw.HorizonLink = QH_LINK (Qh, EHC_TYPE_QH, FALSE);
250 }
251
252 /**
253 Unlink a queue head from the asynchronous schedule list.
254 Need to synchronize with hardware.
255
256 @param Ehc The EHCI device.
257 @param Qh The queue head to unlink.
258
259 **/
260 VOID
EhcUnlinkQhFromAsync(IN PEI_USB2_HC_DEV * Ehc,IN PEI_EHC_QH * Qh)261 EhcUnlinkQhFromAsync (
262 IN PEI_USB2_HC_DEV *Ehc,
263 IN PEI_EHC_QH *Qh
264 )
265 {
266 PEI_EHC_QH *Head;
267
268 ASSERT (Ehc->ReclaimHead->NextQh == Qh);
269
270 //
271 // Remove the QH from reclamation head, then update the hardware
272 // visiable part: Only need to loopback the ReclaimHead. The Qh
273 // is pointing to ReclaimHead (which is staill in the list).
274 //
275 Head = Ehc->ReclaimHead;
276
277 Head->NextQh = Qh->NextQh;
278 Qh->NextQh = NULL;
279
280 Head->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE);
281
282 //
283 // Set and wait the door bell to synchronize with the hardware
284 //
285 EhcSetAndWaitDoorBell (Ehc, EHC_GENERIC_TIMEOUT);
286
287 return;
288 }
289
290 /**
291 Check the URB's execution result and update the URB's
292 result accordingly.
293
294 @param Ehc The EHCI device.
295 @param Urb The URB to check result.
296
297 @retval TRUE URB transfer is finialized.
298 @retval FALSE URB transfer is not finialized.
299
300 **/
301 BOOLEAN
EhcCheckUrbResult(IN PEI_USB2_HC_DEV * Ehc,IN PEI_URB * Urb)302 EhcCheckUrbResult (
303 IN PEI_USB2_HC_DEV *Ehc,
304 IN PEI_URB *Urb
305 )
306 {
307 EFI_LIST_ENTRY *Entry;
308 PEI_EHC_QTD *Qtd;
309 QTD_HW *QtdHw;
310 UINT8 State;
311 BOOLEAN Finished;
312
313 ASSERT ((Ehc != NULL) && (Urb != NULL) && (Urb->Qh != NULL));
314
315 Finished = TRUE;
316 Urb->Completed = 0;
317
318 Urb->Result = EFI_USB_NOERROR;
319
320 if (EhcIsHalt (Ehc) || EhcIsSysError (Ehc)) {
321 Urb->Result |= EFI_USB_ERR_SYSTEM;
322 goto ON_EXIT;
323 }
324
325 EFI_LIST_FOR_EACH (Entry, &Urb->Qh->Qtds) {
326 Qtd = EFI_LIST_CONTAINER (Entry, PEI_EHC_QTD, QtdList);
327 QtdHw = &Qtd->QtdHw;
328 State = (UINT8) QtdHw->Status;
329
330 if (EHC_BIT_IS_SET (State, QTD_STAT_HALTED)) {
331 //
332 // EHCI will halt the queue head when met some error.
333 // If it is halted, the result of URB is finialized.
334 //
335 if ((State & QTD_STAT_ERR_MASK) == 0) {
336 Urb->Result |= EFI_USB_ERR_STALL;
337 }
338
339 if (EHC_BIT_IS_SET (State, QTD_STAT_BABBLE_ERR)) {
340 Urb->Result |= EFI_USB_ERR_BABBLE;
341 }
342
343 if (EHC_BIT_IS_SET (State, QTD_STAT_BUFF_ERR)) {
344 Urb->Result |= EFI_USB_ERR_BUFFER;
345 }
346
347 if (EHC_BIT_IS_SET (State, QTD_STAT_TRANS_ERR) && (QtdHw->ErrCnt == 0)) {
348 Urb->Result |= EFI_USB_ERR_TIMEOUT;
349 }
350
351 Finished = TRUE;
352 goto ON_EXIT;
353
354 } else if (EHC_BIT_IS_SET (State, QTD_STAT_ACTIVE)) {
355 //
356 // The QTD is still active, no need to check furthur.
357 //
358 Urb->Result |= EFI_USB_ERR_NOTEXECUTE;
359
360 Finished = FALSE;
361 goto ON_EXIT;
362
363 } else {
364 //
365 // This QTD is finished OK or met short packet read. Update the
366 // transfer length if it isn't a setup.
367 //
368 if (QtdHw->Pid != QTD_PID_SETUP) {
369 Urb->Completed += Qtd->DataLen - QtdHw->TotalBytes;
370 }
371
372 if ((QtdHw->TotalBytes != 0) && (QtdHw->Pid == QTD_PID_INPUT)) {
373 //EHC_DUMP_QH ((Urb->Qh, "Short packet read", FALSE));
374
375 //
376 // Short packet read condition. If it isn't a setup transfer,
377 // no need to check furthur: the queue head will halt at the
378 // ShortReadStop. If it is a setup transfer, need to check the
379 // Status Stage of the setup transfer to get the finial result
380 //
381 if (QtdHw->AltNext == QTD_LINK (Ehc->ShortReadStop, FALSE)) {
382
383 Finished = TRUE;
384 goto ON_EXIT;
385 }
386 }
387 }
388 }
389
390 ON_EXIT:
391 //
392 // Return the data toggle set by EHCI hardware, bulk and interrupt
393 // transfer will use this to initialize the next transaction. For
394 // Control transfer, it always start a new data toggle sequence for
395 // new transfer.
396 //
397 // NOTICE: don't move DT update before the loop, otherwise there is
398 // a race condition that DT is wrong.
399 //
400 Urb->DataToggle = (UINT8) Urb->Qh->QhHw.DataToggle;
401
402 return Finished;
403 }
404
405 /**
406 Execute the transfer by polling the URB. This is a synchronous operation.
407
408 @param Ehc The EHCI device.
409 @param Urb The URB to execute.
410 @param TimeOut The time to wait before abort, in millisecond.
411
412 @retval EFI_DEVICE_ERROR The transfer failed due to transfer error.
413 @retval EFI_TIMEOUT The transfer failed due to time out.
414 @retval EFI_SUCCESS The transfer finished OK.
415
416 **/
417 EFI_STATUS
EhcExecTransfer(IN PEI_USB2_HC_DEV * Ehc,IN PEI_URB * Urb,IN UINTN TimeOut)418 EhcExecTransfer (
419 IN PEI_USB2_HC_DEV *Ehc,
420 IN PEI_URB *Urb,
421 IN UINTN TimeOut
422 )
423 {
424 EFI_STATUS Status;
425 UINTN Index;
426 UINTN Loop;
427 BOOLEAN Finished;
428 BOOLEAN InfiniteLoop;
429
430 Status = EFI_SUCCESS;
431 Loop = TimeOut * EHC_1_MILLISECOND;
432 Finished = FALSE;
433 InfiniteLoop = FALSE;
434
435 //
436 // If Timeout is 0, then the caller must wait for the function to be completed
437 // until EFI_SUCCESS or EFI_DEVICE_ERROR is returned.
438 //
439 if (TimeOut == 0) {
440 InfiniteLoop = TRUE;
441 }
442
443 for (Index = 0; InfiniteLoop || (Index < Loop); Index++) {
444 Finished = EhcCheckUrbResult (Ehc, Urb);
445
446 if (Finished) {
447 break;
448 }
449
450 MicroSecondDelay (EHC_1_MICROSECOND);
451 }
452
453 if (!Finished) {
454 Status = EFI_TIMEOUT;
455 } else if (Urb->Result != EFI_USB_NOERROR) {
456 Status = EFI_DEVICE_ERROR;
457 }
458
459 return Status;
460 }
461
462