| /** @file | |
| BmDma related function | |
| Copyright (c) 2017 - 2018, Intel Corporation. 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 "DmaProtection.h" | |
| // TBD: May make it a policy | |
| #define DMA_MEMORY_TOP MAX_UINTN | |
| //#define DMA_MEMORY_TOP 0x0000000001FFFFFFULL | |
| #define MAP_INFO_SIGNATURE SIGNATURE_32 ('D', 'M', 'A', 'P') | |
| typedef struct { | |
| UINT32 Signature; | |
| LIST_ENTRY Link; | |
| EDKII_IOMMU_OPERATION Operation; | |
| UINTN NumberOfBytes; | |
| UINTN NumberOfPages; | |
| EFI_PHYSICAL_ADDRESS HostAddress; | |
| EFI_PHYSICAL_ADDRESS DeviceAddress; | |
| } MAP_INFO; | |
| #define MAP_INFO_FROM_LINK(a) CR (a, MAP_INFO, Link, MAP_INFO_SIGNATURE) | |
| LIST_ENTRY gMaps = INITIALIZE_LIST_HEAD_VARIABLE(gMaps); | |
| /** | |
| Provides the controller-specific addresses required to access system memory from a | |
| DMA bus master. | |
| @param This The protocol instance pointer. | |
| @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 PCI 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 PCI controller to use to | |
| access the hosts 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 | |
| IoMmuMap ( | |
| IN EDKII_IOMMU_PROTOCOL *This, | |
| IN EDKII_IOMMU_OPERATION Operation, | |
| IN VOID *HostAddress, | |
| IN OUT UINTN *NumberOfBytes, | |
| OUT EFI_PHYSICAL_ADDRESS *DeviceAddress, | |
| OUT VOID **Mapping | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_PHYSICAL_ADDRESS PhysicalAddress; | |
| MAP_INFO *MapInfo; | |
| EFI_PHYSICAL_ADDRESS DmaMemoryTop; | |
| BOOLEAN NeedRemap; | |
| EFI_TPL OriginalTpl; | |
| if (NumberOfBytes == NULL || DeviceAddress == NULL || | |
| Mapping == NULL) { | |
| DEBUG ((DEBUG_ERROR, "IoMmuMap: %r\n", EFI_INVALID_PARAMETER)); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| DEBUG ((DEBUG_VERBOSE, "IoMmuMap: ==> 0x%08x - 0x%08x (%x)\n", HostAddress, *NumberOfBytes, Operation)); | |
| // | |
| // Make sure that Operation is valid | |
| // | |
| if ((UINT32) Operation >= EdkiiIoMmuOperationMaximum) { | |
| DEBUG ((DEBUG_ERROR, "IoMmuMap: %r\n", EFI_INVALID_PARAMETER)); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| NeedRemap = FALSE; | |
| PhysicalAddress = (EFI_PHYSICAL_ADDRESS) (UINTN) HostAddress; | |
| DmaMemoryTop = DMA_MEMORY_TOP; | |
| // | |
| // Alignment check | |
| // | |
| if ((*NumberOfBytes != ALIGN_VALUE(*NumberOfBytes, SIZE_4KB)) || | |
| (PhysicalAddress != ALIGN_VALUE(PhysicalAddress, SIZE_4KB))) { | |
| if ((Operation == EdkiiIoMmuOperationBusMasterCommonBuffer) || | |
| (Operation == EdkiiIoMmuOperationBusMasterCommonBuffer64)) { | |
| // | |
| // The input buffer might be a subset from IoMmuAllocateBuffer. | |
| // Skip the check. | |
| // | |
| } else { | |
| NeedRemap = TRUE; | |
| } | |
| } | |
| if ((PhysicalAddress + *NumberOfBytes) >= DMA_MEMORY_TOP) { | |
| NeedRemap = TRUE; | |
| } | |
| if (((Operation != EdkiiIoMmuOperationBusMasterRead64 && | |
| Operation != EdkiiIoMmuOperationBusMasterWrite64 && | |
| Operation != EdkiiIoMmuOperationBusMasterCommonBuffer64)) && | |
| ((PhysicalAddress + *NumberOfBytes) > SIZE_4GB)) { | |
| // | |
| // If the root bridge or the device cannot handle performing DMA above | |
| // 4GB but any part of the DMA transfer being mapped is above 4GB, then | |
| // map the DMA transfer to a buffer below 4GB. | |
| // | |
| NeedRemap = TRUE; | |
| DmaMemoryTop = MIN (DmaMemoryTop, SIZE_4GB - 1); | |
| } | |
| if (Operation == EdkiiIoMmuOperationBusMasterCommonBuffer || | |
| Operation == EdkiiIoMmuOperationBusMasterCommonBuffer64) { | |
| if (NeedRemap) { | |
| // | |
| // Common Buffer operations can not be remapped. If the common buffer | |
| // is above 4GB, then it is not possible to generate a mapping, so return | |
| // an error. | |
| // | |
| DEBUG ((DEBUG_ERROR, "IoMmuMap: %r\n", EFI_UNSUPPORTED)); | |
| return EFI_UNSUPPORTED; | |
| } | |
| } | |
| // | |
| // Allocate a MAP_INFO structure to remember the mapping when Unmap() is | |
| // called later. | |
| // | |
| MapInfo = AllocatePool (sizeof (MAP_INFO)); | |
| if (MapInfo == NULL) { | |
| *NumberOfBytes = 0; | |
| DEBUG ((DEBUG_ERROR, "IoMmuMap: %r\n", EFI_OUT_OF_RESOURCES)); | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| // | |
| // Initialize the MAP_INFO structure | |
| // | |
| MapInfo->Signature = MAP_INFO_SIGNATURE; | |
| MapInfo->Operation = Operation; | |
| MapInfo->NumberOfBytes = *NumberOfBytes; | |
| MapInfo->NumberOfPages = EFI_SIZE_TO_PAGES (MapInfo->NumberOfBytes); | |
| MapInfo->HostAddress = PhysicalAddress; | |
| MapInfo->DeviceAddress = DmaMemoryTop; | |
| // | |
| // Allocate a buffer below 4GB to map the transfer to. | |
| // | |
| if (NeedRemap) { | |
| Status = gBS->AllocatePages ( | |
| AllocateMaxAddress, | |
| EfiBootServicesData, | |
| MapInfo->NumberOfPages, | |
| &MapInfo->DeviceAddress | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| FreePool (MapInfo); | |
| *NumberOfBytes = 0; | |
| DEBUG ((DEBUG_ERROR, "IoMmuMap: %r\n", Status)); | |
| return Status; | |
| } | |
| // | |
| // If this is a read operation from the Bus Master's point of view, | |
| // then copy the contents of the real buffer into the mapped buffer | |
| // so the Bus Master can read the contents of the real buffer. | |
| // | |
| if (Operation == EdkiiIoMmuOperationBusMasterRead || | |
| Operation == EdkiiIoMmuOperationBusMasterRead64) { | |
| CopyMem ( | |
| (VOID *) (UINTN) MapInfo->DeviceAddress, | |
| (VOID *) (UINTN) MapInfo->HostAddress, | |
| MapInfo->NumberOfBytes | |
| ); | |
| } | |
| } else { | |
| MapInfo->DeviceAddress = MapInfo->HostAddress; | |
| } | |
| OriginalTpl = gBS->RaiseTPL (VTD_TPL_LEVEL); | |
| InsertTailList (&gMaps, &MapInfo->Link); | |
| gBS->RestoreTPL (OriginalTpl); | |
| // | |
| // The DeviceAddress is the address of the maped buffer below 4GB | |
| // | |
| *DeviceAddress = MapInfo->DeviceAddress; | |
| // | |
| // Return a pointer to the MAP_INFO structure in Mapping | |
| // | |
| *Mapping = MapInfo; | |
| DEBUG ((DEBUG_VERBOSE, "IoMmuMap: 0x%08x - 0x%08x <==\n", *DeviceAddress, *Mapping)); | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Completes the Map() operation and releases any corresponding resources. | |
| @param This The protocol instance pointer. | |
| @param Mapping The mapping value returned from Map(). | |
| @retval EFI_SUCCESS The range was unmapped. | |
| @retval EFI_INVALID_PARAMETER Mapping is not a value that was returned by Map(). | |
| @retval EFI_DEVICE_ERROR The data was not committed to the target system memory. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| IoMmuUnmap ( | |
| IN EDKII_IOMMU_PROTOCOL *This, | |
| IN VOID *Mapping | |
| ) | |
| { | |
| MAP_INFO *MapInfo; | |
| LIST_ENTRY *Link; | |
| EFI_TPL OriginalTpl; | |
| DEBUG ((DEBUG_VERBOSE, "IoMmuUnmap: 0x%08x\n", Mapping)); | |
| if (Mapping == NULL) { | |
| DEBUG ((DEBUG_ERROR, "IoMmuUnmap: %r\n", EFI_INVALID_PARAMETER)); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| OriginalTpl = gBS->RaiseTPL (VTD_TPL_LEVEL); | |
| MapInfo = NULL; | |
| for (Link = GetFirstNode (&gMaps) | |
| ; !IsNull (&gMaps, Link) | |
| ; Link = GetNextNode (&gMaps, Link) | |
| ) { | |
| MapInfo = MAP_INFO_FROM_LINK (Link); | |
| if (MapInfo == Mapping) { | |
| break; | |
| } | |
| } | |
| // | |
| // Mapping is not a valid value returned by Map() | |
| // | |
| if (MapInfo != Mapping) { | |
| gBS->RestoreTPL (OriginalTpl); | |
| DEBUG ((DEBUG_ERROR, "IoMmuUnmap: %r\n", EFI_INVALID_PARAMETER)); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| RemoveEntryList (&MapInfo->Link); | |
| gBS->RestoreTPL (OriginalTpl); | |
| if (MapInfo->DeviceAddress != MapInfo->HostAddress) { | |
| // | |
| // If this is a write operation from the Bus Master's point of view, | |
| // then copy the contents of the mapped buffer into the real buffer | |
| // so the processor can read the contents of the real buffer. | |
| // | |
| if (MapInfo->Operation == EdkiiIoMmuOperationBusMasterWrite || | |
| MapInfo->Operation == EdkiiIoMmuOperationBusMasterWrite64) { | |
| CopyMem ( | |
| (VOID *) (UINTN) MapInfo->HostAddress, | |
| (VOID *) (UINTN) MapInfo->DeviceAddress, | |
| MapInfo->NumberOfBytes | |
| ); | |
| } | |
| // | |
| // Free the mapped buffer and the MAP_INFO structure. | |
| // | |
| gBS->FreePages (MapInfo->DeviceAddress, MapInfo->NumberOfPages); | |
| } | |
| FreePool (Mapping); | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Allocates pages that are suitable for an OperationBusMasterCommonBuffer or | |
| OperationBusMasterCommonBuffer64 mapping. | |
| @param This The protocol instance pointer. | |
| @param Type This parameter is not used and must be ignored. | |
| @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. | |
| @param Attributes The requested bit mask of attributes for the allocated range. | |
| @retval EFI_SUCCESS The requested memory pages were allocated. | |
| @retval EFI_UNSUPPORTED Attributes is unsupported. The only legal attribute bits are | |
| MEMORY_WRITE_COMBINE, MEMORY_CACHED and DUAL_ADDRESS_CYCLE. | |
| @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 | |
| IoMmuAllocateBuffer ( | |
| IN EDKII_IOMMU_PROTOCOL *This, | |
| IN EFI_ALLOCATE_TYPE Type, | |
| IN EFI_MEMORY_TYPE MemoryType, | |
| IN UINTN Pages, | |
| IN OUT VOID **HostAddress, | |
| IN UINT64 Attributes | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_PHYSICAL_ADDRESS PhysicalAddress; | |
| DEBUG ((DEBUG_VERBOSE, "IoMmuAllocateBuffer: ==> 0x%08x\n", Pages)); | |
| // | |
| // Validate Attributes | |
| // | |
| if ((Attributes & EDKII_IOMMU_ATTRIBUTE_INVALID_FOR_ALLOCATE_BUFFER) != 0) { | |
| DEBUG ((DEBUG_ERROR, "IoMmuAllocateBuffer: %r\n", EFI_UNSUPPORTED)); | |
| return EFI_UNSUPPORTED; | |
| } | |
| // | |
| // Check for invalid inputs | |
| // | |
| if (HostAddress == NULL) { | |
| DEBUG ((DEBUG_ERROR, "IoMmuAllocateBuffer: %r\n", EFI_INVALID_PARAMETER)); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| // | |
| // The only valid memory types are EfiBootServicesData and | |
| // EfiRuntimeServicesData | |
| // | |
| if (MemoryType != EfiBootServicesData && | |
| MemoryType != EfiRuntimeServicesData) { | |
| DEBUG ((DEBUG_ERROR, "IoMmuAllocateBuffer: %r\n", EFI_INVALID_PARAMETER)); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| PhysicalAddress = DMA_MEMORY_TOP; | |
| if ((Attributes & EDKII_IOMMU_ATTRIBUTE_DUAL_ADDRESS_CYCLE) == 0) { | |
| // | |
| // Limit allocations to memory below 4GB | |
| // | |
| PhysicalAddress = MIN (PhysicalAddress, SIZE_4GB - 1); | |
| } | |
| Status = gBS->AllocatePages ( | |
| AllocateMaxAddress, | |
| MemoryType, | |
| Pages, | |
| &PhysicalAddress | |
| ); | |
| if (!EFI_ERROR (Status)) { | |
| *HostAddress = (VOID *) (UINTN) PhysicalAddress; | |
| } | |
| DEBUG ((DEBUG_VERBOSE, "IoMmuAllocateBuffer: 0x%08x <==\n", *HostAddress)); | |
| return Status; | |
| } | |
| /** | |
| Frees memory that was allocated with AllocateBuffer(). | |
| @param This The protocol instance pointer. | |
| @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 AllocateBuffer(). | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| IoMmuFreeBuffer ( | |
| IN EDKII_IOMMU_PROTOCOL *This, | |
| IN UINTN Pages, | |
| IN VOID *HostAddress | |
| ) | |
| { | |
| DEBUG ((DEBUG_VERBOSE, "IoMmuFreeBuffer: 0x%\n", Pages)); | |
| return gBS->FreePages ((EFI_PHYSICAL_ADDRESS) (UINTN) HostAddress, Pages); | |
| } | |
| /** | |
| Get device information from mapping. | |
| @param[in] Mapping The mapping. | |
| @param[out] DeviceAddress The device address of the mapping. | |
| @param[out] NumberOfPages The number of pages of the mapping. | |
| @retval EFI_SUCCESS The device information is returned. | |
| @retval EFI_INVALID_PARAMETER The mapping is invalid. | |
| **/ | |
| EFI_STATUS | |
| GetDeviceInfoFromMapping ( | |
| IN VOID *Mapping, | |
| OUT EFI_PHYSICAL_ADDRESS *DeviceAddress, | |
| OUT UINTN *NumberOfPages | |
| ) | |
| { | |
| MAP_INFO *MapInfo; | |
| LIST_ENTRY *Link; | |
| if (Mapping == NULL) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| MapInfo = NULL; | |
| for (Link = GetFirstNode (&gMaps) | |
| ; !IsNull (&gMaps, Link) | |
| ; Link = GetNextNode (&gMaps, Link) | |
| ) { | |
| MapInfo = MAP_INFO_FROM_LINK (Link); | |
| if (MapInfo == Mapping) { | |
| break; | |
| } | |
| } | |
| // | |
| // Mapping is not a valid value returned by Map() | |
| // | |
| if (MapInfo != Mapping) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| *DeviceAddress = MapInfo->DeviceAddress; | |
| *NumberOfPages = MapInfo->NumberOfPages; | |
| return EFI_SUCCESS; | |
| } | |