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