diff options
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei')
13 files changed, 4384 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/DmaMem.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/DmaMem.c new file mode 100644 index 00000000..c3d3bdb3 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/DmaMem.c @@ -0,0 +1,243 @@ +/** @file +The DMA memory help functions. + +Copyright (c) 2017, Intel Corporation. All rights reserved.<BR> + +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "EhcPeim.h" + +/** + Provides the controller-specific addresses required to access system memory from a + DMA bus master. + + @param IoMmu Pointer to IOMMU PPI. + @param Operation Indicates if the bus master is going to read or write to system memory. + @param HostAddress The system memory address to map to the PCI controller. + @param NumberOfBytes On input the number of bytes to map. On output the number of bytes + that were mapped. + @param DeviceAddress The resulting map address for the bus master PCI controller to use to + access the hosts HostAddress. + @param Mapping A resulting value to pass to Unmap(). + + @retval EFI_SUCCESS The range was mapped for the returned NumberOfBytes. + @retval EFI_UNSUPPORTED The HostAddress cannot be mapped as a common buffer. + @retval EFI_INVALID_PARAMETER One or more parameters are invalid. + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack of resources. + @retval EFI_DEVICE_ERROR The system hardware could not map the requested address. + +**/ +EFI_STATUS +IoMmuMap ( + IN EDKII_IOMMU_PPI *IoMmu, + IN EDKII_IOMMU_OPERATION Operation, + IN VOID *HostAddress, + IN OUT UINTN *NumberOfBytes, + OUT EFI_PHYSICAL_ADDRESS *DeviceAddress, + OUT VOID **Mapping + ) +{ + EFI_STATUS Status; + UINT64 Attribute; + + if (IoMmu != NULL) { + Status = IoMmu->Map ( + IoMmu, + Operation, + HostAddress, + NumberOfBytes, + DeviceAddress, + Mapping + ); + if (EFI_ERROR (Status)) { + return EFI_OUT_OF_RESOURCES; + } + switch (Operation) { + case EdkiiIoMmuOperationBusMasterRead: + case EdkiiIoMmuOperationBusMasterRead64: + Attribute = EDKII_IOMMU_ACCESS_READ; + break; + case EdkiiIoMmuOperationBusMasterWrite: + case EdkiiIoMmuOperationBusMasterWrite64: + Attribute = EDKII_IOMMU_ACCESS_WRITE; + break; + case EdkiiIoMmuOperationBusMasterCommonBuffer: + case EdkiiIoMmuOperationBusMasterCommonBuffer64: + Attribute = EDKII_IOMMU_ACCESS_READ | EDKII_IOMMU_ACCESS_WRITE; + break; + default: + ASSERT(FALSE); + return EFI_INVALID_PARAMETER; + } + Status = IoMmu->SetAttribute ( + IoMmu, + *Mapping, + Attribute + ); + if (EFI_ERROR (Status)) { + IoMmu->Unmap (IoMmu, Mapping); + *Mapping = NULL; + return Status; + } + } else { + *DeviceAddress = (EFI_PHYSICAL_ADDRESS) (UINTN) HostAddress; + *Mapping = NULL; + Status = EFI_SUCCESS; + } + return Status; +} + +/** + Completes the Map() operation and releases any corresponding resources. + + @param IoMmu Pointer to IOMMU PPI. + @param Mapping The mapping value returned from Map(). + +**/ +VOID +IoMmuUnmap ( + IN EDKII_IOMMU_PPI *IoMmu, + IN VOID *Mapping + ) +{ + if (IoMmu != NULL) { + IoMmu->SetAttribute (IoMmu, Mapping, 0); + IoMmu->Unmap (IoMmu, Mapping); + } +} + +/** + Allocates pages that are suitable for an OperationBusMasterCommonBuffer or + OperationBusMasterCommonBuffer64 mapping. + + @param IoMmu Pointer to IOMMU PPI. + @param Pages The number of pages to allocate. + @param HostAddress A pointer to store the base system memory address of the + allocated range. + @param DeviceAddress The resulting map address for the bus master PCI controller to use to + access the hosts HostAddress. + @param Mapping A resulting value to pass to Unmap(). + + @retval EFI_SUCCESS The requested memory pages were allocated. + @retval EFI_UNSUPPORTED Attributes is unsupported. The only legal attribute bits are + MEMORY_WRITE_COMBINE and MEMORY_CACHED. + @retval EFI_INVALID_PARAMETER One or more parameters are invalid. + @retval EFI_OUT_OF_RESOURCES The memory pages could not be allocated. + +**/ +EFI_STATUS +IoMmuAllocateBuffer ( + IN EDKII_IOMMU_PPI *IoMmu, + IN UINTN Pages, + OUT VOID **HostAddress, + OUT EFI_PHYSICAL_ADDRESS *DeviceAddress, + OUT VOID **Mapping + ) +{ + EFI_STATUS Status; + UINTN NumberOfBytes; + EFI_PHYSICAL_ADDRESS HostPhyAddress; + + *HostAddress = NULL; + *DeviceAddress = 0; + *Mapping = NULL; + + if (IoMmu != NULL) { + Status = IoMmu->AllocateBuffer ( + IoMmu, + EfiBootServicesData, + Pages, + HostAddress, + 0 + ); + if (EFI_ERROR (Status)) { + return EFI_OUT_OF_RESOURCES; + } + + NumberOfBytes = EFI_PAGES_TO_SIZE (Pages); + Status = IoMmu->Map ( + IoMmu, + EdkiiIoMmuOperationBusMasterCommonBuffer, + *HostAddress, + &NumberOfBytes, + DeviceAddress, + Mapping + ); + if (EFI_ERROR (Status)) { + IoMmu->FreeBuffer (IoMmu, Pages, *HostAddress); + *HostAddress = NULL; + return EFI_OUT_OF_RESOURCES; + } + Status = IoMmu->SetAttribute ( + IoMmu, + *Mapping, + EDKII_IOMMU_ACCESS_READ | EDKII_IOMMU_ACCESS_WRITE + ); + if (EFI_ERROR (Status)) { + IoMmu->Unmap (IoMmu, *Mapping); + IoMmu->FreeBuffer (IoMmu, Pages, *HostAddress); + *Mapping = NULL; + *HostAddress = NULL; + return Status; + } + } else { + Status = PeiServicesAllocatePages ( + EfiBootServicesCode, + Pages, + &HostPhyAddress + ); + if (EFI_ERROR (Status)) { + return EFI_OUT_OF_RESOURCES; + } + *HostAddress = (VOID *) (UINTN) HostPhyAddress; + *DeviceAddress = HostPhyAddress; + *Mapping = NULL; + } + return Status; +} + +/** + Frees memory that was allocated with AllocateBuffer(). + + @param IoMmu Pointer to IOMMU PPI. + @param Pages The number of pages to free. + @param HostAddress The base system memory address of the allocated range. + @param Mapping The mapping value returned from Map(). + +**/ +VOID +IoMmuFreeBuffer ( + IN EDKII_IOMMU_PPI *IoMmu, + IN UINTN Pages, + IN VOID *HostAddress, + IN VOID *Mapping + ) +{ + if (IoMmu != NULL) { + IoMmu->SetAttribute (IoMmu, Mapping, 0); + IoMmu->Unmap (IoMmu, Mapping); + IoMmu->FreeBuffer (IoMmu, Pages, HostAddress); + } +} + +/** + Initialize IOMMU. + + @param IoMmu Pointer to pointer to IOMMU PPI. + +**/ +VOID +IoMmuInit ( + OUT EDKII_IOMMU_PPI **IoMmu + ) +{ + PeiServicesLocatePpi ( + &gEdkiiIoMmuPpiGuid, + 0, + NULL, + (VOID **) IoMmu + ); +} + 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; +} diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhcPeim.h b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhcPeim.h new file mode 100644 index 00000000..373f8384 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhcPeim.h @@ -0,0 +1,316 @@ +/** @file +Private Header file for Usb Host Controller PEIM + +Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.<BR> +Copyright (c) Microsoft Corporation.<BR> + +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _RECOVERY_EHC_H_ +#define _RECOVERY_EHC_H_ + +#include <PiPei.h> + +#include <Ppi/UsbController.h> +#include <Ppi/Usb2HostController.h> +#include <Ppi/IoMmu.h> +#include <Ppi/EndOfPeiPhase.h> + +#include <Library/BaseLib.h> +#include <Library/DebugLib.h> +#include <Library/PeimEntryPoint.h> +#include <Library/PeiServicesLib.h> +#include <Library/BaseMemoryLib.h> +#include <Library/TimerLib.h> +#include <Library/IoLib.h> + +typedef struct _PEI_USB2_HC_DEV PEI_USB2_HC_DEV; + +#define EFI_LIST_ENTRY LIST_ENTRY + +#include "UsbHcMem.h" +#include "EhciReg.h" +#include "EhciUrb.h" +#include "EhciSched.h" + +#define EFI_USB_SPEED_FULL 0x0000 +#define EFI_USB_SPEED_LOW 0x0001 +#define EFI_USB_SPEED_HIGH 0x0002 + +#define PAGESIZE 4096 + +#define EHC_1_MICROSECOND 1 +#define EHC_1_MILLISECOND (1000 * EHC_1_MICROSECOND) +#define EHC_1_SECOND (1000 * EHC_1_MILLISECOND) + +// +// EHCI register operation timeout, set by experience +// +#define EHC_RESET_TIMEOUT (1 * EHC_1_SECOND) +#define EHC_GENERIC_TIMEOUT (10 * EHC_1_MILLISECOND) + + +// +// Wait for roothub port power stable, refers to Spec[EHCI1.0-2.3.9] +// +#define EHC_ROOT_PORT_RECOVERY_STALL (20 * EHC_1_MILLISECOND) + +// +// Sync transfer polling interval, set by experience. +// +#define EHC_SYNC_POLL_INTERVAL (6 * EHC_1_MILLISECOND) + +#define EFI_LIST_CONTAINER(Entry, Type, Field) BASE_CR(Entry, Type, Field) + + +#define EHC_LOW_32BIT(Addr64) ((UINT32)(((UINTN)(Addr64)) & 0XFFFFFFFF)) +#define EHC_HIGH_32BIT(Addr64) ((UINT32)(RShiftU64((UINTN)(Addr64), 32) & 0XFFFFFFFF)) +#define EHC_BIT_IS_SET(Data, Bit) ((BOOLEAN)(((Data) & (Bit)) == (Bit))) + +#define EHC_REG_BIT_IS_SET(Ehc, Offset, Bit) \ + (EHC_BIT_IS_SET(EhcReadOpReg ((Ehc), (Offset)), (Bit))) + +#define USB2_HC_DEV_SIGNATURE SIGNATURE_32 ('e', 'h', 'c', 'i') + +struct _PEI_USB2_HC_DEV { + UINTN Signature; + PEI_USB2_HOST_CONTROLLER_PPI Usb2HostControllerPpi; + EDKII_IOMMU_PPI *IoMmu; + EFI_PEI_PPI_DESCRIPTOR PpiDescriptor; + // + // EndOfPei callback is used to stop the EHC DMA operation + // after exit PEI phase. + // + EFI_PEI_NOTIFY_DESCRIPTOR EndOfPeiNotifyList; + UINT32 UsbHostControllerBaseAddress; + PEI_URB *Urb; + USBHC_MEM_POOL *MemPool; + + // + // Schedule data shared between asynchronous and periodic + // transfers: + // ShortReadStop, as its name indicates, is used to terminate + // the short read except the control transfer. EHCI follows + // the alternative next QTD point when a short read happens. + // For control transfer, even the short read happens, try the + // status stage. + // + PEI_EHC_QTD *ShortReadStop; + EFI_EVENT PollTimer; + + // + // Asynchronous(bulk and control) transfer schedule data: + // ReclaimHead is used as the head of the asynchronous transfer + // list. It acts as the reclamation header. + // + PEI_EHC_QH *ReclaimHead; + + // + // Periodic (interrupt) transfer schedule data: + // + VOID *PeriodFrame; // Mapped as common buffer + VOID *PeriodFrameMap; + + PEI_EHC_QH *PeriodOne; + EFI_LIST_ENTRY AsyncIntTransfers; + + // + // EHCI configuration data + // + UINT32 HcStructParams; // Cache of HC structure parameter, EHC_HCSPARAMS_OFFSET + UINT32 HcCapParams; // Cache of HC capability parameter, HCCPARAMS + UINT32 CapLen; // Capability length + UINT32 High32bitAddr; +}; + +#define PEI_RECOVERY_USB_EHC_DEV_FROM_EHCI_THIS(a) CR (a, PEI_USB2_HC_DEV, Usb2HostControllerPpi, USB2_HC_DEV_SIGNATURE) +#define PEI_RECOVERY_USB_EHC_DEV_FROM_THIS_NOTIFY(a) CR (a, PEI_USB2_HC_DEV, EndOfPeiNotifyList, USB2_HC_DEV_SIGNATURE) + +/** + @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 + ); + +/** + Initialize the memory management pool for the host controller. + + @param Ehc The EHCI device. + @param Check4G Whether the host controller requires allocated memory + from one 4G address space. + @param Which4G The 4G memory area each memory allocated should be from. + + @retval EFI_SUCCESS The memory pool is initialized. + @retval EFI_OUT_OF_RESOURCE Fail to init the memory pool. + +**/ +USBHC_MEM_POOL * +UsbHcInitMemPool ( + IN PEI_USB2_HC_DEV *Ehc, + IN BOOLEAN Check4G, + IN UINT32 Which4G + ) +; + +/** + Release the memory management pool. + + @param Ehc The EHCI device. + @param Pool The USB memory pool to free. + + @retval EFI_DEVICE_ERROR Fail to free the memory pool. + @retval EFI_SUCCESS The memory pool is freed. + +**/ +EFI_STATUS +UsbHcFreeMemPool ( + IN PEI_USB2_HC_DEV *Ehc, + IN USBHC_MEM_POOL *Pool + ) +; + +/** + Allocate some memory from the host controller's memory pool + which can be used to communicate with host controller. + + @param Ehc The EHCI device. + @param Pool The host controller's memory pool. + @param Size Size of the memory to allocate. + + @return The allocated memory or NULL. + +**/ +VOID * +UsbHcAllocateMem ( + IN PEI_USB2_HC_DEV *Ehc, + IN USBHC_MEM_POOL *Pool, + IN UINTN Size + ) +; + +/** + Free the allocated memory back to the memory pool. + + @param Ehc The EHCI device. + @param Pool The memory pool of the host controller. + @param Mem The memory to free. + @param Size The size of the memory to free. + +**/ +VOID +UsbHcFreeMem ( + IN PEI_USB2_HC_DEV *Ehc, + IN USBHC_MEM_POOL *Pool, + IN VOID *Mem, + IN UINTN Size + ) +; + +/** + Provides the controller-specific addresses required to access system memory from a + DMA bus master. + + @param IoMmu Pointer to IOMMU PPI. + @param Operation Indicates if the bus master is going to read or write to system memory. + @param HostAddress The system memory address to map to the PCI controller. + @param NumberOfBytes On input the number of bytes to map. On output the number of bytes + that were mapped. + @param DeviceAddress The resulting map address for the bus master PCI controller to use to + access the hosts HostAddress. + @param Mapping A resulting value to pass to Unmap(). + + @retval EFI_SUCCESS The range was mapped for the returned NumberOfBytes. + @retval EFI_UNSUPPORTED The HostAddress cannot be mapped as a common buffer. + @retval EFI_INVALID_PARAMETER One or more parameters are invalid. + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack of resources. + @retval EFI_DEVICE_ERROR The system hardware could not map the requested address. + +**/ +EFI_STATUS +IoMmuMap ( + IN EDKII_IOMMU_PPI *IoMmu, + IN EDKII_IOMMU_OPERATION Operation, + IN VOID *HostAddress, + IN OUT UINTN *NumberOfBytes, + OUT EFI_PHYSICAL_ADDRESS *DeviceAddress, + OUT VOID **Mapping + ); + +/** + Completes the Map() operation and releases any corresponding resources. + + @param IoMmu Pointer to IOMMU PPI. + @param Mapping The mapping value returned from Map(). + +**/ +VOID +IoMmuUnmap ( + IN EDKII_IOMMU_PPI *IoMmu, + IN VOID *Mapping + ); + +/** + Allocates pages that are suitable for an OperationBusMasterCommonBuffer or + OperationBusMasterCommonBuffer64 mapping. + + @param IoMmu Pointer to IOMMU PPI. + @param Pages The number of pages to allocate. + @param HostAddress A pointer to store the base system memory address of the + allocated range. + @param DeviceAddress The resulting map address for the bus master PCI controller to use to + access the hosts HostAddress. + @param Mapping A resulting value to pass to Unmap(). + + @retval EFI_SUCCESS The requested memory pages were allocated. + @retval EFI_UNSUPPORTED Attributes is unsupported. The only legal attribute bits are + MEMORY_WRITE_COMBINE and MEMORY_CACHED. + @retval EFI_INVALID_PARAMETER One or more parameters are invalid. + @retval EFI_OUT_OF_RESOURCES The memory pages could not be allocated. + +**/ +EFI_STATUS +IoMmuAllocateBuffer ( + IN EDKII_IOMMU_PPI *IoMmu, + IN UINTN Pages, + OUT VOID **HostAddress, + OUT EFI_PHYSICAL_ADDRESS *DeviceAddress, + OUT VOID **Mapping + ); + +/** + Frees memory that was allocated with AllocateBuffer(). + + @param IoMmu Pointer to IOMMU PPI. + @param Pages The number of pages to free. + @param HostAddress The base system memory address of the allocated range. + @param Mapping The mapping value returned from Map(). + +**/ +VOID +IoMmuFreeBuffer ( + IN EDKII_IOMMU_PPI *IoMmu, + IN UINTN Pages, + IN VOID *HostAddress, + IN VOID *Mapping + ); + +/** + Initialize IOMMU. + + @param IoMmu Pointer to pointer to IOMMU PPI. + +**/ +VOID +IoMmuInit ( + OUT EDKII_IOMMU_PPI **IoMmu + ); + +#endif diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciPei.inf b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciPei.inf new file mode 100644 index 00000000..2dc41932 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciPei.inf @@ -0,0 +1,67 @@ +## @file +# The EhcPeim driver is responsible for managing EHCI host controller at PEI phase. +# +# It produces gPeiUsb2HostControllerPpiGuid based on gPeiUsbControllerPpiGuid +# which is used to enable recovery function from USB Drivers. +# +# Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.<BR> +# Copyright (c) Microsoft Corporation.<BR> +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = EhciPei + MODULE_UNI_FILE = EhciPei.uni + FILE_GUID = BAB4F20F-0981-4b5f-A047-6EF83BEEAB3C + MODULE_TYPE = PEIM + VERSION_STRING = 1.0 + + ENTRY_POINT = EhcPeimEntry + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 EBC +# + +[Sources] + EhcPeim.c + EhcPeim.h + EhciUrb.c + EhciSched.c + UsbHcMem.c + EhciReg.h + EhciSched.h + EhciUrb.h + UsbHcMem.h + DmaMem.c + + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + + +[LibraryClasses] + IoLib + TimerLib + BaseLib + BaseMemoryLib + PeimEntryPoint + PeiServicesLib + + +[Ppis] + gPeiUsb2HostControllerPpiGuid ## PRODUCES + gPeiUsbControllerPpiGuid ## CONSUMES + gEdkiiIoMmuPpiGuid ## CONSUMES + gEfiEndOfPeiSignalPpiGuid ## CONSUMES + +[Depex] + gEfiPeiMemoryDiscoveredPpiGuid AND gPeiUsbControllerPpiGuid + +[UserExtensions.TianoCore."ExtraFiles"] + EhciPeiExtra.uni diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciPei.uni b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciPei.uni new file mode 100644 index 00000000..798a8cc0 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciPei.uni @@ -0,0 +1,17 @@ +// /** @file
+// The EhcPeim driver is responsible for managing EHCI host controller at PEI phase.
+//
+// It produces 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
+//
+// **/
+
+
+#string STR_MODULE_ABSTRACT #language en-US "Responsible for managing EHCI host controllers at the PEI phase"
+
+#string STR_MODULE_DESCRIPTION #language en-US "It produces gPeiUsb2HostControllerPpiGuid based on gPeiUsbControllerPpiGuid, which is used to enable recovery function from USB Drivers."
+
diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciPeiExtra.uni b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciPeiExtra.uni new file mode 100644 index 00000000..cf53e805 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciPeiExtra.uni @@ -0,0 +1,14 @@ +// /** @file
+// EhciPei Localized Strings and Content
+//
+// Copyright (c) 2013 - 2018, Intel Corporation. All rights reserved.<BR>
+//
+// SPDX-License-Identifier: BSD-2-Clause-Patent
+//
+// **/
+
+#string STR_PROPERTIES_MODULE_NAME
+#language en-US
+"EHCI PEI Module for Recovery"
+
+
diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciReg.h b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciReg.h new file mode 100644 index 00000000..ee6ea6a2 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciReg.h @@ -0,0 +1,303 @@ +/** @file +Private Header file for Usb Host Controller PEIM + +Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.<BR> + +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _EFI_EHCI_REG_H_ +#define _EFI_EHCI_REG_H_ + + + +// +// Capability register offset +// +#define EHC_CAPLENGTH_OFFSET 0 // Capability register length offset +#define EHC_HCSPARAMS_OFFSET 0x04 // Structural Parameters 04-07h +#define EHC_HCCPARAMS_OFFSET 0x08 // Capability parameters offset + +// +// Capability register bit definition +// +#define HCSP_NPORTS 0x0F // Number of root hub port +#define HCCP_64BIT 0x01 // 64-bit addressing capability + +// +// Operational register offset +// +#define EHC_USBCMD_OFFSET 0x0 // USB command register offset +#define EHC_USBSTS_OFFSET 0x04 // Statue register offset +#define EHC_USBINTR_OFFSET 0x08 // USB interrutp offset +#define EHC_FRINDEX_OFFSET 0x0C // Frame index offset +#define EHC_CTRLDSSEG_OFFSET 0x10 // Control data structure segment offset +#define EHC_FRAME_BASE_OFFSET 0x14 // Frame list base address offset +#define EHC_ASYNC_HEAD_OFFSET 0x18 // Next asynchronous list address offset +#define EHC_CONFIG_FLAG_OFFSET 0x40 // Configure flag register offset +#define EHC_PORT_STAT_OFFSET 0x44 // Port status/control offset + +#define EHC_FRAME_LEN 1024 + +// +// Register bit definition +// +#define CONFIGFLAG_ROUTE_EHC 0x01 // Route port to EHC + +#define USBCMD_RUN 0x01 // Run/stop +#define USBCMD_RESET 0x02 // Start the host controller reset +#define USBCMD_ENABLE_PERIOD 0x10 // Enable periodic schedule +#define USBCMD_ENABLE_ASYNC 0x20 // Enable asynchronous schedule +#define USBCMD_IAAD 0x40 // Interrupt on async advance doorbell + +#define USBSTS_IAA 0x20 // Interrupt on async advance +#define USBSTS_PERIOD_ENABLED 0x4000 // Periodic schedule status +#define USBSTS_ASYNC_ENABLED 0x8000 // Asynchronous schedule status +#define USBSTS_HALT 0x1000 // Host controller halted +#define USBSTS_SYS_ERROR 0x10 // Host system error +#define USBSTS_INTACK_MASK 0x003F // Mask for the interrupt ACK, the WC + // (write clean) bits in USBSTS register + +#define PORTSC_CONN 0x01 // Current Connect Status +#define PORTSC_CONN_CHANGE 0x02 // Connect Status Change +#define PORTSC_ENABLED 0x04 // Port Enable / Disable +#define PORTSC_ENABLE_CHANGE 0x08 // Port Enable / Disable Change +#define PORTSC_OVERCUR 0x10 // Over current Active +#define PORTSC_OVERCUR_CHANGE 0x20 // Over current Change +#define PORSTSC_RESUME 0x40 // Force Port Resume +#define PORTSC_SUSPEND 0x80 // Port Suspend State +#define PORTSC_RESET 0x100 // Port Reset +#define PORTSC_LINESTATE_K 0x400 // Line Status K-state +#define PORTSC_LINESTATE_J 0x800 // Line Status J-state +#define PORTSC_POWER 0x1000 // Port Power +#define PORTSC_OWNER 0x2000 // Port Owner +#define PORTSC_CHANGE_MASK 0x2A // Mask of the port change bits, + // they are WC (write clean) +// +// PCI Configuration Registers +// +#define EHC_BAR_INDEX 0 // how many bytes away from USB_BASE to 0x10 + +#define EHC_LINK_TERMINATED(Link) (((Link) & 0x01) != 0) + +#define EHC_ADDR(High, QhHw32) \ + ((VOID *) (UINTN) (LShiftU64 ((High), 32) | ((QhHw32) & 0xFFFFFFF0))) + +#define EHCI_IS_DATAIN(EndpointAddr) EHC_BIT_IS_SET((EndpointAddr), 0x80) + +// +// Structure to map the hardware port states to the +// UEFI's port states. +// +typedef struct { + UINT16 HwState; + UINT16 UefiState; +} USB_PORT_STATE_MAP; + +// +// Ehci Data and Ctrl Structures +// +#pragma pack(1) +typedef struct { + UINT8 Pi; + UINT8 SubClassCode; + UINT8 BaseCode; +} USB_CLASSC; +#pragma pack() + + +/** + 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 + ) +; + +/** + 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 + ) +; + +/** + 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 + ) +; + +/** + Stop the legacy USB SMI support. + + @param Ehc The EHCI device. + +**/ +VOID +EhcClearLegacySupport ( + IN PEI_USB2_HC_DEV *Ehc + ) +; + +/** + 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 + ) +; + +/** + Clear all the interrutp status bits, these bits + are Write-Clean. + + @param Ehc The EHCI device. + +**/ +VOID +EhcAckAllInterrupt ( + IN PEI_USB2_HC_DEV *Ehc + ) +; + +/** + 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 + ) +; + +/** + 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 + ) +; + +/** + 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 + ) +; + +/** + 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 + ) +; + +/** + 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 + ) +; + +/** + 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 + ) +; + +#endif diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c new file mode 100644 index 00000000..d0b4c42d --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c @@ -0,0 +1,463 @@ +/** @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> +Copyright (c) Microsoft Corporation.<BR> + +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "EhcPeim.h" + +/** + Create helper QTD/QH for the EHCI device. + + @param Ehc The EHCI device. + + @retval EFI_OUT_OF_RESOURCES Failed to allocate resource for helper QTD/QH. + @retval EFI_SUCCESS Helper QH/QTD are created. + +**/ +EFI_STATUS +EhcCreateHelpQ ( + IN PEI_USB2_HC_DEV *Ehc + ) +{ + USB_ENDPOINT Ep; + PEI_EHC_QH *Qh; + QH_HW *QhHw; + PEI_EHC_QTD *Qtd; + + // + // Create an inactive Qtd to terminate the short packet read. + // + Qtd = EhcCreateQtd (Ehc, NULL, 0, QTD_PID_INPUT, 0, 64); + + if (Qtd == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Qtd->QtdHw.Status = QTD_STAT_HALTED; + Ehc->ShortReadStop = Qtd; + + // + // Create a QH to act as the EHC reclamation header. + // Set the header to loopback to itself. + // + Ep.DevAddr = 0; + Ep.EpAddr = 1; + Ep.Direction = EfiUsbDataIn; + Ep.DevSpeed = EFI_USB_SPEED_HIGH; + Ep.MaxPacket = 64; + Ep.HubAddr = 0; + Ep.HubPort = 0; + Ep.Toggle = 0; + Ep.Type = EHC_BULK_TRANSFER; + Ep.PollRate = 1; + + Qh = EhcCreateQh (Ehc, &Ep); + + if (Qh == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + QhHw = &Qh->QhHw; + QhHw->HorizonLink = QH_LINK (QhHw, EHC_TYPE_QH, FALSE); + QhHw->Status = QTD_STAT_HALTED; + QhHw->ReclaimHead = 1; + Ehc->ReclaimHead = Qh; + + // + // Create a dummy QH to act as the terminator for periodical schedule + // + Ep.EpAddr = 2; + Ep.Type = EHC_INT_TRANSFER_SYNC; + + Qh = EhcCreateQh (Ehc, &Ep); + + if (Qh == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Qh->QhHw.Status = QTD_STAT_HALTED; + Ehc->PeriodOne = Qh; + + return EFI_SUCCESS; +} + +/** + Initialize the schedule data structure such as frame list. + + @param Ehc The EHCI device to init schedule data for. + + @retval EFI_OUT_OF_RESOURCES Failed to allocate resource to init schedule data. + @retval EFI_SUCCESS The schedule data is initialized. + +**/ +EFI_STATUS +EhcInitSched ( + IN PEI_USB2_HC_DEV *Ehc + ) +{ + VOID *Buf; + EFI_PHYSICAL_ADDRESS PhyAddr; + VOID *Map; + UINTN Index; + UINT32 *Desc; + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS PciAddr; + + // + // First initialize the periodical schedule data: + // 1. Allocate and map the memory for the frame list + // 2. Create the help QTD/QH + // 3. Initialize the frame entries + // 4. Set the frame list register + // + // + // The Frame List ocupies 4K bytes, + // and must be aligned on 4-Kbyte boundaries. + // + Status = IoMmuAllocateBuffer ( + Ehc->IoMmu, + 1, + &Buf, + &PhyAddr, + &Map + ); + + if (EFI_ERROR (Status) || (Buf == NULL)) { + return EFI_OUT_OF_RESOURCES; + } + + Ehc->PeriodFrame = Buf; + Ehc->PeriodFrameMap = Map; + Ehc->High32bitAddr = EHC_HIGH_32BIT (PhyAddr); + + // + // Init memory pool management then create the helper + // QTD/QH. If failed, previously allocated resources + // will be freed by EhcFreeSched + // + Ehc->MemPool = UsbHcInitMemPool ( + Ehc, + EHC_BIT_IS_SET (Ehc->HcCapParams, HCCP_64BIT), + Ehc->High32bitAddr + ); + + if (Ehc->MemPool == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = EhcCreateHelpQ (Ehc); + + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Initialize the frame list entries then set the registers + // + Desc = (UINT32 *) Ehc->PeriodFrame; + PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Ehc->PeriodOne, sizeof (PEI_EHC_QH)); + for (Index = 0; Index < EHC_FRAME_LEN; Index++) { + Desc[Index] = QH_LINK (PciAddr, EHC_TYPE_QH, FALSE); + } + + EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, EHC_LOW_32BIT (PhyAddr)); + + // + // Second initialize the asynchronous schedule: + // Only need to set the AsynListAddr register to + // the reclamation header + // + PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Ehc->ReclaimHead, sizeof (PEI_EHC_QH)); + EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, EHC_LOW_32BIT (PciAddr)); + return EFI_SUCCESS; +} + +/** + Free the schedule data. It may be partially initialized. + + @param Ehc The EHCI device. + +**/ +VOID +EhcFreeSched ( + IN PEI_USB2_HC_DEV *Ehc + ) +{ + EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, 0); + EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, 0); + + if (Ehc->PeriodOne != NULL) { + UsbHcFreeMem (Ehc, Ehc->MemPool, Ehc->PeriodOne, sizeof (PEI_EHC_QH)); + Ehc->PeriodOne = NULL; + } + + if (Ehc->ReclaimHead != NULL) { + UsbHcFreeMem (Ehc, Ehc->MemPool, Ehc->ReclaimHead, sizeof (PEI_EHC_QH)); + Ehc->ReclaimHead = NULL; + } + + if (Ehc->ShortReadStop != NULL) { + UsbHcFreeMem (Ehc, Ehc->MemPool, Ehc->ShortReadStop, sizeof (PEI_EHC_QTD)); + Ehc->ShortReadStop = NULL; + } + + if (Ehc->MemPool != NULL) { + UsbHcFreeMemPool (Ehc, Ehc->MemPool); + Ehc->MemPool = NULL; + } + + if (Ehc->PeriodFrame != NULL) { + IoMmuFreeBuffer (Ehc->IoMmu, 1, Ehc->PeriodFrame, Ehc->PeriodFrameMap); + Ehc->PeriodFrame = NULL; + } +} + +/** + Link the queue head to the asynchronous schedule list. + UEFI only supports one CTRL/BULK transfer at a time + due to its interfaces. This simplifies the AsynList + management: A reclamation header is always linked to + the AsyncListAddr, the only active QH is appended to it. + + @param Ehc The EHCI device. + @param Qh The queue head to link. + +**/ +VOID +EhcLinkQhToAsync ( + IN PEI_USB2_HC_DEV *Ehc, + IN PEI_EHC_QH *Qh + ) +{ + PEI_EHC_QH *Head; + + // + // Append the queue head after the reclaim header, then + // fix the hardware visiable parts (EHCI R1.0 page 72). + // ReclaimHead is always linked to the EHCI's AsynListAddr. + // + Head = Ehc->ReclaimHead; + + Qh->NextQh = Head->NextQh; + Head->NextQh = Qh; + + Qh->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE);; + Head->QhHw.HorizonLink = QH_LINK (Qh, EHC_TYPE_QH, FALSE); +} + +/** + Unlink a queue head from the asynchronous schedule list. + Need to synchronize with hardware. + + @param Ehc The EHCI device. + @param Qh The queue head to unlink. + +**/ +VOID +EhcUnlinkQhFromAsync ( + IN PEI_USB2_HC_DEV *Ehc, + IN PEI_EHC_QH *Qh + ) +{ + PEI_EHC_QH *Head; + + ASSERT (Ehc->ReclaimHead->NextQh == Qh); + + // + // Remove the QH from reclamation head, then update the hardware + // visiable part: Only need to loopback the ReclaimHead. The Qh + // is pointing to ReclaimHead (which is staill in the list). + // + Head = Ehc->ReclaimHead; + + Head->NextQh = Qh->NextQh; + Qh->NextQh = NULL; + + Head->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE); + + // + // Set and wait the door bell to synchronize with the hardware + // + EhcSetAndWaitDoorBell (Ehc, EHC_GENERIC_TIMEOUT); + + return; +} + +/** + Check the URB's execution result and update the URB's + result accordingly. + + @param Ehc The EHCI device. + @param Urb The URB to check result. + + @retval TRUE URB transfer is finialized. + @retval FALSE URB transfer is not finialized. + +**/ +BOOLEAN +EhcCheckUrbResult ( + IN PEI_USB2_HC_DEV *Ehc, + IN PEI_URB *Urb + ) +{ + EFI_LIST_ENTRY *Entry; + PEI_EHC_QTD *Qtd; + QTD_HW *QtdHw; + UINT8 State; + BOOLEAN Finished; + + ASSERT ((Ehc != NULL) && (Urb != NULL) && (Urb->Qh != NULL)); + + Finished = TRUE; + Urb->Completed = 0; + + Urb->Result = EFI_USB_NOERROR; + + if (EhcIsHalt (Ehc) || EhcIsSysError (Ehc)) { + Urb->Result |= EFI_USB_ERR_SYSTEM; + goto ON_EXIT; + } + + BASE_LIST_FOR_EACH (Entry, &Urb->Qh->Qtds) { + Qtd = EFI_LIST_CONTAINER (Entry, PEI_EHC_QTD, QtdList); + QtdHw = &Qtd->QtdHw; + State = (UINT8) QtdHw->Status; + + if (EHC_BIT_IS_SET (State, QTD_STAT_HALTED)) { + // + // EHCI will halt the queue head when met some error. + // If it is halted, the result of URB is finialized. + // + if ((State & QTD_STAT_ERR_MASK) == 0) { + Urb->Result |= EFI_USB_ERR_STALL; + } + + if (EHC_BIT_IS_SET (State, QTD_STAT_BABBLE_ERR)) { + Urb->Result |= EFI_USB_ERR_BABBLE; + } + + if (EHC_BIT_IS_SET (State, QTD_STAT_BUFF_ERR)) { + Urb->Result |= EFI_USB_ERR_BUFFER; + } + + if (EHC_BIT_IS_SET (State, QTD_STAT_TRANS_ERR) && (QtdHw->ErrCnt == 0)) { + Urb->Result |= EFI_USB_ERR_TIMEOUT; + } + + Finished = TRUE; + goto ON_EXIT; + + } else if (EHC_BIT_IS_SET (State, QTD_STAT_ACTIVE)) { + // + // The QTD is still active, no need to check furthur. + // + Urb->Result |= EFI_USB_ERR_NOTEXECUTE; + + Finished = FALSE; + goto ON_EXIT; + + } else { + // + // This QTD is finished OK or met short packet read. Update the + // transfer length if it isn't a setup. + // + if (QtdHw->Pid != QTD_PID_SETUP) { + Urb->Completed += Qtd->DataLen - QtdHw->TotalBytes; + } + + if ((QtdHw->TotalBytes != 0) && (QtdHw->Pid == QTD_PID_INPUT)) { + //EHC_DUMP_QH ((Urb->Qh, "Short packet read", FALSE)); + + // + // Short packet read condition. If it isn't a setup transfer, + // no need to check furthur: the queue head will halt at the + // ShortReadStop. If it is a setup transfer, need to check the + // Status Stage of the setup transfer to get the finial result + // + if (QtdHw->AltNext == QTD_LINK (Ehc->ShortReadStop, FALSE)) { + + Finished = TRUE; + goto ON_EXIT; + } + } + } + } + +ON_EXIT: + // + // Return the data toggle set by EHCI hardware, bulk and interrupt + // transfer will use this to initialize the next transaction. For + // Control transfer, it always start a new data toggle sequence for + // new transfer. + // + // NOTICE: don't move DT update before the loop, otherwise there is + // a race condition that DT is wrong. + // + Urb->DataToggle = (UINT8) Urb->Qh->QhHw.DataToggle; + + return Finished; +} + +/** + Execute the transfer by polling the URB. This is a synchronous operation. + + @param Ehc The EHCI device. + @param Urb The URB to execute. + @param TimeOut The time to wait before abort, in millisecond. + + @retval EFI_DEVICE_ERROR The transfer failed due to transfer error. + @retval EFI_TIMEOUT The transfer failed due to time out. + @retval EFI_SUCCESS The transfer finished OK. + +**/ +EFI_STATUS +EhcExecTransfer ( + IN PEI_USB2_HC_DEV *Ehc, + IN PEI_URB *Urb, + IN UINTN TimeOut + ) +{ + EFI_STATUS Status; + UINTN Index; + UINTN Loop; + BOOLEAN Finished; + BOOLEAN InfiniteLoop; + + Status = EFI_SUCCESS; + Loop = TimeOut * EHC_1_MILLISECOND; + Finished = FALSE; + InfiniteLoop = FALSE; + + // + // If Timeout is 0, then the caller must wait for the function to be completed + // until EFI_SUCCESS or EFI_DEVICE_ERROR is returned. + // + if (TimeOut == 0) { + InfiniteLoop = TRUE; + } + + for (Index = 0; InfiniteLoop || (Index < Loop); Index++) { + Finished = EhcCheckUrbResult (Ehc, Urb); + + if (Finished) { + break; + } + + MicroSecondDelay (EHC_1_MICROSECOND); + } + + if (!Finished) { + Status = EFI_TIMEOUT; + } else if (Urb->Result != EFI_USB_NOERROR) { + Status = EFI_DEVICE_ERROR; + } + + return Status; +} + diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.h b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.h new file mode 100644 index 00000000..060fcc2d --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.h @@ -0,0 +1,93 @@ +/** @file +Private Header file for Usb Host Controller PEIM + +Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.<BR> + +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _EFI_EHCI_SCHED_H_ +#define _EFI_EHCI_SCHED_H_ + +/** + Initialize the schedule data structure such as frame list. + + @param Ehc The EHCI device to init schedule data for. + + @retval EFI_OUT_OF_RESOURCES Failed to allocate resource to init schedule data. + @retval EFI_SUCCESS The schedule data is initialized. + +**/ +EFI_STATUS +EhcInitSched ( + IN PEI_USB2_HC_DEV *Ehc + ) +; + +/** + Free the schedule data. It may be partially initialized. + + @param Ehc The EHCI device. + +**/ +VOID +EhcFreeSched ( + IN PEI_USB2_HC_DEV *Ehc + ) +; + +/** + Link the queue head to the asynchronous schedule list. + UEFI only supports one CTRL/BULK transfer at a time + due to its interfaces. This simplifies the AsynList + management: A reclamation header is always linked to + the AsyncListAddr, the only active QH is appended to it. + + @param Ehc The EHCI device. + @param Qh The queue head to link. + +**/ +VOID +EhcLinkQhToAsync ( + IN PEI_USB2_HC_DEV *Ehc, + IN PEI_EHC_QH *Qh + ) +; + +/** + Unlink a queue head from the asynchronous schedule list. + Need to synchronize with hardware. + + @param Ehc The EHCI device. + @param Qh The queue head to unlink. + +**/ +VOID +EhcUnlinkQhFromAsync ( + IN PEI_USB2_HC_DEV *Ehc, + IN PEI_EHC_QH *Qh + ) +; + +/** + Execute the transfer by polling the URB. This is a synchronous operation. + + @param Ehc The EHCI device. + @param Urb The URB to execute. + @param TimeOut The time to wait before abort, in millisecond. + + @retval EFI_DEVICE_ERROR The transfer failed due to transfer error. + @retval EFI_TIMEOUT The transfer failed due to time out. + @retval EFI_SUCCESS The transfer finished OK. + +**/ +EFI_STATUS +EhcExecTransfer ( + IN PEI_USB2_HC_DEV *Ehc, + IN PEI_URB *Urb, + IN UINTN TimeOut + ) +; + +#endif diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciUrb.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciUrb.c new file mode 100644 index 00000000..e00e0231 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciUrb.c @@ -0,0 +1,627 @@ +/** @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> +Copyright (c) Microsoft Corporation.<BR> + +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "EhcPeim.h" + +/** + Delete a single asynchronous interrupt transfer for + the device and endpoint. + + @param Ehc The EHCI device. + @param Data Current data not associated with a QTD. + @param DataLen The length of the data. + @param PktId Packet ID to use in the QTD. + @param Toggle Data toggle to use in the QTD. + @param MaxPacket Maximu packet length of the endpoint. + + @retval the pointer to the created QTD or NULL if failed to create one. + +**/ +PEI_EHC_QTD * +EhcCreateQtd ( + IN PEI_USB2_HC_DEV *Ehc, + IN UINT8 *Data, + IN UINTN DataLen, + IN UINT8 PktId, + IN UINT8 Toggle, + IN UINTN MaxPacket + ) +{ + PEI_EHC_QTD *Qtd; + QTD_HW *QtdHw; + UINTN Index; + UINTN Len; + UINTN ThisBufLen; + + ASSERT (Ehc != NULL); + + Qtd = UsbHcAllocateMem (Ehc, Ehc->MemPool, sizeof (PEI_EHC_QTD)); + + if (Qtd == NULL) { + return NULL; + } + + Qtd->Signature = EHC_QTD_SIG; + Qtd->Data = Data; + Qtd->DataLen = 0; + + InitializeListHead (&Qtd->QtdList); + + QtdHw = &Qtd->QtdHw; + QtdHw->NextQtd = QTD_LINK (NULL, TRUE); + QtdHw->AltNext = QTD_LINK (NULL, TRUE); + QtdHw->Status = QTD_STAT_ACTIVE; + QtdHw->Pid = PktId; + QtdHw->ErrCnt = QTD_MAX_ERR; + QtdHw->Ioc = 0; + QtdHw->TotalBytes = 0; + QtdHw->DataToggle = Toggle; + + // + // Fill in the buffer points + // + if (Data != NULL) { + Len = 0; + + for (Index = 0; Index <= QTD_MAX_BUFFER; Index++) { + // + // Set the buffer point (Check page 41 EHCI Spec 1.0). No need to + // compute the offset and clear Reserved fields. This is already + // done in the data point. + // + QtdHw->Page[Index] = EHC_LOW_32BIT (Data); + QtdHw->PageHigh[Index] = EHC_HIGH_32BIT (Data); + + ThisBufLen = QTD_BUF_LEN - (EHC_LOW_32BIT (Data) & QTD_BUF_MASK); + + if (Len + ThisBufLen >= DataLen) { + Len = DataLen; + break; + } + + Len += ThisBufLen; + Data += ThisBufLen; + } + + // + // Need to fix the last pointer if the Qtd can't hold all the + // user's data to make sure that the length is in the unit of + // max packets. If it can hold all the data, there is no such + // need. + // + if (Len < DataLen) { + Len = Len - Len % MaxPacket; + } + + QtdHw->TotalBytes = (UINT32) Len; + Qtd->DataLen = Len; + } + + return Qtd; +} + +/** + Initialize the queue head for interrupt transfer, + that is, initialize the following three fields: + 1. SplitXState in the Status field. + 2. Microframe S-mask. + 3. Microframe C-mask. + + @param Ep The queue head's related endpoint. + @param QhHw The queue head to initialize. + +**/ +VOID +EhcInitIntQh ( + IN USB_ENDPOINT *Ep, + IN QH_HW *QhHw + ) +{ + // + // Because UEFI interface can't utilitize an endpoint with + // poll rate faster than 1ms, only need to set one bit in + // the queue head. simple. But it may be changed later. If + // sub-1ms interrupt is supported, need to update the S-Mask + // here + // + if (Ep->DevSpeed == EFI_USB_SPEED_HIGH) { + QhHw->SMask = QH_MICROFRAME_0; + return ; + } + + // + // For low/full speed device, the transfer must go through + // the split transaction. Need to update three fields + // 1. SplitXState in the status + // 2. Microframe S-Mask + // 3. Microframe C-Mask + // UEFI USB doesn't exercise admission control. It simplely + // schedule the high speed transactions in microframe 0, and + // full/low speed transactions at microframe 1. This also + // avoid the use of FSTN. + // + QhHw->SMask = QH_MICROFRAME_1; + QhHw->CMask = QH_MICROFRAME_3 | QH_MICROFRAME_4 | QH_MICROFRAME_5; +} + +/** + Allocate and initialize a EHCI queue head. + + @param Ehci The EHCI device. + @param Ep The endpoint to create queue head for. + + @retval the pointer to the created queue head or NULL if failed to create one. + +**/ +PEI_EHC_QH * +EhcCreateQh ( + IN PEI_USB2_HC_DEV *Ehci, + IN USB_ENDPOINT *Ep + ) +{ + PEI_EHC_QH *Qh; + QH_HW *QhHw; + + Qh = UsbHcAllocateMem (Ehci, Ehci->MemPool, sizeof (PEI_EHC_QH)); + + if (Qh == NULL) { + return NULL; + } + + Qh->Signature = EHC_QH_SIG; + Qh->NextQh = NULL; + Qh->Interval = Ep->PollRate; + + InitializeListHead (&Qh->Qtds); + + QhHw = &Qh->QhHw; + QhHw->HorizonLink = QH_LINK (NULL, 0, TRUE); + QhHw->DeviceAddr = Ep->DevAddr; + QhHw->Inactive = 0; + QhHw->EpNum = Ep->EpAddr; + QhHw->EpSpeed = Ep->DevSpeed; + QhHw->DtCtrl = 0; + QhHw->ReclaimHead = 0; + QhHw->MaxPacketLen = (UINT32) Ep->MaxPacket; + QhHw->CtrlEp = 0; + QhHw->NakReload = QH_NAK_RELOAD; + QhHw->HubAddr = Ep->HubAddr; + QhHw->PortNum = Ep->HubPort; + QhHw->Multiplier = 1; + QhHw->DataToggle = Ep->Toggle; + + if (Ep->DevSpeed != EFI_USB_SPEED_HIGH) { + QhHw->Status |= QTD_STAT_DO_SS; + } + + switch (Ep->Type) { + case EHC_CTRL_TRANSFER: + // + // Special initialization for the control transfer: + // 1. Control transfer initialize data toggle from each QTD + // 2. Set the Control Endpoint Flag (C) for low/full speed endpoint. + // + QhHw->DtCtrl = 1; + + if (Ep->DevSpeed != EFI_USB_SPEED_HIGH) { + QhHw->CtrlEp = 1; + } + break; + + case EHC_INT_TRANSFER_ASYNC: + case EHC_INT_TRANSFER_SYNC: + // + // Special initialization for the interrupt transfer + // to set the S-Mask and C-Mask + // + QhHw->NakReload = 0; + EhcInitIntQh (Ep, QhHw); + break; + + case EHC_BULK_TRANSFER: + if ((Ep->DevSpeed == EFI_USB_SPEED_HIGH) && (Ep->Direction == EfiUsbDataOut)) { + QhHw->Status |= QTD_STAT_DO_PING; + } + + break; + } + + return Qh; +} + +/** + Convert the poll interval from application to that + be used by EHCI interface data structure. Only need + to get the max 2^n that is less than interval. UEFI + can't support high speed endpoint with a interval less + than 8 microframe because interval is specified in + the unit of ms (millisecond). + + @param Interval The interval to convert. + + @retval The converted interval. + +**/ +UINTN +EhcConvertPollRate ( + IN UINTN Interval + ) +{ + UINTN BitCount; + + if (Interval == 0) { + return 1; + } + + // + // Find the index (1 based) of the highest non-zero bit + // + BitCount = 0; + + while (Interval != 0) { + Interval >>= 1; + BitCount++; + } + + return (UINTN)1 << (BitCount - 1); +} + +/** + Free a list of QTDs. + + @param Ehc The EHCI device. + @param Qtds The list head of the QTD. + +**/ +VOID +EhcFreeQtds ( + IN PEI_USB2_HC_DEV *Ehc, + IN EFI_LIST_ENTRY *Qtds + ) +{ + EFI_LIST_ENTRY *Entry; + EFI_LIST_ENTRY *Next; + PEI_EHC_QTD *Qtd; + + BASE_LIST_FOR_EACH_SAFE (Entry, Next, Qtds) { + Qtd = EFI_LIST_CONTAINER (Entry, PEI_EHC_QTD, QtdList); + + RemoveEntryList (&Qtd->QtdList); + UsbHcFreeMem (Ehc, Ehc->MemPool, Qtd, sizeof (PEI_EHC_QTD)); + } +} + +/** + Free an allocated URB. It is possible for it to be partially inited. + + @param Ehc The EHCI device. + @param Urb The URB to free. + +**/ +VOID +EhcFreeUrb ( + IN PEI_USB2_HC_DEV *Ehc, + IN PEI_URB *Urb + ) +{ + if (Urb->RequestPhy != NULL) { + IoMmuUnmap (Ehc->IoMmu, Urb->RequestMap); + } + + if (Urb->DataMap != NULL) { + IoMmuUnmap (Ehc->IoMmu, Urb->DataMap); + } + + if (Urb->Qh != NULL) { + // + // Ensure that this queue head has been unlinked from the + // schedule data structures. Free all the associated QTDs + // + EhcFreeQtds (Ehc, &Urb->Qh->Qtds); + UsbHcFreeMem (Ehc, Ehc->MemPool, Urb->Qh, sizeof (PEI_EHC_QH)); + } +} + +/** + Create a list of QTDs for the URB. + + @param Ehc The EHCI device. + @param Urb The URB to create QTDs for. + + @retval EFI_OUT_OF_RESOURCES Failed to allocate resource for QTD. + @retval EFI_SUCCESS The QTDs are allocated for the URB. + +**/ +EFI_STATUS +EhcCreateQtds ( + IN PEI_USB2_HC_DEV *Ehc, + IN PEI_URB *Urb + ) +{ + USB_ENDPOINT *Ep; + PEI_EHC_QH *Qh; + PEI_EHC_QTD *Qtd; + PEI_EHC_QTD *StatusQtd; + PEI_EHC_QTD *NextQtd; + EFI_LIST_ENTRY *Entry; + UINT32 AlterNext; + UINT8 Toggle; + UINTN Len; + UINT8 Pid; + + ASSERT ((Urb != NULL) && (Urb->Qh != NULL)); + + // + // EHCI follows the alternet next QTD pointer if it meets + // a short read and the AlterNext pointer is valid. UEFI + // EHCI driver should terminate the transfer except the + // control transfer. + // + Toggle = 0; + Qh = Urb->Qh; + Ep = &Urb->Ep; + StatusQtd = NULL; + AlterNext = QTD_LINK (NULL, TRUE); + + if (Ep->Direction == EfiUsbDataIn) { + AlterNext = QTD_LINK (Ehc->ShortReadStop, FALSE); + } + + // + // Build the Setup and status packets for control transfer + // + if (Urb->Ep.Type == EHC_CTRL_TRANSFER) { + Len = sizeof (EFI_USB_DEVICE_REQUEST); + Qtd = EhcCreateQtd (Ehc, Urb->RequestPhy, Len, QTD_PID_SETUP, 0, Ep->MaxPacket); + + if (Qtd == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + InsertTailList (&Qh->Qtds, &Qtd->QtdList); + + // + // Create the status packet now. Set the AlterNext to it. So, when + // EHCI meets a short control read, it can resume at the status stage. + // Use the opposite direction of the data stage, or IN if there is + // no data stage. + // + if (Ep->Direction == EfiUsbDataIn) { + Pid = QTD_PID_OUTPUT; + } else { + Pid = QTD_PID_INPUT; + } + + StatusQtd = EhcCreateQtd (Ehc, NULL, 0, Pid, 1, Ep->MaxPacket); + + if (StatusQtd == NULL) { + goto ON_ERROR; + } + + if (Ep->Direction == EfiUsbDataIn) { + AlterNext = QTD_LINK (StatusQtd, FALSE); + } + + Toggle = 1; + } + + // + // Build the data packets for all the transfers + // + if (Ep->Direction == EfiUsbDataIn) { + Pid = QTD_PID_INPUT; + } else { + Pid = QTD_PID_OUTPUT; + } + + Qtd = NULL; + Len = 0; + + while (Len < Urb->DataLen) { + Qtd = EhcCreateQtd ( + Ehc, + (UINT8 *) Urb->DataPhy + Len, + Urb->DataLen - Len, + Pid, + Toggle, + Ep->MaxPacket + ); + + if (Qtd == NULL) { + goto ON_ERROR; + } + + Qtd->QtdHw.AltNext = AlterNext; + InsertTailList (&Qh->Qtds, &Qtd->QtdList); + + // + // Switch the Toggle bit if odd number of packets are included in the QTD. + // + if (((Qtd->DataLen + Ep->MaxPacket - 1) / Ep->MaxPacket) % 2) { + Toggle = (UINT8) (1 - Toggle); + } + + Len += Qtd->DataLen; + } + + // + // Insert the status packet for control transfer + // + if (Ep->Type == EHC_CTRL_TRANSFER) { + InsertTailList (&Qh->Qtds, &StatusQtd->QtdList); + } + + // + // OK, all the QTDs needed are created. Now, fix the NextQtd point + // + BASE_LIST_FOR_EACH (Entry, &Qh->Qtds) { + Qtd = EFI_LIST_CONTAINER (Entry, PEI_EHC_QTD, QtdList); + + // + // break if it is the last entry on the list + // + if (Entry->ForwardLink == &Qh->Qtds) { + break; + } + + NextQtd = EFI_LIST_CONTAINER (Entry->ForwardLink, PEI_EHC_QTD, QtdList); + Qtd->QtdHw.NextQtd = QTD_LINK (NextQtd, FALSE); + } + + // + // Link the QTDs to the queue head + // + NextQtd = EFI_LIST_CONTAINER (Qh->Qtds.ForwardLink, PEI_EHC_QTD, QtdList); + Qh->QhHw.NextQtd = QTD_LINK (NextQtd, FALSE); + return EFI_SUCCESS; + +ON_ERROR: + EhcFreeQtds (Ehc, &Qh->Qtds); + return EFI_OUT_OF_RESOURCES; +} + +/** + Create a new URB and its associated QTD. + + @param Ehc The EHCI device. + @param DevAddr The device address. + @param EpAddr Endpoint addrress & its direction. + @param DevSpeed The device speed. + @param Toggle Initial data toggle to use. + @param MaxPacket The max packet length of the endpoint. + @param Hub The transaction translator to use. + @param Type The transaction type. + @param Request The standard USB request for control transfer. + @param Data The user data to transfer. + @param DataLen The length of data buffer. + @param Callback The function to call when data is transferred. + @param Context The context to the callback. + @param Interval The interval for interrupt transfer. + + @retval the pointer to the created URB or NULL. + +**/ +PEI_URB * +EhcCreateUrb ( + IN PEI_USB2_HC_DEV *Ehc, + IN UINT8 DevAddr, + IN UINT8 EpAddr, + IN UINT8 DevSpeed, + IN UINT8 Toggle, + IN UINTN MaxPacket, + IN EFI_USB2_HC_TRANSACTION_TRANSLATOR *Hub, + IN UINTN Type, + IN EFI_USB_DEVICE_REQUEST *Request, + IN VOID *Data, + IN UINTN DataLen, + IN EFI_ASYNC_USB_TRANSFER_CALLBACK Callback, + IN VOID *Context, + IN UINTN Interval + ) +{ + USB_ENDPOINT *Ep; + EFI_PHYSICAL_ADDRESS PhyAddr; + EDKII_IOMMU_OPERATION MapOp; + EFI_STATUS Status; + UINTN Len; + PEI_URB *Urb; + VOID *Map; + + Map = NULL; + + Urb = Ehc->Urb; + Urb->Signature = EHC_URB_SIG; + InitializeListHead (&Urb->UrbList); + + Ep = &Urb->Ep; + Ep->DevAddr = DevAddr; + Ep->EpAddr = (UINT8) (EpAddr & 0x0F); + Ep->Direction = (((EpAddr & 0x80) != 0) ? EfiUsbDataIn : EfiUsbDataOut); + Ep->DevSpeed = DevSpeed; + Ep->MaxPacket = MaxPacket; + + Ep->HubAddr = 0; + Ep->HubPort = 0; + + if (DevSpeed != EFI_USB_SPEED_HIGH) { + ASSERT (Hub != NULL); + + Ep->HubAddr = Hub->TranslatorHubAddress; + Ep->HubPort = Hub->TranslatorPortNumber; + } + + Ep->Toggle = Toggle; + Ep->Type = Type; + Ep->PollRate = EhcConvertPollRate (Interval); + + Urb->Request = Request; + Urb->Data = Data; + Urb->DataLen = DataLen; + Urb->Callback = Callback; + Urb->Context = Context; + Urb->Qh = EhcCreateQh (Ehc, &Urb->Ep); + + if (Urb->Qh == NULL) { + goto ON_ERROR; + } + + Urb->RequestPhy = NULL; + Urb->RequestMap = NULL; + Urb->DataPhy = NULL; + Urb->DataMap = NULL; + + // + // Map the request and user data + // + if (Request != NULL) { + Len = sizeof (EFI_USB_DEVICE_REQUEST); + MapOp = EdkiiIoMmuOperationBusMasterRead; + Status = IoMmuMap (Ehc->IoMmu, MapOp, Request, &Len, &PhyAddr, &Map); + + if (EFI_ERROR (Status) || (Len != sizeof (EFI_USB_DEVICE_REQUEST))) { + goto ON_ERROR; + } + + Urb->RequestPhy = (VOID *) ((UINTN) PhyAddr); + Urb->RequestMap = Map; + } + + if (Data != NULL) { + Len = DataLen; + + if (Ep->Direction == EfiUsbDataIn) { + MapOp = EdkiiIoMmuOperationBusMasterWrite; + } else { + MapOp = EdkiiIoMmuOperationBusMasterRead; + } + + Status = IoMmuMap (Ehc->IoMmu, MapOp, Data, &Len, &PhyAddr, &Map); + + if (EFI_ERROR (Status) || (Len != DataLen)) { + goto ON_ERROR; + } + + Urb->DataPhy = (VOID *) ((UINTN) PhyAddr); + Urb->DataMap = Map; + } + + Status = EhcCreateQtds (Ehc, Urb); + + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + + return Urb; + +ON_ERROR: + EhcFreeUrb (Ehc, Urb); + return NULL; +} diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciUrb.h b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciUrb.h new file mode 100644 index 00000000..a432d1d7 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciUrb.h @@ -0,0 +1,324 @@ +/** @file +Private Header file for Usb Host Controller PEIM + +Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.<BR> + +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _EFI_EHCI_URB_H_ +#define _EFI_EHCI_URB_H_ + +typedef struct _PEI_EHC_QTD PEI_EHC_QTD; +typedef struct _PEI_EHC_QH PEI_EHC_QH; +typedef struct _PEI_URB PEI_URB; + +#define EHC_CTRL_TRANSFER 0x01 +#define EHC_BULK_TRANSFER 0x02 +#define EHC_INT_TRANSFER_SYNC 0x04 +#define EHC_INT_TRANSFER_ASYNC 0x08 + +#define EHC_QTD_SIG SIGNATURE_32 ('U', 'S', 'B', 'T') +#define EHC_QH_SIG SIGNATURE_32 ('U', 'S', 'B', 'H') +#define EHC_URB_SIG SIGNATURE_32 ('U', 'S', 'B', 'R') + +// +// Hardware related bit definitions +// +#define EHC_TYPE_ITD 0x00 +#define EHC_TYPE_QH 0x02 +#define EHC_TYPE_SITD 0x04 +#define EHC_TYPE_FSTN 0x06 + +#define QH_NAK_RELOAD 3 +#define QH_HSHBW_MULTI 1 + +#define QTD_MAX_ERR 3 +#define QTD_PID_OUTPUT 0x00 +#define QTD_PID_INPUT 0x01 +#define QTD_PID_SETUP 0x02 + +#define QTD_STAT_DO_OUT 0 +#define QTD_STAT_DO_SS 0 +#define QTD_STAT_DO_PING 0x01 +#define QTD_STAT_DO_CS 0x02 +#define QTD_STAT_TRANS_ERR 0x08 +#define QTD_STAT_BABBLE_ERR 0x10 +#define QTD_STAT_BUFF_ERR 0x20 +#define QTD_STAT_HALTED 0x40 +#define QTD_STAT_ACTIVE 0x80 +#define QTD_STAT_ERR_MASK (QTD_STAT_TRANS_ERR | QTD_STAT_BABBLE_ERR | QTD_STAT_BUFF_ERR) + +#define QTD_MAX_BUFFER 4 +#define QTD_BUF_LEN 4096 +#define QTD_BUF_MASK 0x0FFF + +#define QH_MICROFRAME_0 0x01 +#define QH_MICROFRAME_1 0x02 +#define QH_MICROFRAME_2 0x04 +#define QH_MICROFRAME_3 0x08 +#define QH_MICROFRAME_4 0x10 +#define QH_MICROFRAME_5 0x20 +#define QH_MICROFRAME_6 0x40 +#define QH_MICROFRAME_7 0x80 + +#define USB_ERR_SHORT_PACKET 0x200 + +// +// Fill in the hardware link point: pass in a EHC_QH/QH_HW +// pointer to QH_LINK; A EHC_QTD/QTD_HW pointer to QTD_LINK +// +#define QH_LINK(Addr, Type, Term) \ + ((UINT32) ((EHC_LOW_32BIT (Addr) & 0xFFFFFFE0) | (Type) | ((Term) ? 1 : 0))) + +#define QTD_LINK(Addr, Term) QH_LINK((Addr), 0, (Term)) + +// +// The defination of EHCI hardware used data structure for +// little endian architecture. The QTD and QH structures +// are required to be 32 bytes aligned. Don't add members +// to the head of the associated software strucuture. +// +#pragma pack(1) +typedef struct { + UINT32 NextQtd; + UINT32 AltNext; + + UINT32 Status : 8; + UINT32 Pid : 2; + UINT32 ErrCnt : 2; + UINT32 CurPage : 3; + UINT32 Ioc : 1; + UINT32 TotalBytes : 15; + UINT32 DataToggle : 1; + + UINT32 Page[5]; + UINT32 PageHigh[5]; +} QTD_HW; + +typedef struct { + UINT32 HorizonLink; + // + // Endpoint capabilities/Characteristics DWord 1 and DWord 2 + // + UINT32 DeviceAddr : 7; + UINT32 Inactive : 1; + UINT32 EpNum : 4; + UINT32 EpSpeed : 2; + UINT32 DtCtrl : 1; + UINT32 ReclaimHead : 1; + UINT32 MaxPacketLen : 11; + UINT32 CtrlEp : 1; + UINT32 NakReload : 4; + + UINT32 SMask : 8; + UINT32 CMask : 8; + UINT32 HubAddr : 7; + UINT32 PortNum : 7; + UINT32 Multiplier : 2; + + // + // Transaction execution overlay area + // + UINT32 CurQtd; + UINT32 NextQtd; + UINT32 AltQtd; + + UINT32 Status : 8; + UINT32 Pid : 2; + UINT32 ErrCnt : 2; + UINT32 CurPage : 3; + UINT32 Ioc : 1; + UINT32 TotalBytes : 15; + UINT32 DataToggle : 1; + + UINT32 Page[5]; + UINT32 PageHigh[5]; +} QH_HW; +#pragma pack() + + +// +// Endpoint address and its capabilities +// +typedef struct _USB_ENDPOINT { + UINT8 DevAddr; + UINT8 EpAddr; // Endpoint address, no direction encoded in + EFI_USB_DATA_DIRECTION Direction; + UINT8 DevSpeed; + UINTN MaxPacket; + UINT8 HubAddr; + UINT8 HubPort; + UINT8 Toggle; // Data toggle, not used for control transfer + UINTN Type; + UINTN PollRate; // Polling interval used by EHCI +} USB_ENDPOINT; + +// +// Software QTD strcture, this is used to manage all the +// QTD generated from a URB. Don't add fields before QtdHw. +// +struct _PEI_EHC_QTD { + QTD_HW QtdHw; + UINT32 Signature; + EFI_LIST_ENTRY QtdList; // The list of QTDs to one end point + UINT8 *Data; // Buffer of the original data + UINTN DataLen; // Original amount of data in this QTD +}; + + + +// +// Software QH structure. All three different transaction types +// supported by UEFI USB, that is the control/bulk/interrupt +// transfers use the queue head and queue token strcuture. +// +// Interrupt QHs are linked to periodic frame list in the reversed +// 2^N tree. Each interrupt QH is linked to the list starting at +// frame 0. There is a dummy interrupt QH linked to each frame as +// a sentinental whose polling interval is 1. Synchronous interrupt +// transfer is linked after this dummy QH. +// +// For control/bulk transfer, only synchronous (in the sense of UEFI) +// transfer is supported. A dummy QH is linked to EHCI AsyncListAddr +// as the reclamation header. New transfer is inserted after this QH. +// +struct _PEI_EHC_QH { + QH_HW QhHw; + UINT32 Signature; + PEI_EHC_QH *NextQh; // The queue head pointed to by horizontal link + EFI_LIST_ENTRY Qtds; // The list of QTDs to this queue head + UINTN Interval; +}; + +// +// URB (Usb Request Block) contains information for all kinds of +// usb requests. +// +struct _PEI_URB { + UINT32 Signature; + EFI_LIST_ENTRY UrbList; + + // + // Transaction information + // + USB_ENDPOINT Ep; + EFI_USB_DEVICE_REQUEST *Request; // Control transfer only + VOID *RequestPhy; // Address of the mapped request + VOID *RequestMap; + VOID *Data; + UINTN DataLen; + VOID *DataPhy; // Address of the mapped user data + VOID *DataMap; + EFI_ASYNC_USB_TRANSFER_CALLBACK Callback; + VOID *Context; + + // + // Schedule data + // + PEI_EHC_QH *Qh; + + // + // Transaction result + // + UINT32 Result; + UINTN Completed; // completed data length + UINT8 DataToggle; +}; + +/** + Delete a single asynchronous interrupt transfer for + the device and endpoint. + + @param Ehc The EHCI device. + @param Data Current data not associated with a QTD. + @param DataLen The length of the data. + @param PktId Packet ID to use in the QTD. + @param Toggle Data toggle to use in the QTD. + @param MaxPacket Maximu packet length of the endpoint. + + @retval the pointer to the created QTD or NULL if failed to create one. + +**/ +PEI_EHC_QTD * +EhcCreateQtd ( + IN PEI_USB2_HC_DEV *Ehc, + IN UINT8 *Data, + IN UINTN DataLen, + IN UINT8 PktId, + IN UINT8 Toggle, + IN UINTN MaxPacket + ) +; + +/** + Allocate and initialize a EHCI queue head. + + @param Ehci The EHCI device. + @param Ep The endpoint to create queue head for. + + @retval the pointer to the created queue head or NULL if failed to create one. + +**/ +PEI_EHC_QH * +EhcCreateQh ( + IN PEI_USB2_HC_DEV *Ehci, + IN USB_ENDPOINT *Ep + ) +; + +/** + Free an allocated URB. It is possible for it to be partially inited. + + @param Ehc The EHCI device. + @param Urb The URB to free. + +**/ +VOID +EhcFreeUrb ( + IN PEI_USB2_HC_DEV *Ehc, + IN PEI_URB *Urb + ) +; + +/** + Create a new URB and its associated QTD. + + @param Ehc The EHCI device. + @param DevAddr The device address. + @param EpAddr Endpoint addrress & its direction. + @param DevSpeed The device speed. + @param Toggle Initial data toggle to use. + @param MaxPacket The max packet length of the endpoint. + @param Hub The transaction translator to use. + @param Type The transaction type. + @param Request The standard USB request for control transfer. + @param Data The user data to transfer. + @param DataLen The length of data buffer. + @param Callback The function to call when data is transferred. + @param Context The context to the callback. + @param Interval The interval for interrupt transfer. + + @retval the pointer to the created URB or NULL. + +**/ +PEI_URB * +EhcCreateUrb ( + IN PEI_USB2_HC_DEV *Ehc, + IN UINT8 DevAddr, + IN UINT8 EpAddr, + IN UINT8 DevSpeed, + IN UINT8 Toggle, + IN UINTN MaxPacket, + IN EFI_USB2_HC_TRANSACTION_TRANSLATOR *Hub, + IN UINTN Type, + IN EFI_USB_DEVICE_REQUEST *Request, + IN VOID *Data, + IN UINTN DataLen, + IN EFI_ASYNC_USB_TRANSFER_CALLBACK Callback, + IN VOID *Context, + IN UINTN Interval + ) +; +#endif diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/UsbHcMem.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/UsbHcMem.c new file mode 100644 index 00000000..fd472c53 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/UsbHcMem.c @@ -0,0 +1,517 @@ +/** @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" + +/** + Allocate a block of memory to be used by the buffer pool. + + @param Ehc The EHCI device. + @param Pool The buffer pool to allocate memory for. + @param Pages How many pages to allocate. + + @return The allocated memory block or NULL if failed. + +**/ +USBHC_MEM_BLOCK * +UsbHcAllocMemBlock ( + IN PEI_USB2_HC_DEV *Ehc, + IN USBHC_MEM_POOL *Pool, + IN UINTN Pages + ) +{ + USBHC_MEM_BLOCK *Block; + VOID *BufHost; + VOID *Mapping; + EFI_PHYSICAL_ADDRESS MappedAddr; + EFI_STATUS Status; + UINTN PageNumber; + EFI_PHYSICAL_ADDRESS TempPtr; + + Mapping = NULL; + PageNumber = sizeof(USBHC_MEM_BLOCK)/PAGESIZE +1; + Status = PeiServicesAllocatePages ( + EfiBootServicesCode, + PageNumber, + &TempPtr + ); + + if (EFI_ERROR (Status)) { + return NULL; + } + ZeroMem ((VOID *)(UINTN)TempPtr, PageNumber*EFI_PAGE_SIZE); + + // + // each bit in the bit array represents USBHC_MEM_UNIT + // bytes of memory in the memory block. + // + ASSERT (USBHC_MEM_UNIT * 8 <= EFI_PAGE_SIZE); + + Block = (USBHC_MEM_BLOCK*)(UINTN)TempPtr; + Block->BufLen = EFI_PAGES_TO_SIZE (Pages); + Block->BitsLen = Block->BufLen / (USBHC_MEM_UNIT * 8); + + PageNumber = (Block->BitsLen)/PAGESIZE +1; + Status = PeiServicesAllocatePages ( + EfiBootServicesCode, + PageNumber, + &TempPtr + ); + + if (EFI_ERROR (Status)) { + return NULL; + } + ZeroMem ((VOID *)(UINTN)TempPtr, PageNumber*EFI_PAGE_SIZE); + + Block->Bits = (UINT8 *)(UINTN)TempPtr; + + Status = IoMmuAllocateBuffer ( + Ehc->IoMmu, + Pages, + (VOID **) &BufHost, + &MappedAddr, + &Mapping + ); + if (EFI_ERROR (Status)) { + return NULL; + } + ZeroMem (BufHost, Pages*EFI_PAGE_SIZE); + + // + // Check whether the data structure used by the host controller + // should be restricted into the same 4G + // + if (Pool->Check4G && (Pool->Which4G != USB_HC_HIGH_32BIT (MappedAddr))) { + return NULL; + } + + Block->BufHost = BufHost; + Block->Buf = (UINT8 *) ((UINTN) MappedAddr); + Block->Mapping = Mapping; + Block->Next = NULL; + + return Block; + +} + +/** + Free the memory block from the memory pool. + + @param Ehc The EHCI device. + @param Pool The memory pool to free the block from. + @param Block The memory block to free. + +**/ +VOID +UsbHcFreeMemBlock ( + IN PEI_USB2_HC_DEV *Ehc, + IN USBHC_MEM_POOL *Pool, + IN USBHC_MEM_BLOCK *Block + ) +{ + ASSERT ((Pool != NULL) && (Block != NULL)); + + IoMmuFreeBuffer (Ehc->IoMmu, EFI_SIZE_TO_PAGES (Block->BufLen), Block->BufHost, Block->Mapping); +} + +/** + Alloc some memory from the block. + + @param Block The memory block to allocate memory from. + @param Units Number of memory units to allocate. + + @return The pointer to the allocated memory. If couldn't allocate the needed memory, + the return value is NULL. + +**/ +VOID * +UsbHcAllocMemFromBlock ( + IN USBHC_MEM_BLOCK *Block, + IN UINTN Units + ) +{ + UINTN Byte; + UINT8 Bit; + UINTN StartByte; + UINT8 StartBit; + UINTN Available; + UINTN Count; + + ASSERT ((Block != 0) && (Units != 0)); + + StartByte = 0; + StartBit = 0; + Available = 0; + + for (Byte = 0, Bit = 0; Byte < Block->BitsLen;) { + // + // If current bit is zero, the corresponding memory unit is + // available, otherwise we need to restart our searching. + // Available counts the consective number of zero bit. + // + if (!USB_HC_BIT_IS_SET (Block->Bits[Byte], Bit)) { + Available++; + + if (Available >= Units) { + break; + } + + NEXT_BIT (Byte, Bit); + + } else { + NEXT_BIT (Byte, Bit); + + Available = 0; + StartByte = Byte; + StartBit = Bit; + } + } + + if (Available < Units) { + return NULL; + } + + // + // Mark the memory as allocated + // + Byte = StartByte; + Bit = StartBit; + + for (Count = 0; Count < Units; Count++) { + ASSERT (!USB_HC_BIT_IS_SET (Block->Bits[Byte], Bit)); + + Block->Bits[Byte] = (UINT8) (Block->Bits[Byte] | (UINT8) USB_HC_BIT (Bit)); + NEXT_BIT (Byte, Bit); + } + + return Block->Buf + (StartByte * 8 + StartBit) * USBHC_MEM_UNIT; +} + +/** + Calculate the corresponding pci bus address according to the Mem parameter. + + @param Pool The memory pool of the host controller. + @param Mem The pointer to host memory. + @param Size The size of the memory region. + + @return the pci memory address +**/ +EFI_PHYSICAL_ADDRESS +UsbHcGetPciAddressForHostMem ( + IN USBHC_MEM_POOL *Pool, + IN VOID *Mem, + IN UINTN Size + ) +{ + USBHC_MEM_BLOCK *Head; + USBHC_MEM_BLOCK *Block; + UINTN AllocSize; + EFI_PHYSICAL_ADDRESS PhyAddr; + UINTN Offset; + + Head = Pool->Head; + AllocSize = USBHC_MEM_ROUND (Size); + + if (Mem == NULL) { + return 0; + } + + for (Block = Head; Block != NULL; Block = Block->Next) { + // + // scan the memory block list for the memory block that + // completely contains the allocated memory. + // + if ((Block->BufHost <= (UINT8 *) Mem) && (((UINT8 *) Mem + AllocSize) <= (Block->BufHost + Block->BufLen))) { + break; + } + } + + ASSERT ((Block != NULL)); + // + // calculate the pci memory address for host memory address. + // + Offset = (UINT8 *)Mem - Block->BufHost; + PhyAddr = (EFI_PHYSICAL_ADDRESS)(UINTN) (Block->Buf + Offset); + return PhyAddr; +} + +/** + Insert the memory block to the pool's list of the blocks. + + @param Head The head of the memory pool's block list. + @param Block The memory block to insert. + +**/ +VOID +UsbHcInsertMemBlockToPool ( + IN USBHC_MEM_BLOCK *Head, + IN USBHC_MEM_BLOCK *Block + ) +{ + ASSERT ((Head != NULL) && (Block != NULL)); + Block->Next = Head->Next; + Head->Next = Block; +} + +/** + Is the memory block empty? + + @param Block The memory block to check. + + @retval TRUE The memory block is empty. + @retval FALSE The memory block isn't empty. + +**/ +BOOLEAN +UsbHcIsMemBlockEmpty ( + IN USBHC_MEM_BLOCK *Block + ) +{ + UINTN Index; + + + for (Index = 0; Index < Block->BitsLen; Index++) { + if (Block->Bits[Index] != 0) { + return FALSE; + } + } + + return TRUE; +} + + +/** + Initialize the memory management pool for the host controller. + + @param Ehc The EHCI device. + @param Check4G Whether the host controller requires allocated memory. + from one 4G address space. + @param Which4G The 4G memory area each memory allocated should be from. + + @retval EFI_SUCCESS The memory pool is initialized. + @retval EFI_OUT_OF_RESOURCE Fail to init the memory pool. + +**/ +USBHC_MEM_POOL * +UsbHcInitMemPool ( + IN PEI_USB2_HC_DEV *Ehc, + IN BOOLEAN Check4G, + IN UINT32 Which4G + ) +{ + USBHC_MEM_POOL *Pool; + UINTN PageNumber; + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS TempPtr; + + PageNumber = sizeof(USBHC_MEM_POOL)/PAGESIZE +1; + Status = PeiServicesAllocatePages ( + EfiBootServicesCode, + PageNumber, + &TempPtr + ); + + if (EFI_ERROR (Status)) { + return NULL; + } + ZeroMem ((VOID *)(UINTN)TempPtr, PageNumber*EFI_PAGE_SIZE); + + Pool = (USBHC_MEM_POOL *) ((UINTN) TempPtr); + + Pool->Check4G = Check4G; + Pool->Which4G = Which4G; + Pool->Head = UsbHcAllocMemBlock (Ehc, Pool, USBHC_MEM_DEFAULT_PAGES); + + if (Pool->Head == NULL) { + Pool = NULL; + } + + return Pool; +} + +/** + Release the memory management pool. + + @param Ehc The EHCI device. + @param Pool The USB memory pool to free. + + @retval EFI_DEVICE_ERROR Fail to free the memory pool. + @retval EFI_SUCCESS The memory pool is freed. + +**/ +EFI_STATUS +UsbHcFreeMemPool ( + IN PEI_USB2_HC_DEV *Ehc, + IN USBHC_MEM_POOL *Pool + ) +{ + USBHC_MEM_BLOCK *Block; + + ASSERT (Pool->Head != NULL); + + // + // Unlink all the memory blocks from the pool, then free them. + // + for (Block = Pool->Head->Next; Block != NULL; Block = Block->Next) { + UsbHcFreeMemBlock (Ehc, Pool, Block); + } + + UsbHcFreeMemBlock (Ehc, Pool, Pool->Head); + + return EFI_SUCCESS; +} + +/** + Allocate some memory from the host controller's memory pool + which can be used to communicate with host controller. + + @param Ehc The EHCI device. + @param Pool The host controller's memory pool. + @param Size Size of the memory to allocate. + + @return The allocated memory or NULL. + +**/ +VOID * +UsbHcAllocateMem ( + IN PEI_USB2_HC_DEV *Ehc, + IN USBHC_MEM_POOL *Pool, + IN UINTN Size + ) +{ + USBHC_MEM_BLOCK *Head; + USBHC_MEM_BLOCK *Block; + USBHC_MEM_BLOCK *NewBlock; + VOID *Mem; + UINTN AllocSize; + UINTN Pages; + + Mem = NULL; + AllocSize = USBHC_MEM_ROUND (Size); + Head = Pool->Head; + ASSERT (Head != NULL); + + // + // First check whether current memory blocks can satisfy the allocation. + // + for (Block = Head; Block != NULL; Block = Block->Next) { + Mem = UsbHcAllocMemFromBlock (Block, AllocSize / USBHC_MEM_UNIT); + + if (Mem != NULL) { + ZeroMem (Mem, Size); + break; + } + } + + if (Mem != NULL) { + return Mem; + } + + // + // Create a new memory block if there is not enough memory + // in the pool. If the allocation size is larger than the + // default page number, just allocate a large enough memory + // block. Otherwise allocate default pages. + // + if (AllocSize > EFI_PAGES_TO_SIZE (USBHC_MEM_DEFAULT_PAGES)) { + Pages = EFI_SIZE_TO_PAGES (AllocSize) + 1; + } else { + Pages = USBHC_MEM_DEFAULT_PAGES; + } + NewBlock = UsbHcAllocMemBlock (Ehc,Pool, Pages); + + if (NewBlock == NULL) { + return NULL; + } + + // + // Add the new memory block to the pool, then allocate memory from it + // + UsbHcInsertMemBlockToPool (Head, NewBlock); + Mem = UsbHcAllocMemFromBlock (NewBlock, AllocSize / USBHC_MEM_UNIT); + + if (Mem != NULL) { + ZeroMem (Mem, Size); + } + + return Mem; +} + +/** + Free the allocated memory back to the memory pool. + + @param Ehc The EHCI device. + @param Pool The memory pool of the host controller. + @param Mem The memory to free. + @param Size The size of the memory to free. + +**/ +VOID +UsbHcFreeMem ( + IN PEI_USB2_HC_DEV *Ehc, + IN USBHC_MEM_POOL *Pool, + IN VOID *Mem, + IN UINTN Size + ) +{ + USBHC_MEM_BLOCK *Head; + USBHC_MEM_BLOCK *Block; + UINT8 *ToFree; + UINTN AllocSize; + UINTN Byte; + UINTN Bit; + UINTN Count; + + Head = Pool->Head; + AllocSize = USBHC_MEM_ROUND (Size); + ToFree = (UINT8 *) Mem; + + for (Block = Head; Block != NULL; Block = Block->Next) { + // + // scan the memory block list for the memory block that + // completely contains the memory to free. + // + if ((Block->Buf <= ToFree) && ((ToFree + AllocSize) <= (Block->Buf + Block->BufLen))) { + // + // compute the start byte and bit in the bit array + // + Byte = ((ToFree - Block->Buf) / USBHC_MEM_UNIT) / 8; + Bit = ((ToFree - Block->Buf) / USBHC_MEM_UNIT) % 8; + + // + // reset associated bits in bit array + // + for (Count = 0; Count < (AllocSize / USBHC_MEM_UNIT); Count++) { + ASSERT (USB_HC_BIT_IS_SET (Block->Bits[Byte], Bit)); + + Block->Bits[Byte] = (UINT8) (Block->Bits[Byte] ^ USB_HC_BIT (Bit)); + NEXT_BIT (Byte, Bit); + } + + break; + } + } + + // + // If Block == NULL, it means that the current memory isn't + // in the host controller's pool. This is critical because + // the caller has passed in a wrong memory point + // + ASSERT (Block != NULL); + + // + // Release the current memory block if it is empty and not the head + // + if ((Block != Head) && UsbHcIsMemBlockEmpty (Block)) { + UsbHcFreeMemBlock (Ehc, Pool, Block); + } + + return ; +} diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/UsbHcMem.h b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/UsbHcMem.h new file mode 100644 index 00000000..fc05521e --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/UsbHcMem.h @@ -0,0 +1,86 @@ +/** @file +Private Header file for Usb Host Controller PEIM + +Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.<BR> + +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _EFI_EHCI_MEM_H_ +#define _EFI_EHCI_MEM_H_ + +#include <Uefi.h> +#include <IndustryStandard/Pci22.h> + +#define USB_HC_BIT(a) ((UINTN)(1 << (a))) + +#define USB_HC_BIT_IS_SET(Data, Bit) \ + ((BOOLEAN)(((Data) & USB_HC_BIT(Bit)) == USB_HC_BIT(Bit))) + +#define USB_HC_HIGH_32BIT(Addr64) \ + ((UINT32)(RShiftU64((UINTN)(Addr64), 32) & 0XFFFFFFFF)) + +typedef struct _USBHC_MEM_BLOCK USBHC_MEM_BLOCK; + +struct _USBHC_MEM_BLOCK { + UINT8 *Bits; // Bit array to record which unit is allocated + UINTN BitsLen; + UINT8 *Buf; + UINT8 *BufHost; + UINTN BufLen; // Memory size in bytes + VOID *Mapping; + USBHC_MEM_BLOCK *Next; +}; + +// +// USBHC_MEM_POOL is used to manage the memory used by USB +// host controller. EHCI requires the control memory and transfer +// data to be on the same 4G memory. +// +typedef struct _USBHC_MEM_POOL { + BOOLEAN Check4G; + UINT32 Which4G; + USBHC_MEM_BLOCK *Head; +} USBHC_MEM_POOL; + +// +// Memory allocation unit, must be 2^n, n>4 +// +#define USBHC_MEM_UNIT 64 + +#define USBHC_MEM_UNIT_MASK (USBHC_MEM_UNIT - 1) +#define USBHC_MEM_DEFAULT_PAGES 16 + +#define USBHC_MEM_ROUND(Len) (((Len) + USBHC_MEM_UNIT_MASK) & (~USBHC_MEM_UNIT_MASK)) + +// +// Advance the byte and bit to the next bit, adjust byte accordingly. +// +#define NEXT_BIT(Byte, Bit) \ + do { \ + (Bit)++; \ + if ((Bit) > 7) { \ + (Byte)++; \ + (Bit) = 0; \ + } \ + } while (0) + + +/** + Calculate the corresponding pci bus address according to the Mem parameter. + + @param Pool The memory pool of the host controller. + @param Mem The pointer to host memory. + @param Size The size of the memory region. + + @return the pci memory address +**/ +EFI_PHYSICAL_ADDRESS +UsbHcGetPciAddressForHostMem ( + IN USBHC_MEM_POOL *Pool, + IN VOID *Mem, + IN UINTN Size + ); + +#endif |