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