audk/IntelSiliconPkg/Feature/VTd/IntelVTdDxe/BmDma.c

539 lines
18 KiB
C

/** @file
BmDma related function
Copyright (c) 2017 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "DmaProtection.h"
// TBD: May make it a policy
#define DMA_MEMORY_TOP MAX_UINTN
//#define DMA_MEMORY_TOP 0x0000000001FFFFFFULL
#define MAP_HANDLE_INFO_SIGNATURE SIGNATURE_32 ('H', 'M', 'A', 'P')
typedef struct {
UINT32 Signature;
LIST_ENTRY Link;
EFI_HANDLE DeviceHandle;
UINT64 IoMmuAccess;
} MAP_HANDLE_INFO;
#define MAP_HANDLE_INFO_FROM_LINK(a) CR (a, MAP_HANDLE_INFO, Link, MAP_HANDLE_INFO_SIGNATURE)
#define MAP_INFO_SIGNATURE SIGNATURE_32 ('D', 'M', 'A', 'P')
typedef struct {
UINT32 Signature;
LIST_ENTRY Link;
EDKII_IOMMU_OPERATION Operation;
UINTN NumberOfBytes;
UINTN NumberOfPages;
EFI_PHYSICAL_ADDRESS HostAddress;
EFI_PHYSICAL_ADDRESS DeviceAddress;
LIST_ENTRY HandleList;
} MAP_INFO;
#define MAP_INFO_FROM_LINK(a) CR (a, MAP_INFO, Link, MAP_INFO_SIGNATURE)
LIST_ENTRY gMaps = INITIALIZE_LIST_HEAD_VARIABLE(gMaps);
/**
This function fills DeviceHandle/IoMmuAccess to the MAP_HANDLE_INFO,
based upon the DeviceAddress.
@param[in] DeviceHandle The device who initiates the DMA access request.
@param[in] DeviceAddress The base of device memory address to be used as the DMA memory.
@param[in] Length The length of device memory address to be used as the DMA memory.
@param[in] IoMmuAccess The IOMMU access.
**/
VOID
SyncDeviceHandleToMapInfo (
IN EFI_HANDLE DeviceHandle,
IN EFI_PHYSICAL_ADDRESS DeviceAddress,
IN UINT64 Length,
IN UINT64 IoMmuAccess
)
{
MAP_INFO *MapInfo;
MAP_HANDLE_INFO *MapHandleInfo;
LIST_ENTRY *Link;
EFI_TPL OriginalTpl;
//
// Find MapInfo according to DeviceAddress
//
OriginalTpl = gBS->RaiseTPL (VTD_TPL_LEVEL);
MapInfo = NULL;
for (Link = GetFirstNode (&gMaps)
; !IsNull (&gMaps, Link)
; Link = GetNextNode (&gMaps, Link)
) {
MapInfo = MAP_INFO_FROM_LINK (Link);
if (MapInfo->DeviceAddress == DeviceAddress) {
break;
}
}
if ((MapInfo == NULL) || (MapInfo->DeviceAddress != DeviceAddress)) {
DEBUG ((DEBUG_ERROR, "SyncDeviceHandleToMapInfo: DeviceAddress(0x%lx) - not found\n", DeviceAddress));
gBS->RestoreTPL (OriginalTpl);
return ;
}
//
// Find MapHandleInfo according to DeviceHandle
//
MapHandleInfo = NULL;
for (Link = GetFirstNode (&MapInfo->HandleList)
; !IsNull (&MapInfo->HandleList, Link)
; Link = GetNextNode (&MapInfo->HandleList, Link)
) {
MapHandleInfo = MAP_HANDLE_INFO_FROM_LINK (Link);
if (MapHandleInfo->DeviceHandle == DeviceHandle) {
break;
}
}
if ((MapHandleInfo != NULL) && (MapHandleInfo->DeviceHandle == DeviceHandle)) {
MapHandleInfo->IoMmuAccess = IoMmuAccess;
gBS->RestoreTPL (OriginalTpl);
return ;
}
//
// No DeviceHandle
// Initialize and insert the MAP_HANDLE_INFO structure
//
MapHandleInfo = AllocatePool (sizeof (MAP_HANDLE_INFO));
if (MapHandleInfo == NULL) {
DEBUG ((DEBUG_ERROR, "SyncDeviceHandleToMapInfo: %r\n", EFI_OUT_OF_RESOURCES));
gBS->RestoreTPL (OriginalTpl);
return ;
}
MapHandleInfo->Signature = MAP_HANDLE_INFO_SIGNATURE;
MapHandleInfo->DeviceHandle = DeviceHandle;
MapHandleInfo->IoMmuAccess = IoMmuAccess;
InsertTailList (&MapInfo->HandleList, &MapHandleInfo->Link);
gBS->RestoreTPL (OriginalTpl);
return ;
}
/**
Provides the controller-specific addresses required to access system memory from a
DMA bus master.
@param This The protocol instance pointer.
@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
EFIAPI
IoMmuMap (
IN EDKII_IOMMU_PROTOCOL *This,
IN EDKII_IOMMU_OPERATION Operation,
IN VOID *HostAddress,
IN OUT UINTN *NumberOfBytes,
OUT EFI_PHYSICAL_ADDRESS *DeviceAddress,
OUT VOID **Mapping
)
{
EFI_STATUS Status;
EFI_PHYSICAL_ADDRESS PhysicalAddress;
MAP_INFO *MapInfo;
EFI_PHYSICAL_ADDRESS DmaMemoryTop;
BOOLEAN NeedRemap;
EFI_TPL OriginalTpl;
if (NumberOfBytes == NULL || DeviceAddress == NULL ||
Mapping == NULL) {
DEBUG ((DEBUG_ERROR, "IoMmuMap: %r\n", EFI_INVALID_PARAMETER));
return EFI_INVALID_PARAMETER;
}
DEBUG ((DEBUG_VERBOSE, "IoMmuMap: ==> 0x%08x - 0x%08x (%x)\n", HostAddress, *NumberOfBytes, Operation));
//
// Make sure that Operation is valid
//
if ((UINT32) Operation >= EdkiiIoMmuOperationMaximum) {
DEBUG ((DEBUG_ERROR, "IoMmuMap: %r\n", EFI_INVALID_PARAMETER));
return EFI_INVALID_PARAMETER;
}
NeedRemap = FALSE;
PhysicalAddress = (EFI_PHYSICAL_ADDRESS) (UINTN) HostAddress;
DmaMemoryTop = DMA_MEMORY_TOP;
//
// Alignment check
//
if ((*NumberOfBytes != ALIGN_VALUE(*NumberOfBytes, SIZE_4KB)) ||
(PhysicalAddress != ALIGN_VALUE(PhysicalAddress, SIZE_4KB))) {
if ((Operation == EdkiiIoMmuOperationBusMasterCommonBuffer) ||
(Operation == EdkiiIoMmuOperationBusMasterCommonBuffer64)) {
//
// The input buffer might be a subset from IoMmuAllocateBuffer.
// Skip the check.
//
} else {
NeedRemap = TRUE;
}
}
if ((PhysicalAddress + *NumberOfBytes) >= DMA_MEMORY_TOP) {
NeedRemap = TRUE;
}
if (((Operation != EdkiiIoMmuOperationBusMasterRead64 &&
Operation != EdkiiIoMmuOperationBusMasterWrite64 &&
Operation != EdkiiIoMmuOperationBusMasterCommonBuffer64)) &&
((PhysicalAddress + *NumberOfBytes) > SIZE_4GB)) {
//
// If the root bridge or the device cannot handle performing DMA above
// 4GB but any part of the DMA transfer being mapped is above 4GB, then
// map the DMA transfer to a buffer below 4GB.
//
NeedRemap = TRUE;
DmaMemoryTop = MIN (DmaMemoryTop, SIZE_4GB - 1);
}
if (Operation == EdkiiIoMmuOperationBusMasterCommonBuffer ||
Operation == EdkiiIoMmuOperationBusMasterCommonBuffer64) {
if (NeedRemap) {
//
// Common Buffer operations can not be remapped. If the common buffer
// is above 4GB, then it is not possible to generate a mapping, so return
// an error.
//
DEBUG ((DEBUG_ERROR, "IoMmuMap: %r\n", EFI_UNSUPPORTED));
return EFI_UNSUPPORTED;
}
}
//
// Allocate a MAP_INFO structure to remember the mapping when Unmap() is
// called later.
//
MapInfo = AllocatePool (sizeof (MAP_INFO));
if (MapInfo == NULL) {
*NumberOfBytes = 0;
DEBUG ((DEBUG_ERROR, "IoMmuMap: %r\n", EFI_OUT_OF_RESOURCES));
return EFI_OUT_OF_RESOURCES;
}
//
// Initialize the MAP_INFO structure
//
MapInfo->Signature = MAP_INFO_SIGNATURE;
MapInfo->Operation = Operation;
MapInfo->NumberOfBytes = *NumberOfBytes;
MapInfo->NumberOfPages = EFI_SIZE_TO_PAGES (MapInfo->NumberOfBytes);
MapInfo->HostAddress = PhysicalAddress;
MapInfo->DeviceAddress = DmaMemoryTop;
InitializeListHead(&MapInfo->HandleList);
//
// Allocate a buffer below 4GB to map the transfer to.
//
if (NeedRemap) {
Status = gBS->AllocatePages (
AllocateMaxAddress,
EfiBootServicesData,
MapInfo->NumberOfPages,
&MapInfo->DeviceAddress
);
if (EFI_ERROR (Status)) {
FreePool (MapInfo);
*NumberOfBytes = 0;
DEBUG ((DEBUG_ERROR, "IoMmuMap: %r\n", Status));
return Status;
}
//
// If this is a read operation from the Bus Master's point of view,
// then copy the contents of the real buffer into the mapped buffer
// so the Bus Master can read the contents of the real buffer.
//
if (Operation == EdkiiIoMmuOperationBusMasterRead ||
Operation == EdkiiIoMmuOperationBusMasterRead64) {
CopyMem (
(VOID *) (UINTN) MapInfo->DeviceAddress,
(VOID *) (UINTN) MapInfo->HostAddress,
MapInfo->NumberOfBytes
);
}
} else {
MapInfo->DeviceAddress = MapInfo->HostAddress;
}
OriginalTpl = gBS->RaiseTPL (VTD_TPL_LEVEL);
InsertTailList (&gMaps, &MapInfo->Link);
gBS->RestoreTPL (OriginalTpl);
//
// The DeviceAddress is the address of the maped buffer below 4GB
//
*DeviceAddress = MapInfo->DeviceAddress;
//
// Return a pointer to the MAP_INFO structure in Mapping
//
*Mapping = MapInfo;
DEBUG ((DEBUG_VERBOSE, "IoMmuMap: 0x%08x - 0x%08x <==\n", *DeviceAddress, *Mapping));
return EFI_SUCCESS;
}
/**
Completes the Map() operation and releases any corresponding resources.
@param This The protocol instance pointer.
@param Mapping The mapping value returned from Map().
@retval EFI_SUCCESS The range was unmapped.
@retval EFI_INVALID_PARAMETER Mapping is not a value that was returned by Map().
@retval EFI_DEVICE_ERROR The data was not committed to the target system memory.
**/
EFI_STATUS
EFIAPI
IoMmuUnmap (
IN EDKII_IOMMU_PROTOCOL *This,
IN VOID *Mapping
)
{
MAP_INFO *MapInfo;
MAP_HANDLE_INFO *MapHandleInfo;
LIST_ENTRY *Link;
EFI_TPL OriginalTpl;
DEBUG ((DEBUG_VERBOSE, "IoMmuUnmap: 0x%08x\n", Mapping));
if (Mapping == NULL) {
DEBUG ((DEBUG_ERROR, "IoMmuUnmap: %r\n", EFI_INVALID_PARAMETER));
return EFI_INVALID_PARAMETER;
}
OriginalTpl = gBS->RaiseTPL (VTD_TPL_LEVEL);
MapInfo = NULL;
for (Link = GetFirstNode (&gMaps)
; !IsNull (&gMaps, Link)
; Link = GetNextNode (&gMaps, Link)
) {
MapInfo = MAP_INFO_FROM_LINK (Link);
if (MapInfo == Mapping) {
break;
}
}
//
// Mapping is not a valid value returned by Map()
//
if (MapInfo != Mapping) {
gBS->RestoreTPL (OriginalTpl);
DEBUG ((DEBUG_ERROR, "IoMmuUnmap: %r\n", EFI_INVALID_PARAMETER));
return EFI_INVALID_PARAMETER;
}
RemoveEntryList (&MapInfo->Link);
gBS->RestoreTPL (OriginalTpl);
//
// remove all nodes in MapInfo->HandleList
//
while (!IsListEmpty (&MapInfo->HandleList)) {
MapHandleInfo = MAP_HANDLE_INFO_FROM_LINK (MapInfo->HandleList.ForwardLink);
RemoveEntryList (&MapHandleInfo->Link);
FreePool (MapHandleInfo);
}
if (MapInfo->DeviceAddress != MapInfo->HostAddress) {
//
// If this is a write operation from the Bus Master's point of view,
// then copy the contents of the mapped buffer into the real buffer
// so the processor can read the contents of the real buffer.
//
if (MapInfo->Operation == EdkiiIoMmuOperationBusMasterWrite ||
MapInfo->Operation == EdkiiIoMmuOperationBusMasterWrite64) {
CopyMem (
(VOID *) (UINTN) MapInfo->HostAddress,
(VOID *) (UINTN) MapInfo->DeviceAddress,
MapInfo->NumberOfBytes
);
}
//
// Free the mapped buffer and the MAP_INFO structure.
//
gBS->FreePages (MapInfo->DeviceAddress, MapInfo->NumberOfPages);
}
FreePool (Mapping);
return EFI_SUCCESS;
}
/**
Allocates pages that are suitable for an OperationBusMasterCommonBuffer or
OperationBusMasterCommonBuffer64 mapping.
@param This The protocol instance pointer.
@param Type This parameter is not used and must be ignored.
@param MemoryType The type of memory to allocate, EfiBootServicesData or
EfiRuntimeServicesData.
@param Pages The number of pages to allocate.
@param HostAddress A pointer to store the base system memory address of the
allocated range.
@param Attributes The requested bit mask of attributes for 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, MEMORY_CACHED and DUAL_ADDRESS_CYCLE.
@retval EFI_INVALID_PARAMETER One or more parameters are invalid.
@retval EFI_OUT_OF_RESOURCES The memory pages could not be allocated.
**/
EFI_STATUS
EFIAPI
IoMmuAllocateBuffer (
IN EDKII_IOMMU_PROTOCOL *This,
IN EFI_ALLOCATE_TYPE Type,
IN EFI_MEMORY_TYPE MemoryType,
IN UINTN Pages,
IN OUT VOID **HostAddress,
IN UINT64 Attributes
)
{
EFI_STATUS Status;
EFI_PHYSICAL_ADDRESS PhysicalAddress;
DEBUG ((DEBUG_VERBOSE, "IoMmuAllocateBuffer: ==> 0x%08x\n", Pages));
//
// Validate Attributes
//
if ((Attributes & EDKII_IOMMU_ATTRIBUTE_INVALID_FOR_ALLOCATE_BUFFER) != 0) {
DEBUG ((DEBUG_ERROR, "IoMmuAllocateBuffer: %r\n", EFI_UNSUPPORTED));
return EFI_UNSUPPORTED;
}
//
// Check for invalid inputs
//
if (HostAddress == NULL) {
DEBUG ((DEBUG_ERROR, "IoMmuAllocateBuffer: %r\n", EFI_INVALID_PARAMETER));
return EFI_INVALID_PARAMETER;
}
//
// The only valid memory types are EfiBootServicesData and
// EfiRuntimeServicesData
//
if (MemoryType != EfiBootServicesData &&
MemoryType != EfiRuntimeServicesData) {
DEBUG ((DEBUG_ERROR, "IoMmuAllocateBuffer: %r\n", EFI_INVALID_PARAMETER));
return EFI_INVALID_PARAMETER;
}
PhysicalAddress = DMA_MEMORY_TOP;
if ((Attributes & EDKII_IOMMU_ATTRIBUTE_DUAL_ADDRESS_CYCLE) == 0) {
//
// Limit allocations to memory below 4GB
//
PhysicalAddress = MIN (PhysicalAddress, SIZE_4GB - 1);
}
Status = gBS->AllocatePages (
AllocateMaxAddress,
MemoryType,
Pages,
&PhysicalAddress
);
if (!EFI_ERROR (Status)) {
*HostAddress = (VOID *) (UINTN) PhysicalAddress;
}
DEBUG ((DEBUG_VERBOSE, "IoMmuAllocateBuffer: 0x%08x <==\n", *HostAddress));
return Status;
}
/**
Frees memory that was allocated with AllocateBuffer().
@param This The protocol instance pointer.
@param Pages The number of pages to free.
@param HostAddress The base system memory address of the allocated range.
@retval EFI_SUCCESS The requested memory pages were freed.
@retval EFI_INVALID_PARAMETER The memory range specified by HostAddress and Pages
was not allocated with AllocateBuffer().
**/
EFI_STATUS
EFIAPI
IoMmuFreeBuffer (
IN EDKII_IOMMU_PROTOCOL *This,
IN UINTN Pages,
IN VOID *HostAddress
)
{
DEBUG ((DEBUG_VERBOSE, "IoMmuFreeBuffer: 0x%\n", Pages));
return gBS->FreePages ((EFI_PHYSICAL_ADDRESS) (UINTN) HostAddress, Pages);
}
/**
Get device information from mapping.
@param[in] Mapping The mapping.
@param[out] DeviceAddress The device address of the mapping.
@param[out] NumberOfPages The number of pages of the mapping.
@retval EFI_SUCCESS The device information is returned.
@retval EFI_INVALID_PARAMETER The mapping is invalid.
**/
EFI_STATUS
GetDeviceInfoFromMapping (
IN VOID *Mapping,
OUT EFI_PHYSICAL_ADDRESS *DeviceAddress,
OUT UINTN *NumberOfPages
)
{
MAP_INFO *MapInfo;
LIST_ENTRY *Link;
if (Mapping == NULL) {
return EFI_INVALID_PARAMETER;
}
MapInfo = NULL;
for (Link = GetFirstNode (&gMaps)
; !IsNull (&gMaps, Link)
; Link = GetNextNode (&gMaps, Link)
) {
MapInfo = MAP_INFO_FROM_LINK (Link);
if (MapInfo == Mapping) {
break;
}
}
//
// Mapping is not a valid value returned by Map()
//
if (MapInfo != Mapping) {
return EFI_INVALID_PARAMETER;
}
*DeviceAddress = MapInfo->DeviceAddress;
*NumberOfPages = MapInfo->NumberOfPages;
return EFI_SUCCESS;
}