/** @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.
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; }