diff options
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhcPeim.c')
-rw-r--r-- | src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhcPeim.c | 1314 |
1 files changed, 1314 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhcPeim.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhcPeim.c new file mode 100644 index 00000000..7477f42b --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhcPeim.c @@ -0,0 +1,1314 @@ +/** @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; +} |