diff options
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.c')
-rw-r--r-- | src/VBox/Devices/EFI/Firmware/EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.c | 624 |
1 files changed, 624 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.c b/src/VBox/Devices/EFI/Firmware/EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.c new file mode 100644 index 00000000..bd9cab9f --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.c @@ -0,0 +1,624 @@ +/** @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 (Map->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", __FUNCTION__)); + 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); +} |