/** @file | |
Generic non-coherent implementation of DmaLib.h | |
Copyright (c) 2008 - 2010, Apple Inc. All rights reserved.<BR> | |
Copyright (c) 2015 - 2017, Linaro, Ltd. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <PiDxe.h> | |
#include <Library/BaseLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/DmaLib.h> | |
#include <Library/DxeServicesTableLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Library/IoLib.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Protocol/Cpu.h> | |
typedef struct { | |
EFI_PHYSICAL_ADDRESS HostAddress; | |
VOID *BufferAddress; | |
UINTN NumberOfBytes; | |
DMA_MAP_OPERATION Operation; | |
BOOLEAN DoubleBuffer; | |
} MAP_INFO_INSTANCE; | |
typedef struct { | |
LIST_ENTRY Link; | |
VOID *HostAddress; | |
UINTN NumPages; | |
UINT64 Attributes; | |
} UNCACHED_ALLOCATION; | |
STATIC EFI_CPU_ARCH_PROTOCOL *mCpu; | |
STATIC LIST_ENTRY UncachedAllocationList; | |
STATIC PHYSICAL_ADDRESS mDmaHostAddressLimit; | |
STATIC | |
PHYSICAL_ADDRESS | |
HostToDeviceAddress ( | |
IN VOID *Address | |
) | |
{ | |
return (PHYSICAL_ADDRESS)(UINTN)Address + PcdGet64 (PcdDmaDeviceOffset); | |
} | |
/** | |
Allocates one or more 4KB pages of a certain memory type at a specified | |
alignment. | |
Allocates the number of 4KB pages specified by Pages of a certain memory type | |
with an alignment specified by Alignment. The allocated buffer is returned. | |
If Pages is 0, then NULL is returned. If there is not enough memory at the | |
specified alignment remaining to satisfy the request, then NULL is returned. | |
If Alignment is not a power of two and Alignment is not zero, then ASSERT(). | |
If Pages plus EFI_SIZE_TO_PAGES (Alignment) overflows, then ASSERT(). | |
@param MemoryType The type of memory to allocate. | |
@param Pages The number of 4 KB pages to allocate. | |
@param Alignment The requested alignment of the allocation. | |
Must be a power of two. | |
If Alignment is zero, then byte alignment is | |
used. | |
@return A pointer to the allocated buffer or NULL if allocation fails. | |
**/ | |
STATIC | |
VOID * | |
InternalAllocateAlignedPages ( | |
IN EFI_MEMORY_TYPE MemoryType, | |
IN UINTN Pages, | |
IN UINTN Alignment | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_PHYSICAL_ADDRESS Memory; | |
UINTN AlignedMemory; | |
UINTN AlignmentMask; | |
UINTN UnalignedPages; | |
UINTN RealPages; | |
// | |
// Alignment must be a power of two or zero. | |
// | |
ASSERT ((Alignment & (Alignment - 1)) == 0); | |
if (Pages == 0) { | |
return NULL; | |
} | |
if (Alignment > EFI_PAGE_SIZE) { | |
// | |
// Calculate the total number of pages since alignment is larger than page | |
// size. | |
// | |
AlignmentMask = Alignment - 1; | |
RealPages = Pages + EFI_SIZE_TO_PAGES (Alignment); | |
// | |
// Make sure that Pages plus EFI_SIZE_TO_PAGES (Alignment) does not | |
// overflow. | |
// | |
ASSERT (RealPages > Pages); | |
Memory = mDmaHostAddressLimit; | |
Status = gBS->AllocatePages ( | |
AllocateMaxAddress, | |
MemoryType, | |
RealPages, | |
&Memory | |
); | |
if (EFI_ERROR (Status)) { | |
return NULL; | |
} | |
AlignedMemory = ((UINTN)Memory + AlignmentMask) & ~AlignmentMask; | |
UnalignedPages = EFI_SIZE_TO_PAGES (AlignedMemory - (UINTN)Memory); | |
if (UnalignedPages > 0) { | |
// | |
// Free first unaligned page(s). | |
// | |
Status = gBS->FreePages (Memory, UnalignedPages); | |
ASSERT_EFI_ERROR (Status); | |
} | |
Memory = AlignedMemory + EFI_PAGES_TO_SIZE (Pages); | |
UnalignedPages = RealPages - Pages - UnalignedPages; | |
if (UnalignedPages > 0) { | |
// | |
// Free last unaligned page(s). | |
// | |
Status = gBS->FreePages (Memory, UnalignedPages); | |
ASSERT_EFI_ERROR (Status); | |
} | |
} else { | |
// | |
// Do not over-allocate pages in this case. | |
// | |
Memory = mDmaHostAddressLimit; | |
Status = gBS->AllocatePages ( | |
AllocateMaxAddress, | |
MemoryType, | |
Pages, | |
&Memory | |
); | |
if (EFI_ERROR (Status)) { | |
return NULL; | |
} | |
AlignedMemory = (UINTN)Memory; | |
} | |
return (VOID *)AlignedMemory; | |
} | |
/** | |
Provides the DMA controller-specific addresses needed to access system memory. | |
Operation is relative to the DMA bus master. | |
@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 DMA | |
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 | |
controller to use to access the host's | |
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 | |
DmaMap ( | |
IN DMA_MAP_OPERATION Operation, | |
IN VOID *HostAddress, | |
IN OUT UINTN *NumberOfBytes, | |
OUT PHYSICAL_ADDRESS *DeviceAddress, | |
OUT VOID **Mapping | |
) | |
{ | |
EFI_STATUS Status; | |
MAP_INFO_INSTANCE *Map; | |
VOID *Buffer; | |
EFI_GCD_MEMORY_SPACE_DESCRIPTOR GcdDescriptor; | |
UINTN AllocSize; | |
if ((HostAddress == NULL) || | |
(NumberOfBytes == NULL) || | |
(DeviceAddress == NULL) || | |
(Mapping == NULL)) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
if (Operation >= MapOperationMaximum) { | |
return EFI_INVALID_PARAMETER; | |
} | |
*DeviceAddress = HostToDeviceAddress (HostAddress); | |
// Remember range so we can flush on the other side | |
Map = AllocatePool (sizeof (MAP_INFO_INSTANCE)); | |
if (Map == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
if (((UINTN)HostAddress + *NumberOfBytes) > mDmaHostAddressLimit) { | |
if (Operation == MapOperationBusMasterCommonBuffer) { | |
goto CommonBufferError; | |
} | |
AllocSize = ALIGN_VALUE (*NumberOfBytes, mCpu->DmaBufferAlignment); | |
Map->BufferAddress = InternalAllocateAlignedPages ( | |
EfiBootServicesData, | |
EFI_SIZE_TO_PAGES (AllocSize), | |
mCpu->DmaBufferAlignment | |
); | |
if (Map->BufferAddress == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto FreeMapInfo; | |
} | |
if (Operation == MapOperationBusMasterRead) { | |
CopyMem (Map->BufferAddress, (VOID *)(UINTN)HostAddress, *NumberOfBytes); | |
} | |
mCpu->FlushDataCache ( | |
mCpu, | |
(UINTN)Map->BufferAddress, | |
AllocSize, | |
EfiCpuFlushTypeWriteBack | |
); | |
*DeviceAddress = HostToDeviceAddress (Map->BufferAddress); | |
} else if ((Operation != MapOperationBusMasterRead) && | |
((((UINTN)HostAddress & (mCpu->DmaBufferAlignment - 1)) != 0) || | |
((*NumberOfBytes & (mCpu->DmaBufferAlignment - 1)) != 0))) | |
{ | |
// Get the cacheability of the region | |
Status = gDS->GetMemorySpaceDescriptor ((UINTN)HostAddress, &GcdDescriptor); | |
if (EFI_ERROR (Status)) { | |
goto FreeMapInfo; | |
} | |
// If the mapped buffer is not an uncached buffer | |
if ((GcdDescriptor.Attributes & (EFI_MEMORY_WB | EFI_MEMORY_WT)) != 0) { | |
// | |
// Operations of type MapOperationBusMasterCommonBuffer are only allowed | |
// on uncached buffers. | |
// | |
if (Operation == MapOperationBusMasterCommonBuffer) { | |
goto CommonBufferError; | |
} | |
// | |
// If the buffer does not fill entire cache lines we must double buffer | |
// into a suitably aligned allocation that allows us to invalidate the | |
// cache without running the risk of corrupting adjacent unrelated data. | |
// Note that pool allocations are guaranteed to be 8 byte aligned, so | |
// we only have to add (alignment - 8) worth of padding. | |
// | |
Map->DoubleBuffer = TRUE; | |
AllocSize = ALIGN_VALUE (*NumberOfBytes, mCpu->DmaBufferAlignment) + | |
(mCpu->DmaBufferAlignment - 8); | |
Map->BufferAddress = AllocatePool (AllocSize); | |
if (Map->BufferAddress == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto FreeMapInfo; | |
} | |
Buffer = ALIGN_POINTER (Map->BufferAddress, mCpu->DmaBufferAlignment); | |
*DeviceAddress = HostToDeviceAddress (Buffer); | |
// | |
// Get rid of any dirty cachelines covering the double buffer. This | |
// prevents them from being written back unexpectedly, potentially | |
// overwriting the data we receive from the device. | |
// | |
mCpu->FlushDataCache ( | |
mCpu, | |
(UINTN)Buffer, | |
*NumberOfBytes, | |
EfiCpuFlushTypeWriteBack | |
); | |
} else { | |
Map->DoubleBuffer = FALSE; | |
} | |
} else { | |
Map->DoubleBuffer = FALSE; | |
DEBUG_CODE_BEGIN (); | |
// | |
// The operation type check above only executes if the buffer happens to be | |
// misaligned with respect to CWG, but even if it is aligned, we should not | |
// allow arbitrary buffers to be used for creating consistent mappings. | |
// So duplicate the check here when running in DEBUG mode, just to assert | |
// that we are not trying to create a consistent mapping for cached memory. | |
// | |
Status = gDS->GetMemorySpaceDescriptor ((UINTN)HostAddress, &GcdDescriptor); | |
ASSERT_EFI_ERROR (Status); | |
ASSERT ( | |
Operation != MapOperationBusMasterCommonBuffer || | |
(GcdDescriptor.Attributes & (EFI_MEMORY_WB | EFI_MEMORY_WT)) == 0 | |
); | |
DEBUG_CODE_END (); | |
// Flush the Data Cache (should not have any effect if the memory region is | |
// uncached) | |
mCpu->FlushDataCache ( | |
mCpu, | |
(UINTN)HostAddress, | |
*NumberOfBytes, | |
EfiCpuFlushTypeWriteBackInvalidate | |
); | |
} | |
Map->HostAddress = (UINTN)HostAddress; | |
Map->NumberOfBytes = *NumberOfBytes; | |
Map->Operation = Operation; | |
*Mapping = Map; | |
return EFI_SUCCESS; | |
CommonBufferError: | |
DEBUG (( | |
DEBUG_ERROR, | |
"%a: Operation type 'MapOperationBusMasterCommonBuffer' is only " | |
"supported\non memory regions that were allocated using " | |
"DmaAllocateBuffer ()\n", | |
__func__ | |
)); | |
Status = EFI_UNSUPPORTED; | |
FreeMapInfo: | |
FreePool (Map); | |
return Status; | |
} | |
/** | |
Completes the DmaMapBusMasterRead(), DmaMapBusMasterWrite(), or | |
DmaMapBusMasterCommonBuffer() operation and releases any corresponding | |
resources. | |
@param Mapping The mapping value returned from DmaMap*(). | |
@retval EFI_SUCCESS The range was unmapped. | |
@retval EFI_DEVICE_ERROR The data was not committed to the target system | |
memory. | |
@retval EFI_INVALID_PARAMETER An inconsistency was detected between the | |
mapping type and the DoubleBuffer field | |
**/ | |
EFI_STATUS | |
EFIAPI | |
DmaUnmap ( | |
IN VOID *Mapping | |
) | |
{ | |
MAP_INFO_INSTANCE *Map; | |
EFI_STATUS Status; | |
VOID *Buffer; | |
UINTN AllocSize; | |
if (Mapping == NULL) { | |
ASSERT (FALSE); | |
return EFI_INVALID_PARAMETER; | |
} | |
Map = (MAP_INFO_INSTANCE *)Mapping; | |
Status = EFI_SUCCESS; | |
if (((UINTN)Map->HostAddress + Map->NumberOfBytes) > mDmaHostAddressLimit) { | |
AllocSize = ALIGN_VALUE (Map->NumberOfBytes, mCpu->DmaBufferAlignment); | |
if (Map->Operation == MapOperationBusMasterWrite) { | |
mCpu->FlushDataCache ( | |
mCpu, | |
(UINTN)Map->BufferAddress, | |
AllocSize, | |
EfiCpuFlushTypeInvalidate | |
); | |
CopyMem ( | |
(VOID *)(UINTN)Map->HostAddress, | |
Map->BufferAddress, | |
Map->NumberOfBytes | |
); | |
} | |
FreePages (Map->BufferAddress, EFI_SIZE_TO_PAGES (AllocSize)); | |
} else if (Map->DoubleBuffer) { | |
ASSERT (Map->Operation == MapOperationBusMasterWrite); | |
if (Map->Operation != MapOperationBusMasterWrite) { | |
Status = EFI_INVALID_PARAMETER; | |
} else { | |
Buffer = ALIGN_POINTER (Map->BufferAddress, mCpu->DmaBufferAlignment); | |
mCpu->FlushDataCache ( | |
mCpu, | |
(UINTN)Buffer, | |
Map->NumberOfBytes, | |
EfiCpuFlushTypeInvalidate | |
); | |
CopyMem ((VOID *)(UINTN)Map->HostAddress, Buffer, Map->NumberOfBytes); | |
FreePool (Map->BufferAddress); | |
} | |
} else { | |
if (Map->Operation == MapOperationBusMasterWrite) { | |
// | |
// Make sure we read buffer from uncached memory and not the cache | |
// | |
mCpu->FlushDataCache ( | |
mCpu, | |
Map->HostAddress, | |
Map->NumberOfBytes, | |
EfiCpuFlushTypeInvalidate | |
); | |
} | |
} | |
FreePool (Map); | |
return Status; | |
} | |
/** | |
Allocates pages that are suitable for an DmaMap() of type | |
MapOperationBusMasterCommonBuffer mapping. | |
@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. | |
@retval EFI_SUCCESS The requested memory pages were allocated. | |
@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 | |
DmaAllocateBuffer ( | |
IN EFI_MEMORY_TYPE MemoryType, | |
IN UINTN Pages, | |
OUT VOID **HostAddress | |
) | |
{ | |
return DmaAllocateAlignedBuffer (MemoryType, Pages, 0, HostAddress); | |
} | |
/** | |
Allocates pages that are suitable for an DmaMap() of type | |
MapOperationBusMasterCommonBuffer mapping, at the requested alignment. | |
@param MemoryType The type of memory to allocate, | |
EfiBootServicesData or EfiRuntimeServicesData. | |
@param Pages The number of pages to allocate. | |
@param Alignment Alignment in bytes of the base of the returned | |
buffer (must be a power of 2) | |
@param HostAddress A pointer to store the base system memory | |
address of the allocated range. | |
@retval EFI_SUCCESS The requested memory pages were allocated. | |
@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 | |
DmaAllocateAlignedBuffer ( | |
IN EFI_MEMORY_TYPE MemoryType, | |
IN UINTN Pages, | |
IN UINTN Alignment, | |
OUT VOID **HostAddress | |
) | |
{ | |
EFI_GCD_MEMORY_SPACE_DESCRIPTOR GcdDescriptor; | |
VOID *Allocation; | |
UINT64 MemType; | |
UNCACHED_ALLOCATION *Alloc; | |
EFI_STATUS Status; | |
if (Alignment == 0) { | |
Alignment = EFI_PAGE_SIZE; | |
} | |
if ((HostAddress == NULL) || | |
((Alignment & (Alignment - 1)) != 0)) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
if ((MemoryType == EfiBootServicesData) || | |
(MemoryType == EfiRuntimeServicesData)) | |
{ | |
Allocation = InternalAllocateAlignedPages (MemoryType, Pages, Alignment); | |
} else { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (Allocation == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
// Get the cacheability of the region | |
Status = gDS->GetMemorySpaceDescriptor ((UINTN)Allocation, &GcdDescriptor); | |
if (EFI_ERROR (Status)) { | |
goto FreeBuffer; | |
} | |
// Choose a suitable uncached memory type that is supported by the region | |
if (GcdDescriptor.Capabilities & EFI_MEMORY_WC) { | |
MemType = EFI_MEMORY_WC; | |
} else if (GcdDescriptor.Capabilities & EFI_MEMORY_UC) { | |
MemType = EFI_MEMORY_UC; | |
} else { | |
Status = EFI_UNSUPPORTED; | |
goto FreeBuffer; | |
} | |
Alloc = AllocatePool (sizeof *Alloc); | |
if (Alloc == NULL) { | |
goto FreeBuffer; | |
} | |
Alloc->HostAddress = Allocation; | |
Alloc->NumPages = Pages; | |
Alloc->Attributes = GcdDescriptor.Attributes; | |
InsertHeadList (&UncachedAllocationList, &Alloc->Link); | |
// Remap the region with the new attributes | |
Status = gDS->SetMemorySpaceAttributes ( | |
(PHYSICAL_ADDRESS)(UINTN)Allocation, | |
EFI_PAGES_TO_SIZE (Pages), | |
MemType | |
); | |
if (EFI_ERROR (Status)) { | |
goto FreeAlloc; | |
} | |
Status = mCpu->FlushDataCache ( | |
mCpu, | |
(PHYSICAL_ADDRESS)(UINTN)Allocation, | |
EFI_PAGES_TO_SIZE (Pages), | |
EfiCpuFlushTypeInvalidate | |
); | |
if (EFI_ERROR (Status)) { | |
goto FreeAlloc; | |
} | |
*HostAddress = Allocation; | |
return EFI_SUCCESS; | |
FreeAlloc: | |
RemoveEntryList (&Alloc->Link); | |
FreePool (Alloc); | |
FreeBuffer: | |
FreePages (Allocation, Pages); | |
return Status; | |
} | |
/** | |
Frees memory that was allocated with DmaAllocateBuffer(). | |
@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 | |
DmaAllocateBuffer(). | |
**/ | |
EFI_STATUS | |
EFIAPI | |
DmaFreeBuffer ( | |
IN UINTN Pages, | |
IN VOID *HostAddress | |
) | |
{ | |
LIST_ENTRY *Link; | |
UNCACHED_ALLOCATION *Alloc; | |
BOOLEAN Found; | |
EFI_STATUS Status; | |
if (HostAddress == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
for (Link = GetFirstNode (&UncachedAllocationList), Found = FALSE; | |
!IsNull (&UncachedAllocationList, Link); | |
Link = GetNextNode (&UncachedAllocationList, Link)) | |
{ | |
Alloc = BASE_CR (Link, UNCACHED_ALLOCATION, Link); | |
if ((Alloc->HostAddress == HostAddress) && (Alloc->NumPages == Pages)) { | |
Found = TRUE; | |
break; | |
} | |
} | |
if (!Found) { | |
ASSERT (FALSE); | |
return EFI_INVALID_PARAMETER; | |
} | |
RemoveEntryList (&Alloc->Link); | |
Status = gDS->SetMemorySpaceAttributes ( | |
(PHYSICAL_ADDRESS)(UINTN)HostAddress, | |
EFI_PAGES_TO_SIZE (Pages), | |
Alloc->Attributes | |
); | |
if (EFI_ERROR (Status)) { | |
goto FreeAlloc; | |
} | |
// | |
// If we fail to restore the original attributes, it is better to leak the | |
// memory than to return it to the heap | |
// | |
FreePages (HostAddress, Pages); | |
FreeAlloc: | |
FreePool (Alloc); | |
return Status; | |
} | |
EFI_STATUS | |
EFIAPI | |
NonCoherentDmaLibConstructor ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
InitializeListHead (&UncachedAllocationList); | |
// | |
// Ensure that the combination of DMA addressing offset and limit produces | |
// a sane value. | |
// | |
ASSERT (PcdGet64 (PcdDmaDeviceLimit) > PcdGet64 (PcdDmaDeviceOffset)); | |
mDmaHostAddressLimit = PcdGet64 (PcdDmaDeviceLimit) - | |
PcdGet64 (PcdDmaDeviceOffset); | |
// Get the Cpu protocol for later use | |
return gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **)&mCpu); | |
} |