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