The non-coherent DmaLib implementation in ArmDmaLib no longer relies on anything in ArmPkg. So clone it into EmbeddedPkg, and rename it to NonCoherentDmaLib. Contributed-under: TianoCore Contribution Agreement 1.1 Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org> Reviewed-by: Leif Lindholm <leif.lindholm@linaro.org>
		
			
				
	
	
		
			492 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			492 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /** @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>
 | |
| 
 | |
|   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 <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
 | |
| HostToDeviceAddress (
 | |
|   IN  VOID      *Address
 | |
|   )
 | |
| {
 | |
|   return (PHYSICAL_ADDRESS)(UINTN)Address + PcdGet64 (PcdDmaDeviceOffset);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   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 (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) {
 | |
|         DEBUG ((DEBUG_ERROR,
 | |
|           "%a: Operation type 'MapOperationBusMasterCommonBuffer' is only "
 | |
|           "supported\non memory regions that were allocated using "
 | |
|           "DmaAllocateBuffer ()\n", __FUNCTION__));
 | |
|         Status = EFI_UNSUPPORTED;
 | |
|         goto FreeMapInfo;
 | |
|       }
 | |
| 
 | |
|       //
 | |
|       // 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;
 | |
| 
 | |
| 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;
 | |
| 
 | |
|   if (Mapping == NULL) {
 | |
|     ASSERT (FALSE);
 | |
|     return EFI_INVALID_PARAMETER;
 | |
|   }
 | |
| 
 | |
|   Map = (MAP_INFO_INSTANCE *)Mapping;
 | |
| 
 | |
|   Status = EFI_SUCCESS;
 | |
|   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) {
 | |
|     Allocation = AllocateAlignedPages (Pages, Alignment);
 | |
|   } else if (MemoryType == EfiRuntimeServicesData) {
 | |
|     Allocation = AllocateAlignedRuntimePages (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);
 | |
| 
 | |
|   // Get the Cpu protocol for later use
 | |
|   return gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **)&mCpu);
 | |
| }
 |