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