/** @file Usb3 Debug Port library instance Copyright (c) 2013 - 2019, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Usb3DebugPortLibInternal.h" extern EFI_SMRAM_DESCRIPTOR mSmramCheckRanges[MAX_SMRAM_RANGE]; extern UINTN mSmramCheckRangeCount; extern BOOLEAN mUsb3InSmm; extern UINT64 mUsb3MmioSize; extern BOOLEAN mUsb3GetCapSuccess; GUID gUsb3DbgGuid = USB3_DBG_GUID; USB3_DEBUG_PORT_CONTROLLER mUsb3DebugPort; USB3_DEBUG_PORT_INSTANCE *mUsb3Instance = NULL; EFI_PCI_IO_PROTOCOL *mUsb3PciIo = NULL; /** Creates a named event that can be signaled. This function creates an event using NotifyTpl, NoifyFunction. If Name is NULL, then ASSERT(). If NotifyTpl is not a legal TPL value, then ASSERT(). If NotifyFunction is NULL, then ASSERT(). @param Name Supplies the GUID name of the event. @param NotifyTpl Supplies the task priority level of the event notifications. @param NotifyFunction Supplies the function to notify when the event is signaled. @param Event A pointer to the event created. @retval EFI_SUCCESS A named event was created. @retval EFI_OUT_OF_RESOURCES There are not enough resource to create the named event. **/ EFI_STATUS EFIAPI Usb3NamedEventListen ( IN CONST EFI_GUID *Name, IN EFI_TPL NotifyTpl, IN EFI_EVENT_NOTIFY NotifyFunction, IN EFI_EVENT *Event ) { EFI_STATUS Status; VOID *RegistrationLocal; ASSERT (Name != NULL); ASSERT (NotifyFunction != NULL); ASSERT (NotifyTpl <= TPL_HIGH_LEVEL); // // Create event // Status = gBS->CreateEvent ( EVT_NOTIFY_SIGNAL, NotifyTpl, NotifyFunction, NULL, Event ); ASSERT_EFI_ERROR (Status); // // Register for an installation of protocol interface // Status = gBS->RegisterProtocolNotify ( (EFI_GUID *) Name, *Event, &RegistrationLocal ); ASSERT_EFI_ERROR (Status); return Status; } /** USB3 map one DMA buffer. @param PciIo Pointer to PciIo for USB3 debug port. @param Address DMA buffer address to be mapped. @param NumberOfBytes Number of bytes to be mapped. **/ VOID Usb3MapOneDmaBuffer ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN EFI_PHYSICAL_ADDRESS Address, IN UINTN NumberOfBytes ) { EFI_STATUS Status; VOID *HostAddress; EFI_PHYSICAL_ADDRESS DeviceAddress; VOID *Mapping; HostAddress = (VOID *) (UINTN) Address; Status = PciIo->Map ( PciIo, EfiPciIoOperationBusMasterCommonBuffer, HostAddress, &NumberOfBytes, &DeviceAddress, &Mapping ); ASSERT_EFI_ERROR (Status); ASSERT (DeviceAddress == ((EFI_PHYSICAL_ADDRESS) (UINTN) HostAddress)); } /** USB3 map DMA buffers. @param Instance Pointer to USB3 debug port instance. @param PciIo Pointer to PciIo for USB3 debug port. **/ VOID Usb3MapDmaBuffers ( IN USB3_DEBUG_PORT_INSTANCE *Instance, IN EFI_PCI_IO_PROTOCOL *PciIo ) { Usb3MapOneDmaBuffer ( PciIo, Instance->Urb.Data, XHC_DEBUG_PORT_DATA_LENGTH ); Usb3MapOneDmaBuffer ( PciIo, Instance->TransferRingIn.RingSeg0, sizeof (TRB_TEMPLATE) * TR_RING_TRB_NUMBER ); Usb3MapOneDmaBuffer ( PciIo, Instance->TransferRingOut.RingSeg0, sizeof (TRB_TEMPLATE) * TR_RING_TRB_NUMBER ); Usb3MapOneDmaBuffer ( PciIo, Instance->EventRing.EventRingSeg0, sizeof (TRB_TEMPLATE) * EVENT_RING_TRB_NUMBER ); Usb3MapOneDmaBuffer ( PciIo, Instance->EventRing.ERSTBase, sizeof (EVENT_RING_SEG_TABLE_ENTRY) * ERST_NUMBER ); Usb3MapOneDmaBuffer ( PciIo, Instance->DebugCapabilityContext, sizeof (XHC_DC_CONTEXT) ); Usb3MapOneDmaBuffer ( PciIo, ((XHC_DC_CONTEXT *) (UINTN) Instance->DebugCapabilityContext)->DbcInfoContext.String0DescAddress, STRING0_DESC_LEN + MANU_DESC_LEN + PRODUCT_DESC_LEN + SERIAL_DESC_LEN ); } /** Invoke a notification event @param[in] Event Event whose notification function is being invoked. @param[in] Context The pointer to the notification function's context, which is implementation-dependent. **/ VOID EFIAPI Usb3DxeSmmReadyToLockNotify ( IN EFI_EVENT Event, IN VOID *Context ) { ASSERT (mUsb3Instance != NULL); // // For the case that the USB3 debug port instance and DMA buffers are // from PEI HOB with IOMMU enabled. // Reinitialize USB3 debug port with granted DXE DMA buffer accessible // by SMM environment. // InitializeUsb3DebugPort (mUsb3Instance); SaveUsb3InstanceAddress (mUsb3Instance); gBS->CloseEvent (Event); } /** USB3 get IOMMU protocol. @return Pointer to IOMMU protocol. **/ EDKII_IOMMU_PROTOCOL * Usb3GetIoMmu ( VOID ) { EFI_STATUS Status; EDKII_IOMMU_PROTOCOL *IoMmu; IoMmu = NULL; Status = gBS->LocateProtocol ( &gEdkiiIoMmuProtocolGuid, NULL, (VOID **) &IoMmu ); if (!EFI_ERROR (Status) && (IoMmu != NULL)) { return IoMmu; } return NULL; } /** Invoke a notification event @param[in] Event Event whose notification function is being invoked. @param[in] Context The pointer to the notification function's context, which is implementation-dependent. **/ VOID EFIAPI Usb3PciIoNotify ( IN EFI_EVENT Event, IN VOID *Context ) { EFI_STATUS Status; UINTN PciIoHandleCount; EFI_HANDLE *PciIoHandleBuffer; UINTN Index; EFI_PCI_IO_PROTOCOL *PciIo; UINTN PciSegment; UINTN PciBusNumber; UINTN PciDeviceNumber; UINTN PciFunctionNumber; EFI_EVENT SmmReadyToLockEvent; Status = gBS->LocateHandleBuffer ( ByProtocol, &gEfiPciIoProtocolGuid, NULL, &PciIoHandleCount, &PciIoHandleBuffer ); if (!EFI_ERROR (Status) && (PciIoHandleBuffer != NULL) && (PciIoHandleCount != 0)) { for (Index = 0; Index < PciIoHandleCount; Index++) { Status = gBS->HandleProtocol ( PciIoHandleBuffer[Index], &gEfiPciIoProtocolGuid, (VOID **) &PciIo ); ASSERT_EFI_ERROR (Status); Status = PciIo->GetLocation (PciIo, &PciSegment, &PciBusNumber, &PciDeviceNumber, &PciFunctionNumber); ASSERT_EFI_ERROR (Status); if ((PciBusNumber == mUsb3DebugPort.PciAddress.Bus) && (PciDeviceNumber == mUsb3DebugPort.PciAddress.Device) && (PciFunctionNumber == mUsb3DebugPort.PciAddress.Function)) { // // Found the PciIo for USB3 debug port. // ASSERT (mUsb3Instance != NULL); if (Usb3GetIoMmu () != NULL) { Usb3MapDmaBuffers (mUsb3Instance, PciIo); if (mUsb3Instance->FromHob) { mUsb3PciIo = PciIo; Usb3NamedEventListen ( &gEfiDxeSmmReadyToLockProtocolGuid, TPL_NOTIFY, Usb3DxeSmmReadyToLockNotify, &SmmReadyToLockEvent ); } } gBS->CloseEvent (Event); break; } } gBS->FreePool (PciIoHandleBuffer); } } /** Return XHCI MMIO base address. **/ EFI_PHYSICAL_ADDRESS GetXhciBaseAddress ( VOID ) { UINT8 Bus; UINT8 Device; UINT8 Function; EFI_PHYSICAL_ADDRESS Address; UINT32 Low; UINT32 High; if (mUsb3DebugPort.Controller == 0) { mUsb3DebugPort.Controller = GetUsb3DebugPortController(); } Bus = mUsb3DebugPort.PciAddress.Bus; Device = mUsb3DebugPort.PciAddress.Device; Function = mUsb3DebugPort.PciAddress.Function; Low = PciRead32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET)); High = PciRead32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET + 4)); Address = (EFI_PHYSICAL_ADDRESS) (LShiftU64 ((UINT64) High, 32) | Low); // // Mask other parts which are not part of base address // Address &= XHCI_BASE_ADDRESS_64_BIT_MASK; return Address; } /** Return XHCI debug instance address. **/ USB3_DEBUG_PORT_INSTANCE * GetUsb3DebugPortInstance ( VOID ) { USB3_DEBUG_PORT_INSTANCE *Instance; EFI_PHYSICAL_ADDRESS XhcMmioBase; UINT64 CapabilityPointer; UINT32 Capability; BOOLEAN Flag; UINT8 Bus; UINT8 Device; UINT8 Function; UINT16 Command; USB3_DEBUG_PORT_CONTROLLER UsbDebugPort; Instance = NULL; XhcMmioBase = GetXhciBaseAddress (); if ((XhcMmioBase == 0) || (XhcMmioBase == XHCI_BASE_ADDRESS_64_BIT_MASK)) { return NULL; } if (mUsb3Instance != NULL) { FixUsb3InstanceResource (mUsb3Instance, XhcMmioBase); return mUsb3Instance; } Command = GetXhciPciCommand (); UsbDebugPort.Controller = GetUsb3DebugPortController(); Bus = UsbDebugPort.PciAddress.Bus; Device = UsbDebugPort.PciAddress.Device; Function = UsbDebugPort.PciAddress.Function; // // Set Command Register // if ((Command & EFI_PCI_COMMAND_MEMORY_SPACE) == 0) { PciWrite16(PCI_LIB_ADDRESS(Bus, Device, Function, PCI_COMMAND_OFFSET), Command | EFI_PCI_COMMAND_MEMORY_SPACE); PciRead16(PCI_LIB_ADDRESS(Bus, Device, Function, PCI_COMMAND_OFFSET)); } // // Calculate capability offset from HCCPARAMS [16:31], in 32-bit words // CapabilityPointer = XhcMmioBase + (MmioRead32 ((UINTN)(XhcMmioBase + XHC_HCCPARAMS_OFFSET)) >> 16) * 4; // // Search XHCI debug capability // Flag = FALSE; Capability = MmioRead32 ((UINTN)CapabilityPointer); while (TRUE) { if ((Capability & XHC_CAPABILITY_ID_MASK) == PCI_CAPABILITY_ID_DEBUG_PORT) { Flag = TRUE; break; } if ((((Capability & XHC_NEXT_CAPABILITY_MASK) >> 8) & XHC_CAPABILITY_ID_MASK) == 0) { // // Reach the end of list, quit // break; } CapabilityPointer += ((Capability & XHC_NEXT_CAPABILITY_MASK) >> 8) * 4; Capability = MmioRead32 ((UINTN)CapabilityPointer); } if (Flag) { Instance = (USB3_DEBUG_PORT_INSTANCE *)(UINTN) MmioRead32 ((UINTN) (CapabilityPointer + XHC_DC_DCDDI2)); if (Instance != NULL) { FixUsb3InstanceResource (Instance, XhcMmioBase); } } // // Restore Command Register // PciWrite16(PCI_LIB_ADDRESS (Bus, Device, Function, PCI_COMMAND_OFFSET), Command); return Instance; } /** Initialize USB3 debug port. This method invokes various internal functions to facilitate detection and initialization of USB3 debug port. @retval RETURN_SUCCESS The USB3 debug port was initialized. **/ RETURN_STATUS EFIAPI USB3Initialize ( VOID ) { // // Leave it empty, we assume PEI phase already do initialization // return RETURN_SUCCESS; } /** Initialize USB3 debug port. This method invokes various internal functions to facilitate detection and initialization of USB3 debug port. @retval RETURN_SUCCESS The serial device was initialized. **/ RETURN_STATUS EFIAPI USB3InitializeReal ( VOID ) { USB3_DEBUG_PORT_INSTANCE UsbDbg; USB3_DEBUG_PORT_INSTANCE *Instance; EFI_PHYSICAL_ADDRESS Address; EFI_STATUS Status; EFI_EVENT Event; if ((gST == NULL) || (gBS == NULL)) { // // gST and gBS have not been initialized yet // return EFI_DEVICE_ERROR; } Status = EfiGetSystemConfigurationTable (&gUsb3DbgGuid, (VOID **) &mUsb3Instance); if (!EFI_ERROR (Status)) { goto Done; } // // It is first time to run DXE instance, copy Instance from Hob to ACPINvs // NOTE: Hob is not ready at this time, so copy it from XHCI register. // Instance = GetUsb3DebugPortInstance (); if (Instance == NULL) { // // Initialize USB debug // SetMem (&UsbDbg, sizeof(UsbDbg), 0); DiscoverUsb3DebugPort (&UsbDbg); if (UsbDbg.DebugSupport) { InitializeUsb3DebugPort (&UsbDbg); } Instance = &UsbDbg; } Address = SIZE_4GB; Status = gBS->AllocatePages ( AllocateMaxAddress, EfiACPIMemoryNVS, EFI_SIZE_TO_PAGES (sizeof (USB3_DEBUG_PORT_INSTANCE)), &Address ); if (EFI_ERROR (Status)) { return Status; } CopyMem ( (VOID *)(UINTN)Address, Instance, sizeof (USB3_DEBUG_PORT_INSTANCE) ); mUsb3Instance = (USB3_DEBUG_PORT_INSTANCE *)(UINTN)Address; Status = gBS->InstallConfigurationTable (&gUsb3DbgGuid, mUsb3Instance); if (EFI_ERROR (Status)) { return Status; } if (mUsb3Instance->DebugSupport) { SaveUsb3InstanceAddress (mUsb3Instance); } Done: if ((mUsb3Instance != NULL) && mUsb3Instance->Ready && (mUsb3Instance->PciIoEvent == 0)) { Status = Usb3NamedEventListen ( &gEfiPciIoProtocolGuid, TPL_NOTIFY, Usb3PciIoNotify, &Event ); if (!EFI_ERROR (Status)) { mUsb3Instance->PciIoEvent = (EFI_PHYSICAL_ADDRESS) (UINTN) Event; } } return RETURN_SUCCESS; } /** Calculate the size of XHCI MMIO space. @retval TURE The XHCI MMIO is in SMRAM ranges. @retval FALSE The XHCI MMIO is out of SMRAM ranges. **/ UINT64 CalculateMmioSize ( VOID ) { UINT8 Bus; UINT8 Device; UINT8 Function; UINT32 Value; UINT32 Mask; UINT64 MmioSize; UINT16 Command; USB3_DEBUG_PORT_CONTROLLER UsbDebugPort; EFI_PHYSICAL_ADDRESS XhcMmioBase; UsbDebugPort.Controller = GetUsb3DebugPortController(); Bus = UsbDebugPort.PciAddress.Bus; Device = UsbDebugPort.PciAddress.Device; Function = UsbDebugPort.PciAddress.Function; Mask = 0xFFFFFFF0; MmioSize = 0; XhcMmioBase = GetXhciBaseAddress (); // // Disable MSE // Command = PciRead16 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_COMMAND_OFFSET)); PciWrite16 (PCI_LIB_ADDRESS (Bus, Device, Function, PCI_COMMAND_OFFSET), Command & ~(EFI_PCI_COMMAND_MEMORY_SPACE)); // // Get Mmio Size // PciWrite32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET), 0xFFFFFFFF); Value = PciRead32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET)); switch (Value & 0x07) { case 0x0: // // Memory space: anywhere in 32 bit address space // MmioSize = (~(Value & Mask)) + 1; break; case 0x4: // // Memory space: anywhere in 64 bit address space // MmioSize = Value & Mask; PciWrite32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET + 4), 0xFFFFFFFF); Value = PciRead32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET + 4)); // // Fix the length to support some spefic 64 bit BAR // Value |= ((UINT32)(-1) << HighBitSet32 (Value)); // // Calculate the size of 64bit bar // MmioSize |= LShiftU64 ((UINT64) Value, 32); MmioSize = (~(MmioSize)) + 1; break; default: // // Unknown BAR type // MmioSize = (~(Value & Mask)) + 1; break; }; // // Restore MMIO address // PciWrite32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET), (UINT32)XhcMmioBase); PciWrite32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET + 4), (UINT32) (XhcMmioBase >> 32)); PciWrite16 (PCI_LIB_ADDRESS (Bus, Device, Function, PCI_COMMAND_OFFSET), Command | EFI_PCI_COMMAND_MEMORY_SPACE); return MmioSize; } /** The constructor function initialize USB3 debug port. @param ImageHandle The firmware allocated handle for the EFI image. @param SystemTable A pointer to the EFI System Table. @retval EFI_SUCCESS The constructor always returns EFI_SUCCESS. **/ EFI_STATUS EFIAPI Usb3DebugPortLibDxeConstructor ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_SMM_BASE2_PROTOCOL *SmmBase; EFI_SMM_ACCESS2_PROTOCOL *SmmAccess; UINTN Size; EFI_STATUS Status; // // Do real initialization here, because we need copy data from Hob to ACPINvs. // We must do it in constructor because it depends on UefiBootServicesTableLib. // if (FeaturePcdGet (PcdUsb3DebugFeatureEnable)) { USB3InitializeReal (); } mUsb3MmioSize = CalculateMmioSize (); if (gBS != NULL) { SmmBase = NULL; Status = gBS->LocateProtocol (&gEfiSmmBase2ProtocolGuid, NULL, (VOID **)&SmmBase); if (!EFI_ERROR (Status)) { SmmBase->InSmm(SmmBase, &mUsb3InSmm); } if (mUsb3InSmm) { // // Get SMRAM information // SmmAccess = NULL; Status = gBS->LocateProtocol (&gEfiSmmAccess2ProtocolGuid, NULL, (VOID **)&SmmAccess); if (!EFI_ERROR (Status)) { Size = sizeof (mSmramCheckRanges); Status = SmmAccess->GetCapabilities (SmmAccess, &Size, mSmramCheckRanges); if (!EFI_ERROR (Status)) { mSmramCheckRangeCount = Size / sizeof (EFI_SMRAM_DESCRIPTOR); } } } } return EFI_SUCCESS; } /** The destructor function. @param ImageHandle The firmware allocated handle for the EFI image. @param SystemTable A pointer to the EFI System Table. @retval EFI_SUCCESS The destructor always returns EFI_SUCCESS. **/ EFI_STATUS EFIAPI Usb3DebugPortLibDxeDestructor ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { if ((mUsb3Instance != NULL) && (mUsb3Instance->PciIoEvent != 0)) { // // Close the event created. // gBS->CloseEvent ((EFI_EVENT) (UINTN) mUsb3Instance->PciIoEvent); mUsb3Instance->PciIoEvent = 0; } return EFI_SUCCESS; } /** Allocates pages that are suitable for an OperationBusMasterCommonBuffer or OperationBusMasterCommonBuffer64 mapping. @param PciIo Pointer to PciIo for USB3 debug port. @param Pages The number of pages to allocate. @param Address A pointer to store the base system memory address of the allocated range. @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 Usb3AllocateDmaBuffer ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINTN Pages, OUT VOID **Address ) { EFI_STATUS Status; *Address = NULL; Status = PciIo->AllocateBuffer ( PciIo, AllocateAnyPages, EfiRuntimeServicesData, Pages, Address, 0 ); if (!EFI_ERROR (Status)) { Usb3MapOneDmaBuffer ( PciIo, (EFI_PHYSICAL_ADDRESS) (UINTN) *Address, EFI_PAGES_TO_SIZE (Pages) ); } return Status; } /** Allocate aligned memory for XHC's usage. @param BufferSize The size, in bytes, of the Buffer. @return A pointer to the allocated buffer or NULL if allocation fails. **/ VOID* AllocateAlignBuffer ( IN UINTN BufferSize ) { VOID *Buf; EFI_PHYSICAL_ADDRESS Address; EFI_STATUS Status; Buf = NULL; if (gBS != NULL) { if (mUsb3PciIo != NULL) { Usb3AllocateDmaBuffer ( mUsb3PciIo, EFI_SIZE_TO_PAGES (BufferSize), &Buf ); } else { Address = 0xFFFFFFFF; Status = gBS->AllocatePages ( AllocateMaxAddress, EfiACPIMemoryNVS, EFI_SIZE_TO_PAGES (BufferSize), &Address ); if (!EFI_ERROR (Status)) { Buf = (VOID *)(UINTN)Address; } } } return Buf; } /** Check whether AllocatePages in permanent memory is ready. @retval TRUE AllocatePages in permanent memory is ready. @retval FALSE AllocatePages in permanent memory is not ready. **/ BOOLEAN IsAllocatePagesReady ( VOID ) { if (gBS != NULL) { return TRUE; } return FALSE; }