mirror of https://github.com/acidanthera/audk.git
442 lines
15 KiB
C
442 lines
15 KiB
C
/** @file
|
|
BmDma related function
|
|
|
|
Copyright (c) 2017, Intel Corporation. All rights reserved.<BR>
|
|
This program and the accompanying materials
|
|
are licensed and made available under the terms and conditions of the BSD License
|
|
which accompanies this distribution. The full text of the license may be found at
|
|
http://opensource.org/licenses/bsd-license.php
|
|
|
|
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
|
|
|
**/
|
|
|
|
#include <PiDxe.h>
|
|
|
|
#include <Protocol/IoMmu.h>
|
|
|
|
#include <Library/BaseLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/BaseMemoryLib.h>
|
|
#include <Library/MemoryAllocationLib.h>
|
|
#include <Library/UefiBootServicesTableLib.h>
|
|
|
|
// TBD: May make it a policy
|
|
#define DMA_MEMORY_TOP MAX_UINTN
|
|
//#define DMA_MEMORY_TOP 0x0000000001FFFFFFULL
|
|
|
|
#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;
|
|
} MAP_INFO;
|
|
#define MAP_INFO_FROM_LINK(a) CR (a, MAP_INFO, Link, MAP_INFO_SIGNATURE)
|
|
|
|
LIST_ENTRY gMaps = INITIALIZE_LIST_HEAD_VARIABLE(gMaps);
|
|
|
|
/**
|
|
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;
|
|
|
|
DEBUG ((DEBUG_VERBOSE, "IoMmuMap: ==> 0x%08x - 0x%08x (%x)\n", HostAddress, NumberOfBytes, Operation));
|
|
|
|
if (HostAddress == NULL || NumberOfBytes == NULL || DeviceAddress == NULL ||
|
|
Mapping == NULL) {
|
|
DEBUG ((DEBUG_ERROR, "IoMmuMap: %r\n", EFI_INVALID_PARAMETER));
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// 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
|
|
// if 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;
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
InsertTailList (&gMaps, &MapInfo->Link);
|
|
|
|
//
|
|
// 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;
|
|
LIST_ENTRY *Link;
|
|
|
|
DEBUG ((DEBUG_VERBOSE, "IoMmuUnmap: 0x%08x\n", Mapping));
|
|
|
|
if (Mapping == NULL) {
|
|
DEBUG ((DEBUG_ERROR, "IoMmuUnmap: %r\n", EFI_INVALID_PARAMETER));
|
|
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) {
|
|
DEBUG ((DEBUG_ERROR, "IoMmuUnmap: %r\n", EFI_INVALID_PARAMETER));
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
RemoveEntryList (&MapInfo->Link);
|
|
|
|
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 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
|
|
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;
|
|
}
|
|
|