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 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 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 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 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 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 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 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