| /** @file | |
| PEIM to produce gPeiUsb2HostControllerPpiGuid based on gPeiUsbControllerPpiGuid | |
| which is used to enable recovery function from USB Drivers. | |
| Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include "EhcPeim.h" | |
| // | |
| // Two arrays used to translate the EHCI port state (change) | |
| // to the UEFI protocol's port state (change). | |
| // | |
| USB_PORT_STATE_MAP mUsbPortStateMap[] = { | |
| { PORTSC_CONN, USB_PORT_STAT_CONNECTION }, | |
| { PORTSC_ENABLED, USB_PORT_STAT_ENABLE }, | |
| { PORTSC_SUSPEND, USB_PORT_STAT_SUSPEND }, | |
| { PORTSC_OVERCUR, USB_PORT_STAT_OVERCURRENT }, | |
| { PORTSC_RESET, USB_PORT_STAT_RESET }, | |
| { PORTSC_POWER, USB_PORT_STAT_POWER }, | |
| { PORTSC_OWNER, USB_PORT_STAT_OWNER } | |
| }; | |
| USB_PORT_STATE_MAP mUsbPortChangeMap[] = { | |
| { PORTSC_CONN_CHANGE, USB_PORT_STAT_C_CONNECTION }, | |
| { PORTSC_ENABLE_CHANGE, USB_PORT_STAT_C_ENABLE }, | |
| { PORTSC_OVERCUR_CHANGE, USB_PORT_STAT_C_OVERCURRENT } | |
| }; | |
| /** | |
| Read Ehc Operation register. | |
| @param Ehc The EHCI device. | |
| @param Offset The operation register offset. | |
| @retval the register content read. | |
| **/ | |
| UINT32 | |
| EhcReadOpReg ( | |
| IN PEI_USB2_HC_DEV *Ehc, | |
| IN UINT32 Offset | |
| ) | |
| { | |
| UINT32 Data; | |
| ASSERT (Ehc->CapLen != 0); | |
| Data = MmioRead32 (Ehc->UsbHostControllerBaseAddress + Ehc->CapLen + Offset); | |
| return Data; | |
| } | |
| /** | |
| Write the data to the EHCI operation register. | |
| @param Ehc The EHCI device. | |
| @param Offset EHCI operation register offset. | |
| @param Data The data to write. | |
| **/ | |
| VOID | |
| EhcWriteOpReg ( | |
| IN PEI_USB2_HC_DEV *Ehc, | |
| IN UINT32 Offset, | |
| IN UINT32 Data | |
| ) | |
| { | |
| ASSERT (Ehc->CapLen != 0); | |
| MmioWrite32 (Ehc->UsbHostControllerBaseAddress + Ehc->CapLen + Offset, Data); | |
| } | |
| /** | |
| Set one bit of the operational register while keeping other bits. | |
| @param Ehc The EHCI device. | |
| @param Offset The offset of the operational register. | |
| @param Bit The bit mask of the register to set. | |
| **/ | |
| VOID | |
| EhcSetOpRegBit ( | |
| IN PEI_USB2_HC_DEV *Ehc, | |
| IN UINT32 Offset, | |
| IN UINT32 Bit | |
| ) | |
| { | |
| UINT32 Data; | |
| Data = EhcReadOpReg (Ehc, Offset); | |
| Data |= Bit; | |
| EhcWriteOpReg (Ehc, Offset, Data); | |
| } | |
| /** | |
| Clear one bit of the operational register while keeping other bits. | |
| @param Ehc The EHCI device. | |
| @param Offset The offset of the operational register. | |
| @param Bit The bit mask of the register to clear. | |
| **/ | |
| VOID | |
| EhcClearOpRegBit ( | |
| IN PEI_USB2_HC_DEV *Ehc, | |
| IN UINT32 Offset, | |
| IN UINT32 Bit | |
| ) | |
| { | |
| UINT32 Data; | |
| Data = EhcReadOpReg (Ehc, Offset); | |
| Data &= ~Bit; | |
| EhcWriteOpReg (Ehc, Offset, Data); | |
| } | |
| /** | |
| Wait the operation register's bit as specified by Bit | |
| to become set (or clear). | |
| @param Ehc The EHCI device. | |
| @param Offset The offset of the operational register. | |
| @param Bit The bit mask of the register to wait for. | |
| @param WaitToSet Wait the bit to set or clear. | |
| @param Timeout The time to wait before abort (in millisecond). | |
| @retval EFI_SUCCESS The bit successfully changed by host controller. | |
| @retval EFI_TIMEOUT The time out occurred. | |
| **/ | |
| EFI_STATUS | |
| EhcWaitOpRegBit ( | |
| IN PEI_USB2_HC_DEV *Ehc, | |
| IN UINT32 Offset, | |
| IN UINT32 Bit, | |
| IN BOOLEAN WaitToSet, | |
| IN UINT32 Timeout | |
| ) | |
| { | |
| UINT32 Index; | |
| for (Index = 0; Index < Timeout / EHC_SYNC_POLL_INTERVAL + 1; Index++) { | |
| if (EHC_REG_BIT_IS_SET (Ehc, Offset, Bit) == WaitToSet) { | |
| return EFI_SUCCESS; | |
| } | |
| MicroSecondDelay (EHC_SYNC_POLL_INTERVAL); | |
| } | |
| return EFI_TIMEOUT; | |
| } | |
| /** | |
| Read EHCI capability register. | |
| @param Ehc The EHCI device. | |
| @param Offset Capability register address. | |
| @retval the register content read. | |
| **/ | |
| UINT32 | |
| EhcReadCapRegister ( | |
| IN PEI_USB2_HC_DEV *Ehc, | |
| IN UINT32 Offset | |
| ) | |
| { | |
| UINT32 Data; | |
| Data = MmioRead32 (Ehc->UsbHostControllerBaseAddress + Offset); | |
| return Data; | |
| } | |
| /** | |
| Set door bell and wait it to be ACKed by host controller. | |
| This function is used to synchronize with the hardware. | |
| @param Ehc The EHCI device. | |
| @param Timeout The time to wait before abort (in millisecond, ms). | |
| @retval EFI_TIMEOUT Time out happened while waiting door bell to set. | |
| @retval EFI_SUCCESS Synchronized with the hardware. | |
| **/ | |
| EFI_STATUS | |
| EhcSetAndWaitDoorBell ( | |
| IN PEI_USB2_HC_DEV *Ehc, | |
| IN UINT32 Timeout | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINT32 Data; | |
| EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_IAAD); | |
| Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_IAA, TRUE, Timeout); | |
| // | |
| // ACK the IAA bit in USBSTS register. Make sure other | |
| // interrupt bits are not ACKed. These bits are WC (Write Clean). | |
| // | |
| Data = EhcReadOpReg (Ehc, EHC_USBSTS_OFFSET); | |
| Data &= ~USBSTS_INTACK_MASK; | |
| Data |= USBSTS_IAA; | |
| EhcWriteOpReg (Ehc, EHC_USBSTS_OFFSET, Data); | |
| return Status; | |
| } | |
| /** | |
| Clear all the interrutp status bits, these bits | |
| are Write-Clean. | |
| @param Ehc The EHCI device. | |
| **/ | |
| VOID | |
| EhcAckAllInterrupt ( | |
| IN PEI_USB2_HC_DEV *Ehc | |
| ) | |
| { | |
| EhcWriteOpReg (Ehc, EHC_USBSTS_OFFSET, USBSTS_INTACK_MASK); | |
| } | |
| /** | |
| Enable the periodic schedule then wait EHC to | |
| actually enable it. | |
| @param Ehc The EHCI device. | |
| @param Timeout The time to wait before abort (in millisecond, ms). | |
| @retval EFI_TIMEOUT Time out happened while enabling periodic schedule. | |
| @retval EFI_SUCCESS The periodical schedule is enabled. | |
| **/ | |
| EFI_STATUS | |
| EhcEnablePeriodSchd ( | |
| IN PEI_USB2_HC_DEV *Ehc, | |
| IN UINT32 Timeout | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_ENABLE_PERIOD); | |
| Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_PERIOD_ENABLED, TRUE, Timeout); | |
| return Status; | |
| } | |
| /** | |
| Enable asynchrounous schedule. | |
| @param Ehc The EHCI device. | |
| @param Timeout Time to wait before abort. | |
| @retval EFI_SUCCESS The EHCI asynchronous schedule is enabled. | |
| @retval Others Failed to enable the asynchronous scheudle. | |
| **/ | |
| EFI_STATUS | |
| EhcEnableAsyncSchd ( | |
| IN PEI_USB2_HC_DEV *Ehc, | |
| IN UINT32 Timeout | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_ENABLE_ASYNC); | |
| Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_ASYNC_ENABLED, TRUE, Timeout); | |
| return Status; | |
| } | |
| /** | |
| Check whether Ehc is halted. | |
| @param Ehc The EHCI device. | |
| @retval TRUE The controller is halted. | |
| @retval FALSE The controller isn't halted. | |
| **/ | |
| BOOLEAN | |
| EhcIsHalt ( | |
| IN PEI_USB2_HC_DEV *Ehc | |
| ) | |
| { | |
| return EHC_REG_BIT_IS_SET (Ehc, EHC_USBSTS_OFFSET, USBSTS_HALT); | |
| } | |
| /** | |
| Check whether system error occurred. | |
| @param Ehc The EHCI device. | |
| @retval TRUE System error happened. | |
| @retval FALSE No system error. | |
| **/ | |
| BOOLEAN | |
| EhcIsSysError ( | |
| IN PEI_USB2_HC_DEV *Ehc | |
| ) | |
| { | |
| return EHC_REG_BIT_IS_SET (Ehc, EHC_USBSTS_OFFSET, USBSTS_SYS_ERROR); | |
| } | |
| /** | |
| Reset the host controller. | |
| @param Ehc The EHCI device. | |
| @param Timeout Time to wait before abort (in millisecond, ms). | |
| @retval EFI_TIMEOUT The transfer failed due to time out. | |
| @retval Others Failed to reset the host. | |
| **/ | |
| EFI_STATUS | |
| EhcResetHC ( | |
| IN PEI_USB2_HC_DEV *Ehc, | |
| IN UINT32 Timeout | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| // | |
| // Host can only be reset when it is halt. If not so, halt it | |
| // | |
| if (!EHC_REG_BIT_IS_SET (Ehc, EHC_USBSTS_OFFSET, USBSTS_HALT)) { | |
| Status = EhcHaltHC (Ehc, Timeout); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| } | |
| EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_RESET); | |
| Status = EhcWaitOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_RESET, FALSE, Timeout); | |
| return Status; | |
| } | |
| /** | |
| Halt the host controller. | |
| @param Ehc The EHCI device. | |
| @param Timeout Time to wait before abort. | |
| @retval EFI_TIMEOUT Failed to halt the controller before Timeout. | |
| @retval EFI_SUCCESS The EHCI is halt. | |
| **/ | |
| EFI_STATUS | |
| EhcHaltHC ( | |
| IN PEI_USB2_HC_DEV *Ehc, | |
| IN UINT32 Timeout | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EhcClearOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_RUN); | |
| Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_HALT, TRUE, Timeout); | |
| return Status; | |
| } | |
| /** | |
| Set the EHCI to run. | |
| @param Ehc The EHCI device. | |
| @param Timeout Time to wait before abort. | |
| @retval EFI_SUCCESS The EHCI is running. | |
| @retval Others Failed to set the EHCI to run. | |
| **/ | |
| EFI_STATUS | |
| EhcRunHC ( | |
| IN PEI_USB2_HC_DEV *Ehc, | |
| IN UINT32 Timeout | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_RUN); | |
| Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_HALT, FALSE, Timeout); | |
| return Status; | |
| } | |
| /** | |
| Power On All EHCI Ports. | |
| @param Ehc The EHCI device. | |
| **/ | |
| VOID | |
| EhcPowerOnAllPorts ( | |
| IN PEI_USB2_HC_DEV *Ehc | |
| ) | |
| { | |
| UINT8 PortNumber; | |
| UINT8 Index; | |
| UINT32 RegVal; | |
| PortNumber = (UINT8)(Ehc->HcStructParams & HCSP_NPORTS); | |
| for (Index = 0; Index < PortNumber; Index++) { | |
| // | |
| // Do not clear port status bits on initialization. Otherwise devices will | |
| // not enumerate properly at startup. | |
| // | |
| RegVal = EhcReadOpReg (Ehc, EHC_PORT_STAT_OFFSET + 4 * Index); | |
| RegVal &= ~PORTSC_CHANGE_MASK; | |
| RegVal |= PORTSC_POWER; | |
| EhcWriteOpReg (Ehc, EHC_PORT_STAT_OFFSET + 4 * Index, RegVal); | |
| } | |
| } | |
| /** | |
| Initialize the HC hardware. | |
| EHCI spec lists the five things to do to initialize the hardware. | |
| 1. Program CTRLDSSEGMENT. | |
| 2. Set USBINTR to enable interrupts. | |
| 3. Set periodic list base. | |
| 4. Set USBCMD, interrupt threshold, frame list size etc. | |
| 5. Write 1 to CONFIGFLAG to route all ports to EHCI. | |
| @param Ehc The EHCI device. | |
| @retval EFI_SUCCESS The EHCI has come out of halt state. | |
| @retval EFI_TIMEOUT Time out happened. | |
| **/ | |
| EFI_STATUS | |
| EhcInitHC ( | |
| IN PEI_USB2_HC_DEV *Ehc | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_PHYSICAL_ADDRESS TempPtr; | |
| UINTN PageNumber; | |
| ASSERT (EhcIsHalt (Ehc)); | |
| // | |
| // Allocate the periodic frame and associated memeory | |
| // management facilities if not already done. | |
| // | |
| if (Ehc->PeriodFrame != NULL) { | |
| EhcFreeSched (Ehc); | |
| } | |
| PageNumber = sizeof (PEI_URB)/PAGESIZE +1; | |
| Status = PeiServicesAllocatePages ( | |
| EfiBootServicesCode, | |
| PageNumber, | |
| &TempPtr | |
| ); | |
| Ehc->Urb = (PEI_URB *)((UINTN)TempPtr); | |
| if (Ehc->Urb == NULL) { | |
| return Status; | |
| } | |
| EhcPowerOnAllPorts (Ehc); | |
| MicroSecondDelay (EHC_ROOT_PORT_RECOVERY_STALL); | |
| Status = EhcInitSched (Ehc); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| // | |
| // 1. Program the CTRLDSSEGMENT register with the high 32 bit addr | |
| // | |
| EhcWriteOpReg (Ehc, EHC_CTRLDSSEG_OFFSET, Ehc->High32bitAddr); | |
| // | |
| // 2. Clear USBINTR to disable all the interrupt. UEFI works by polling | |
| // | |
| EhcWriteOpReg (Ehc, EHC_USBINTR_OFFSET, 0); | |
| // | |
| // 3. Program periodic frame list, already done in EhcInitSched | |
| // 4. Start the Host Controller | |
| // | |
| EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_RUN); | |
| // | |
| // 5. Set all ports routing to EHC | |
| // | |
| EhcSetOpRegBit (Ehc, EHC_CONFIG_FLAG_OFFSET, CONFIGFLAG_ROUTE_EHC); | |
| // | |
| // Wait roothub port power stable | |
| // | |
| MicroSecondDelay (EHC_ROOT_PORT_RECOVERY_STALL); | |
| Status = EhcEnablePeriodSchd (Ehc, EHC_GENERIC_TIMEOUT); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| Status = EhcEnableAsyncSchd (Ehc, EHC_GENERIC_TIMEOUT); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Submits bulk transfer to a bulk endpoint of a USB device. | |
| @param PeiServices The pointer of EFI_PEI_SERVICES. | |
| @param This The pointer of PEI_USB2_HOST_CONTROLLER_PPI. | |
| @param DeviceAddress Target device address. | |
| @param EndPointAddress Endpoint number and its direction in bit 7. | |
| @param DeviceSpeed Device speed, Low speed device doesn't support | |
| bulk transfer. | |
| @param MaximumPacketLength Maximum packet size the endpoint is capable of | |
| sending or receiving. | |
| @param Data Array of pointers to the buffers of data to transmit | |
| from or receive into. | |
| @param DataLength The lenght of the data buffer. | |
| @param DataToggle On input, the initial data toggle for the transfer; | |
| On output, it is updated to to next data toggle to use of | |
| the subsequent bulk transfer. | |
| @param TimeOut Indicates the maximum time, in millisecond, which the | |
| transfer is allowed to complete. | |
| If Timeout is 0, then the caller must wait for the function | |
| to be completed until EFI_SUCCESS or EFI_DEVICE_ERROR is returned. | |
| @param Translator A pointr to the transaction translator data. | |
| @param TransferResult A pointer to the detailed result information of the | |
| bulk transfer. | |
| @retval EFI_SUCCESS The transfer was completed successfully. | |
| @retval EFI_OUT_OF_RESOURCES The transfer failed due to lack of resource. | |
| @retval EFI_INVALID_PARAMETER Parameters are invalid. | |
| @retval EFI_TIMEOUT The transfer failed due to timeout. | |
| @retval EFI_DEVICE_ERROR The transfer failed due to host controller error. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EhcBulkTransfer ( | |
| IN EFI_PEI_SERVICES **PeiServices, | |
| IN PEI_USB2_HOST_CONTROLLER_PPI *This, | |
| IN UINT8 DeviceAddress, | |
| IN UINT8 EndPointAddress, | |
| IN UINT8 DeviceSpeed, | |
| IN UINTN MaximumPacketLength, | |
| IN OUT VOID *Data[EFI_USB_MAX_BULK_BUFFER_NUM], | |
| IN OUT UINTN *DataLength, | |
| IN OUT UINT8 *DataToggle, | |
| IN UINTN TimeOut, | |
| IN EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator, | |
| OUT UINT32 *TransferResult | |
| ) | |
| { | |
| PEI_USB2_HC_DEV *Ehc; | |
| PEI_URB *Urb; | |
| EFI_STATUS Status; | |
| // | |
| // Validate the parameters | |
| // | |
| if ((DataLength == NULL) || (*DataLength == 0) || | |
| (Data == NULL) || (Data[0] == NULL) || (TransferResult == NULL)) | |
| { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| if ((*DataToggle != 0) && (*DataToggle != 1)) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| if ((DeviceSpeed == EFI_USB_SPEED_LOW) || | |
| ((DeviceSpeed == EFI_USB_SPEED_FULL) && (MaximumPacketLength > 64)) || | |
| ((EFI_USB_SPEED_HIGH == DeviceSpeed) && (MaximumPacketLength > 512))) | |
| { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| Ehc = PEI_RECOVERY_USB_EHC_DEV_FROM_EHCI_THIS (This); | |
| *TransferResult = EFI_USB_ERR_SYSTEM; | |
| Status = EFI_DEVICE_ERROR; | |
| if (EhcIsHalt (Ehc) || EhcIsSysError (Ehc)) { | |
| EhcAckAllInterrupt (Ehc); | |
| goto ON_EXIT; | |
| } | |
| EhcAckAllInterrupt (Ehc); | |
| // | |
| // Create a new URB, insert it into the asynchronous | |
| // schedule list, then poll the execution status. | |
| // | |
| Urb = EhcCreateUrb ( | |
| Ehc, | |
| DeviceAddress, | |
| EndPointAddress, | |
| DeviceSpeed, | |
| *DataToggle, | |
| MaximumPacketLength, | |
| Translator, | |
| EHC_BULK_TRANSFER, | |
| NULL, | |
| Data[0], | |
| *DataLength, | |
| NULL, | |
| NULL, | |
| 1 | |
| ); | |
| if (Urb == NULL) { | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto ON_EXIT; | |
| } | |
| EhcLinkQhToAsync (Ehc, Urb->Qh); | |
| Status = EhcExecTransfer (Ehc, Urb, TimeOut); | |
| EhcUnlinkQhFromAsync (Ehc, Urb->Qh); | |
| *TransferResult = Urb->Result; | |
| *DataLength = Urb->Completed; | |
| *DataToggle = Urb->DataToggle; | |
| if (*TransferResult == EFI_USB_NOERROR) { | |
| Status = EFI_SUCCESS; | |
| } | |
| EhcAckAllInterrupt (Ehc); | |
| EhcFreeUrb (Ehc, Urb); | |
| ON_EXIT: | |
| return Status; | |
| } | |
| /** | |
| Retrieves the number of root hub ports. | |
| @param[in] PeiServices The pointer to the PEI Services Table. | |
| @param[in] This The pointer to this instance of the | |
| PEI_USB2_HOST_CONTROLLER_PPI. | |
| @param[out] PortNumber The pointer to the number of the root hub ports. | |
| @retval EFI_SUCCESS The port number was retrieved successfully. | |
| @retval EFI_INVALID_PARAMETER PortNumber is NULL. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EhcGetRootHubPortNumber ( | |
| IN EFI_PEI_SERVICES **PeiServices, | |
| IN PEI_USB2_HOST_CONTROLLER_PPI *This, | |
| OUT UINT8 *PortNumber | |
| ) | |
| { | |
| PEI_USB2_HC_DEV *EhcDev; | |
| EhcDev = PEI_RECOVERY_USB_EHC_DEV_FROM_EHCI_THIS (This); | |
| if (PortNumber == NULL) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| *PortNumber = (UINT8)(EhcDev->HcStructParams & HCSP_NPORTS); | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Clears a feature for the specified root hub port. | |
| @param PeiServices The pointer of EFI_PEI_SERVICES. | |
| @param This The pointer of PEI_USB2_HOST_CONTROLLER_PPI. | |
| @param PortNumber Specifies the root hub port whose feature | |
| is requested to be cleared. | |
| @param PortFeature Indicates the feature selector associated with the | |
| feature clear request. | |
| @retval EFI_SUCCESS The feature specified by PortFeature was cleared | |
| for the USB root hub port specified by PortNumber. | |
| @retval EFI_INVALID_PARAMETER PortNumber is invalid or PortFeature is invalid. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EhcClearRootHubPortFeature ( | |
| IN EFI_PEI_SERVICES **PeiServices, | |
| IN PEI_USB2_HOST_CONTROLLER_PPI *This, | |
| IN UINT8 PortNumber, | |
| IN EFI_USB_PORT_FEATURE PortFeature | |
| ) | |
| { | |
| PEI_USB2_HC_DEV *Ehc; | |
| UINT32 Offset; | |
| UINT32 State; | |
| UINT32 TotalPort; | |
| EFI_STATUS Status; | |
| Ehc = PEI_RECOVERY_USB_EHC_DEV_FROM_EHCI_THIS (This); | |
| Status = EFI_SUCCESS; | |
| TotalPort = (Ehc->HcStructParams & HCSP_NPORTS); | |
| if (PortNumber >= TotalPort) { | |
| Status = EFI_INVALID_PARAMETER; | |
| goto ON_EXIT; | |
| } | |
| Offset = EHC_PORT_STAT_OFFSET + (4 * PortNumber); | |
| State = EhcReadOpReg (Ehc, Offset); | |
| State &= ~PORTSC_CHANGE_MASK; | |
| switch (PortFeature) { | |
| case EfiUsbPortEnable: | |
| // | |
| // Clear PORT_ENABLE feature means disable port. | |
| // | |
| State &= ~PORTSC_ENABLED; | |
| EhcWriteOpReg (Ehc, Offset, State); | |
| break; | |
| case EfiUsbPortSuspend: | |
| // | |
| // A write of zero to this bit is ignored by the host | |
| // controller. The host controller will unconditionally | |
| // set this bit to a zero when: | |
| // 1. software sets the Forct Port Resume bit to a zero from a one. | |
| // 2. software sets the Port Reset bit to a one frome a zero. | |
| // | |
| State &= ~PORSTSC_RESUME; | |
| EhcWriteOpReg (Ehc, Offset, State); | |
| break; | |
| case EfiUsbPortReset: | |
| // | |
| // Clear PORT_RESET means clear the reset signal. | |
| // | |
| State &= ~PORTSC_RESET; | |
| EhcWriteOpReg (Ehc, Offset, State); | |
| break; | |
| case EfiUsbPortOwner: | |
| // | |
| // Clear port owner means this port owned by EHC | |
| // | |
| State &= ~PORTSC_OWNER; | |
| EhcWriteOpReg (Ehc, Offset, State); | |
| break; | |
| case EfiUsbPortConnectChange: | |
| // | |
| // Clear connect status change | |
| // | |
| State |= PORTSC_CONN_CHANGE; | |
| EhcWriteOpReg (Ehc, Offset, State); | |
| break; | |
| case EfiUsbPortEnableChange: | |
| // | |
| // Clear enable status change | |
| // | |
| State |= PORTSC_ENABLE_CHANGE; | |
| EhcWriteOpReg (Ehc, Offset, State); | |
| break; | |
| case EfiUsbPortOverCurrentChange: | |
| // | |
| // Clear PortOverCurrent change | |
| // | |
| State |= PORTSC_OVERCUR_CHANGE; | |
| EhcWriteOpReg (Ehc, Offset, State); | |
| break; | |
| case EfiUsbPortPower: | |
| case EfiUsbPortSuspendChange: | |
| case EfiUsbPortResetChange: | |
| // | |
| // Not supported or not related operation | |
| // | |
| break; | |
| default: | |
| Status = EFI_INVALID_PARAMETER; | |
| break; | |
| } | |
| ON_EXIT: | |
| return Status; | |
| } | |
| /** | |
| Sets a feature for the specified root hub port. | |
| @param PeiServices The pointer of EFI_PEI_SERVICES | |
| @param This The pointer of PEI_USB2_HOST_CONTROLLER_PPI | |
| @param PortNumber Root hub port to set. | |
| @param PortFeature Feature to set. | |
| @retval EFI_SUCCESS The feature specified by PortFeature was set. | |
| @retval EFI_INVALID_PARAMETER PortNumber is invalid or PortFeature is invalid. | |
| @retval EFI_TIMEOUT The time out occurred. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EhcSetRootHubPortFeature ( | |
| IN EFI_PEI_SERVICES **PeiServices, | |
| IN PEI_USB2_HOST_CONTROLLER_PPI *This, | |
| IN UINT8 PortNumber, | |
| IN EFI_USB_PORT_FEATURE PortFeature | |
| ) | |
| { | |
| PEI_USB2_HC_DEV *Ehc; | |
| UINT32 Offset; | |
| UINT32 State; | |
| UINT32 TotalPort; | |
| EFI_STATUS Status; | |
| Ehc = PEI_RECOVERY_USB_EHC_DEV_FROM_EHCI_THIS (This); | |
| Status = EFI_SUCCESS; | |
| TotalPort = (Ehc->HcStructParams & HCSP_NPORTS); | |
| if (PortNumber >= TotalPort) { | |
| Status = EFI_INVALID_PARAMETER; | |
| goto ON_EXIT; | |
| } | |
| Offset = (UINT32)(EHC_PORT_STAT_OFFSET + (4 * PortNumber)); | |
| State = EhcReadOpReg (Ehc, Offset); | |
| // | |
| // Mask off the port status change bits, these bits are | |
| // write clean bit | |
| // | |
| State &= ~PORTSC_CHANGE_MASK; | |
| switch (PortFeature) { | |
| case EfiUsbPortEnable: | |
| // | |
| // Sofeware can't set this bit, Port can only be enable by | |
| // EHCI as a part of the reset and enable | |
| // | |
| State |= PORTSC_ENABLED; | |
| EhcWriteOpReg (Ehc, Offset, State); | |
| break; | |
| case EfiUsbPortSuspend: | |
| State |= PORTSC_SUSPEND; | |
| EhcWriteOpReg (Ehc, Offset, State); | |
| break; | |
| case EfiUsbPortReset: | |
| // | |
| // Make sure Host Controller not halt before reset it | |
| // | |
| if (EhcIsHalt (Ehc)) { | |
| Status = EhcRunHC (Ehc, EHC_GENERIC_TIMEOUT); | |
| if (EFI_ERROR (Status)) { | |
| break; | |
| } | |
| } | |
| // | |
| // Set one to PortReset bit must also set zero to PortEnable bit | |
| // | |
| State |= PORTSC_RESET; | |
| State &= ~PORTSC_ENABLED; | |
| EhcWriteOpReg (Ehc, Offset, State); | |
| break; | |
| case EfiUsbPortPower: | |
| // | |
| // Not supported, ignore the operation | |
| // | |
| Status = EFI_SUCCESS; | |
| break; | |
| case EfiUsbPortOwner: | |
| State |= PORTSC_OWNER; | |
| EhcWriteOpReg (Ehc, Offset, State); | |
| break; | |
| default: | |
| Status = EFI_INVALID_PARAMETER; | |
| } | |
| ON_EXIT: | |
| return Status; | |
| } | |
| /** | |
| Retrieves the current status of a USB root hub port. | |
| @param PeiServices The pointer of EFI_PEI_SERVICES. | |
| @param This The pointer of PEI_USB2_HOST_CONTROLLER_PPI. | |
| @param PortNumber The root hub port to retrieve the state from. | |
| @param PortStatus Variable to receive the port state. | |
| @retval EFI_SUCCESS The status of the USB root hub port specified. | |
| by PortNumber was returned in PortStatus. | |
| @retval EFI_INVALID_PARAMETER PortNumber is invalid. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EhcGetRootHubPortStatus ( | |
| IN EFI_PEI_SERVICES **PeiServices, | |
| IN PEI_USB2_HOST_CONTROLLER_PPI *This, | |
| IN UINT8 PortNumber, | |
| OUT EFI_USB_PORT_STATUS *PortStatus | |
| ) | |
| { | |
| PEI_USB2_HC_DEV *Ehc; | |
| UINT32 Offset; | |
| UINT32 State; | |
| UINT32 TotalPort; | |
| UINTN Index; | |
| UINTN MapSize; | |
| EFI_STATUS Status; | |
| if (PortStatus == NULL) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| Ehc = PEI_RECOVERY_USB_EHC_DEV_FROM_EHCI_THIS (This); | |
| Status = EFI_SUCCESS; | |
| TotalPort = (Ehc->HcStructParams & HCSP_NPORTS); | |
| if (PortNumber >= TotalPort) { | |
| Status = EFI_INVALID_PARAMETER; | |
| goto ON_EXIT; | |
| } | |
| Offset = (UINT32)(EHC_PORT_STAT_OFFSET + (4 * PortNumber)); | |
| PortStatus->PortStatus = 0; | |
| PortStatus->PortChangeStatus = 0; | |
| State = EhcReadOpReg (Ehc, Offset); | |
| // | |
| // Identify device speed. If in K state, it is low speed. | |
| // If the port is enabled after reset, the device is of | |
| // high speed. The USB bus driver should retrieve the actual | |
| // port speed after reset. | |
| // | |
| if (EHC_BIT_IS_SET (State, PORTSC_LINESTATE_K)) { | |
| PortStatus->PortStatus |= USB_PORT_STAT_LOW_SPEED; | |
| } else if (EHC_BIT_IS_SET (State, PORTSC_ENABLED)) { | |
| PortStatus->PortStatus |= USB_PORT_STAT_HIGH_SPEED; | |
| } | |
| // | |
| // Convert the EHCI port/port change state to UEFI status | |
| // | |
| MapSize = sizeof (mUsbPortStateMap) / sizeof (USB_PORT_STATE_MAP); | |
| for (Index = 0; Index < MapSize; Index++) { | |
| if (EHC_BIT_IS_SET (State, mUsbPortStateMap[Index].HwState)) { | |
| PortStatus->PortStatus = (UINT16)(PortStatus->PortStatus | mUsbPortStateMap[Index].UefiState); | |
| } | |
| } | |
| MapSize = sizeof (mUsbPortChangeMap) / sizeof (USB_PORT_STATE_MAP); | |
| for (Index = 0; Index < MapSize; Index++) { | |
| if (EHC_BIT_IS_SET (State, mUsbPortChangeMap[Index].HwState)) { | |
| PortStatus->PortChangeStatus = (UINT16)(PortStatus->PortChangeStatus | mUsbPortChangeMap[Index].UefiState); | |
| } | |
| } | |
| ON_EXIT: | |
| return Status; | |
| } | |
| /** | |
| Submits control transfer to a target USB device. | |
| @param PeiServices The pointer of EFI_PEI_SERVICES. | |
| @param This The pointer of PEI_USB2_HOST_CONTROLLER_PPI. | |
| @param DeviceAddress The target device address. | |
| @param DeviceSpeed Target device speed. | |
| @param MaximumPacketLength Maximum packet size the default control transfer | |
| endpoint is capable of sending or receiving. | |
| @param Request USB device request to send. | |
| @param TransferDirection Specifies the data direction for the data stage. | |
| @param Data Data buffer to be transmitted or received from USB device. | |
| @param DataLength The size (in bytes) of the data buffer. | |
| @param TimeOut Indicates the maximum timeout, in millisecond. | |
| If Timeout is 0, then the caller must wait for the function | |
| to be completed until EFI_SUCCESS or EFI_DEVICE_ERROR is returned. | |
| @param Translator Transaction translator to be used by this device. | |
| @param TransferResult Return the result of this control transfer. | |
| @retval EFI_SUCCESS Transfer was completed successfully. | |
| @retval EFI_OUT_OF_RESOURCES The transfer failed due to lack of resources. | |
| @retval EFI_INVALID_PARAMETER Some parameters are invalid. | |
| @retval EFI_TIMEOUT Transfer failed due to timeout. | |
| @retval EFI_DEVICE_ERROR Transfer failed due to host controller or device error. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EhcControlTransfer ( | |
| IN EFI_PEI_SERVICES **PeiServices, | |
| IN PEI_USB2_HOST_CONTROLLER_PPI *This, | |
| IN UINT8 DeviceAddress, | |
| IN UINT8 DeviceSpeed, | |
| IN UINTN MaximumPacketLength, | |
| IN EFI_USB_DEVICE_REQUEST *Request, | |
| IN EFI_USB_DATA_DIRECTION TransferDirection, | |
| IN OUT VOID *Data, | |
| IN OUT UINTN *DataLength, | |
| IN UINTN TimeOut, | |
| IN EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator, | |
| OUT UINT32 *TransferResult | |
| ) | |
| { | |
| PEI_USB2_HC_DEV *Ehc; | |
| PEI_URB *Urb; | |
| UINT8 Endpoint; | |
| EFI_STATUS Status; | |
| // | |
| // Validate parameters | |
| // | |
| if ((Request == NULL) || (TransferResult == NULL)) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| if ((TransferDirection != EfiUsbDataIn) && | |
| (TransferDirection != EfiUsbDataOut) && | |
| (TransferDirection != EfiUsbNoData)) | |
| { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| if ((TransferDirection == EfiUsbNoData) && | |
| ((Data != NULL) || (*DataLength != 0))) | |
| { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| if ((TransferDirection != EfiUsbNoData) && | |
| ((Data == NULL) || (*DataLength == 0))) | |
| { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| if ((MaximumPacketLength != 8) && (MaximumPacketLength != 16) && | |
| (MaximumPacketLength != 32) && (MaximumPacketLength != 64)) | |
| { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| if ((DeviceSpeed == EFI_USB_SPEED_LOW) || | |
| ((DeviceSpeed == EFI_USB_SPEED_FULL) && (MaximumPacketLength > 64)) || | |
| ((EFI_USB_SPEED_HIGH == DeviceSpeed) && (MaximumPacketLength > 512))) | |
| { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| Ehc = PEI_RECOVERY_USB_EHC_DEV_FROM_EHCI_THIS (This); | |
| Status = EFI_DEVICE_ERROR; | |
| *TransferResult = EFI_USB_ERR_SYSTEM; | |
| if (EhcIsHalt (Ehc) || EhcIsSysError (Ehc)) { | |
| EhcAckAllInterrupt (Ehc); | |
| goto ON_EXIT; | |
| } | |
| EhcAckAllInterrupt (Ehc); | |
| // | |
| // Create a new URB, insert it into the asynchronous | |
| // schedule list, then poll the execution status. | |
| // | |
| // | |
| // Encode the direction in address, although default control | |
| // endpoint is bidirectional. EhcCreateUrb expects this | |
| // combination of Ep addr and its direction. | |
| // | |
| Endpoint = (UINT8)(0 | ((TransferDirection == EfiUsbDataIn) ? 0x80 : 0)); | |
| Urb = EhcCreateUrb ( | |
| Ehc, | |
| DeviceAddress, | |
| Endpoint, | |
| DeviceSpeed, | |
| 0, | |
| MaximumPacketLength, | |
| Translator, | |
| EHC_CTRL_TRANSFER, | |
| Request, | |
| Data, | |
| *DataLength, | |
| NULL, | |
| NULL, | |
| 1 | |
| ); | |
| if (Urb == NULL) { | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto ON_EXIT; | |
| } | |
| EhcLinkQhToAsync (Ehc, Urb->Qh); | |
| Status = EhcExecTransfer (Ehc, Urb, TimeOut); | |
| EhcUnlinkQhFromAsync (Ehc, Urb->Qh); | |
| // | |
| // Get the status from URB. The result is updated in EhcCheckUrbResult | |
| // which is called by EhcExecTransfer | |
| // | |
| *TransferResult = Urb->Result; | |
| *DataLength = Urb->Completed; | |
| if (*TransferResult == EFI_USB_NOERROR) { | |
| Status = EFI_SUCCESS; | |
| } | |
| EhcAckAllInterrupt (Ehc); | |
| EhcFreeUrb (Ehc, Urb); | |
| ON_EXIT: | |
| return Status; | |
| } | |
| /** | |
| One notified function to stop the Host Controller at the end of PEI | |
| @param[in] PeiServices Pointer to PEI Services Table. | |
| @param[in] NotifyDescriptor Pointer to the descriptor for the Notification event that | |
| caused this function to execute. | |
| @param[in] Ppi Pointer to the PPI data associated with this function. | |
| @retval EFI_SUCCESS The function completes successfully | |
| @retval others | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EhcEndOfPei ( | |
| IN EFI_PEI_SERVICES **PeiServices, | |
| IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, | |
| IN VOID *Ppi | |
| ) | |
| { | |
| PEI_USB2_HC_DEV *Ehc; | |
| Ehc = PEI_RECOVERY_USB_EHC_DEV_FROM_THIS_NOTIFY (NotifyDescriptor); | |
| EhcHaltHC (Ehc, EHC_GENERIC_TIMEOUT); | |
| EhcFreeSched (Ehc); | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| @param FileHandle Handle of the file being invoked. | |
| @param PeiServices Describes the list of possible PEI Services. | |
| @retval EFI_SUCCESS PPI successfully installed. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EhcPeimEntry ( | |
| IN EFI_PEI_FILE_HANDLE FileHandle, | |
| IN CONST EFI_PEI_SERVICES **PeiServices | |
| ) | |
| { | |
| PEI_USB_CONTROLLER_PPI *ChipSetUsbControllerPpi; | |
| EFI_STATUS Status; | |
| UINT8 Index; | |
| UINTN ControllerType; | |
| UINTN BaseAddress; | |
| UINTN MemPages; | |
| PEI_USB2_HC_DEV *EhcDev; | |
| EFI_PHYSICAL_ADDRESS TempPtr; | |
| // | |
| // Shadow this PEIM to run from memory | |
| // | |
| if (!EFI_ERROR (PeiServicesRegisterForShadow (FileHandle))) { | |
| return EFI_SUCCESS; | |
| } | |
| Status = PeiServicesLocatePpi ( | |
| &gPeiUsbControllerPpiGuid, | |
| 0, | |
| NULL, | |
| (VOID **)&ChipSetUsbControllerPpi | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| return EFI_UNSUPPORTED; | |
| } | |
| Index = 0; | |
| while (TRUE) { | |
| Status = ChipSetUsbControllerPpi->GetUsbController ( | |
| (EFI_PEI_SERVICES **)PeiServices, | |
| ChipSetUsbControllerPpi, | |
| Index, | |
| &ControllerType, | |
| &BaseAddress | |
| ); | |
| // | |
| // When status is error, meant no controller is found | |
| // | |
| if (EFI_ERROR (Status)) { | |
| break; | |
| } | |
| // | |
| // This PEIM is for UHC type controller. | |
| // | |
| if (ControllerType != PEI_EHCI_CONTROLLER) { | |
| Index++; | |
| continue; | |
| } | |
| MemPages = sizeof (PEI_USB2_HC_DEV) / PAGESIZE + 1; | |
| Status = PeiServicesAllocatePages ( | |
| EfiBootServicesCode, | |
| MemPages, | |
| &TempPtr | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| ZeroMem ((VOID *)(UINTN)TempPtr, MemPages*PAGESIZE); | |
| EhcDev = (PEI_USB2_HC_DEV *)((UINTN)TempPtr); | |
| EhcDev->Signature = USB2_HC_DEV_SIGNATURE; | |
| IoMmuInit (&EhcDev->IoMmu); | |
| EhcDev->UsbHostControllerBaseAddress = (UINT32)BaseAddress; | |
| EhcDev->HcStructParams = EhcReadCapRegister (EhcDev, EHC_HCSPARAMS_OFFSET); | |
| EhcDev->HcCapParams = EhcReadCapRegister (EhcDev, EHC_HCCPARAMS_OFFSET); | |
| EhcDev->CapLen = EhcReadCapRegister (EhcDev, EHC_CAPLENGTH_OFFSET) & 0x0FF; | |
| // | |
| // Initialize Uhc's hardware | |
| // | |
| Status = InitializeUsbHC (EhcDev); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| EhcDev->Usb2HostControllerPpi.ControlTransfer = EhcControlTransfer; | |
| EhcDev->Usb2HostControllerPpi.BulkTransfer = EhcBulkTransfer; | |
| EhcDev->Usb2HostControllerPpi.GetRootHubPortNumber = EhcGetRootHubPortNumber; | |
| EhcDev->Usb2HostControllerPpi.GetRootHubPortStatus = EhcGetRootHubPortStatus; | |
| EhcDev->Usb2HostControllerPpi.SetRootHubPortFeature = EhcSetRootHubPortFeature; | |
| EhcDev->Usb2HostControllerPpi.ClearRootHubPortFeature = EhcClearRootHubPortFeature; | |
| EhcDev->PpiDescriptor.Flags = (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST); | |
| EhcDev->PpiDescriptor.Guid = &gPeiUsb2HostControllerPpiGuid; | |
| EhcDev->PpiDescriptor.Ppi = &EhcDev->Usb2HostControllerPpi; | |
| Status = PeiServicesInstallPpi (&EhcDev->PpiDescriptor); | |
| if (EFI_ERROR (Status)) { | |
| Index++; | |
| continue; | |
| } | |
| EhcDev->EndOfPeiNotifyList.Flags = (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST); | |
| EhcDev->EndOfPeiNotifyList.Guid = &gEfiEndOfPeiSignalPpiGuid; | |
| EhcDev->EndOfPeiNotifyList.Notify = EhcEndOfPei; | |
| PeiServicesNotifyPpi (&EhcDev->EndOfPeiNotifyList); | |
| Index++; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| @param EhcDev EHCI Device. | |
| @retval EFI_SUCCESS EHCI successfully initialized. | |
| @retval EFI_ABORTED EHCI was failed to be initialized. | |
| **/ | |
| EFI_STATUS | |
| InitializeUsbHC ( | |
| IN PEI_USB2_HC_DEV *EhcDev | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EhcResetHC (EhcDev, EHC_RESET_TIMEOUT); | |
| Status = EhcInitHC (EhcDev); | |
| if (EFI_ERROR (Status)) { | |
| return EFI_ABORTED; | |
| } | |
| return EFI_SUCCESS; | |
| } |