/** @file Generic non-coherent implementation of DmaLib.h Copyright (c) 2008 - 2010, Apple Inc. All rights reserved.
Copyright (c) 2015 - 2017, Linaro, Ltd. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include #include #include #include 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); }