--- /dev/null
+/** @file\r
+\r
+ The protocol provides support to allocate, free, map and umap a DMA buffer for\r
+ bus master (e.g PciHostBridge). When SEV is enabled, the DMA operations must\r
+ be performed on unencrypted buffer hence we use a bounce buffer to map the guest\r
+ buffer into an unencrypted DMA buffer.\r
+\r
+ Copyright (c) 2017, AMD Inc. All rights reserved.<BR>\r
+ Copyright (c) 2017, Intel Corporation. All rights reserved.<BR>\r
+\r
+ This program and the accompanying materials are licensed and made available\r
+ under the terms and conditions of the BSD License which accompanies this\r
+ distribution. The full text of the license may be found at\r
+ http://opensource.org/licenses/bsd-license.php\r
+\r
+ THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+\r
+**/\r
+\r
+#include "AmdSevIoMmu.h"\r
+\r
+typedef struct {\r
+ EDKII_IOMMU_OPERATION Operation;\r
+ UINTN NumberOfBytes;\r
+ UINTN NumberOfPages;\r
+ EFI_PHYSICAL_ADDRESS HostAddress;\r
+ EFI_PHYSICAL_ADDRESS DeviceAddress;\r
+} MAP_INFO;\r
+\r
+#define NO_MAPPING (VOID *) (UINTN) -1\r
+\r
+/**\r
+ Provides the controller-specific addresses required to access system memory from a\r
+ DMA bus master. On SEV guest, the DMA operations must be performed on shared\r
+ buffer hence we allocate a bounce buffer to map the HostAddress to a DeviceAddress.\r
+ The Encryption attribute is removed from the DeviceAddress buffer.\r
+\r
+ @param This The protocol instance pointer.\r
+ @param Operation Indicates if the bus master is going to read or\r
+ write to system memory.\r
+ @param HostAddress The system memory address to map to the PCI controller.\r
+ @param NumberOfBytes On input the number of bytes to map. On output\r
+ the number of bytes\r
+ that were mapped.\r
+ @param DeviceAddress The resulting map address for the bus master PCI\r
+ controller to use to\r
+ access the hosts HostAddress.\r
+ @param Mapping A resulting value to pass to Unmap().\r
+\r
+ @retval EFI_SUCCESS The range was mapped for the returned NumberOfBytes.\r
+ @retval EFI_UNSUPPORTED The HostAddress cannot be mapped as a common buffer.\r
+ @retval EFI_INVALID_PARAMETER One or more parameters are invalid.\r
+ @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack\r
+ of resources.\r
+ @retval EFI_DEVICE_ERROR The system hardware could not map the requested address.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+IoMmuMap (\r
+ IN EDKII_IOMMU_PROTOCOL *This,\r
+ IN EDKII_IOMMU_OPERATION Operation,\r
+ IN VOID *HostAddress,\r
+ IN OUT UINTN *NumberOfBytes,\r
+ OUT EFI_PHYSICAL_ADDRESS *DeviceAddress,\r
+ OUT VOID **Mapping\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ EFI_PHYSICAL_ADDRESS PhysicalAddress;\r
+ MAP_INFO *MapInfo;\r
+ EFI_PHYSICAL_ADDRESS DmaMemoryTop;\r
+ EFI_ALLOCATE_TYPE AllocateType;\r
+\r
+ if (HostAddress == NULL || NumberOfBytes == NULL || DeviceAddress == NULL ||\r
+ Mapping == NULL) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+\r
+ //\r
+ // Make sure that Operation is valid\r
+ //\r
+ if ((UINT32) Operation >= EdkiiIoMmuOperationMaximum) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+ PhysicalAddress = (EFI_PHYSICAL_ADDRESS) (UINTN) HostAddress;\r
+\r
+ DmaMemoryTop = (UINTN)-1;\r
+ AllocateType = AllocateAnyPages;\r
+\r
+ if (((Operation != EdkiiIoMmuOperationBusMasterRead64 &&\r
+ Operation != EdkiiIoMmuOperationBusMasterWrite64 &&\r
+ Operation != EdkiiIoMmuOperationBusMasterCommonBuffer64)) &&\r
+ ((PhysicalAddress + *NumberOfBytes) > SIZE_4GB)) {\r
+ //\r
+ // If the root bridge or the device cannot handle performing DMA above\r
+ // 4GB but any part of the DMA transfer being mapped is above 4GB, then\r
+ // map the DMA transfer to a buffer below 4GB.\r
+ //\r
+ DmaMemoryTop = SIZE_4GB - 1;\r
+ AllocateType = AllocateMaxAddress;\r
+\r
+ if (Operation == EdkiiIoMmuOperationBusMasterCommonBuffer ||\r
+ Operation == EdkiiIoMmuOperationBusMasterCommonBuffer64) {\r
+ //\r
+ // Common Buffer operations can not be remapped. If the common buffer\r
+ // if above 4GB, then it is not possible to generate a mapping, so return\r
+ // an error.\r
+ //\r
+ return EFI_UNSUPPORTED;\r
+ }\r
+ }\r
+\r
+ //\r
+ // CommandBuffer was allocated by us (AllocateBuffer) and is already in\r
+ // unencryted buffer so no need to create bounce buffer\r
+ //\r
+ if (Operation == EdkiiIoMmuOperationBusMasterCommonBuffer ||\r
+ Operation == EdkiiIoMmuOperationBusMasterCommonBuffer64) {\r
+ *Mapping = NO_MAPPING;\r
+ *DeviceAddress = PhysicalAddress;\r
+\r
+ return EFI_SUCCESS;\r
+ }\r
+\r
+ //\r
+ // Allocate a MAP_INFO structure to remember the mapping when Unmap() is\r
+ // called later.\r
+ //\r
+ MapInfo = AllocatePool (sizeof (MAP_INFO));\r
+ if (MapInfo == NULL) {\r
+ *NumberOfBytes = 0;\r
+ return EFI_OUT_OF_RESOURCES;\r
+ }\r
+\r
+ //\r
+ // Initialize the MAP_INFO structure\r
+ //\r
+ MapInfo->Operation = Operation;\r
+ MapInfo->NumberOfBytes = *NumberOfBytes;\r
+ MapInfo->NumberOfPages = EFI_SIZE_TO_PAGES (MapInfo->NumberOfBytes);\r
+ MapInfo->HostAddress = PhysicalAddress;\r
+ MapInfo->DeviceAddress = DmaMemoryTop;\r
+\r
+ //\r
+ // Allocate a buffer to map the transfer to.\r
+ //\r
+ Status = gBS->AllocatePages (\r
+ AllocateType,\r
+ EfiBootServicesData,\r
+ MapInfo->NumberOfPages,\r
+ &MapInfo->DeviceAddress\r
+ );\r
+ if (EFI_ERROR (Status)) {\r
+ FreePool (MapInfo);\r
+ *NumberOfBytes = 0;\r
+ return Status;\r
+ }\r
+\r
+ //\r
+ // Clear the memory encryption mask from the device buffer\r
+ //\r
+ Status = MemEncryptSevClearPageEncMask (0, MapInfo->DeviceAddress, MapInfo->NumberOfPages, TRUE);\r
+ ASSERT_EFI_ERROR(Status);\r
+\r
+ //\r
+ // If this is a read operation from the Bus Master's point of view,\r
+ // then copy the contents of the real buffer into the mapped buffer\r
+ // so the Bus Master can read the contents of the real buffer.\r
+ //\r
+ if (Operation == EdkiiIoMmuOperationBusMasterRead ||\r
+ Operation == EdkiiIoMmuOperationBusMasterRead64) {\r
+ CopyMem (\r
+ (VOID *) (UINTN) MapInfo->DeviceAddress,\r
+ (VOID *) (UINTN) MapInfo->HostAddress,\r
+ MapInfo->NumberOfBytes\r
+ );\r
+ }\r
+\r
+ //\r
+ // The DeviceAddress is the address of the maped buffer below 4GB\r
+ //\r
+ *DeviceAddress = MapInfo->DeviceAddress;\r
+\r
+ //\r
+ // Return a pointer to the MAP_INFO structure in Mapping\r
+ //\r
+ *Mapping = MapInfo;\r
+\r
+ DEBUG ((DEBUG_VERBOSE, "%a Device 0x%Lx Host 0x%Lx Pages 0x%Lx Bytes 0x%Lx\n",\r
+ __FUNCTION__, MapInfo->DeviceAddress, MapInfo->HostAddress,\r
+ MapInfo->NumberOfPages, MapInfo->NumberOfBytes));\r
+\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+ Completes the Map() operation and releases any corresponding resources.\r
+\r
+ @param This The protocol instance pointer.\r
+ @param Mapping The mapping value returned from Map().\r
+\r
+ @retval EFI_SUCCESS The range was unmapped.\r
+ @retval EFI_INVALID_PARAMETER Mapping is not a value that was returned by Map().\r
+ @retval EFI_DEVICE_ERROR The data was not committed to the target system memory.\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+IoMmuUnmap (\r
+ IN EDKII_IOMMU_PROTOCOL *This,\r
+ IN VOID *Mapping\r
+ )\r
+{\r
+ MAP_INFO *MapInfo;\r
+ EFI_STATUS Status;\r
+\r
+ if (Mapping == NULL) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+\r
+ //\r
+ // See if the Map() operation associated with this Unmap() required a mapping\r
+ // buffer. If a mapping buffer was not required, then this function simply\r
+ // buffer. If a mapping buffer was not required, then this function simply\r
+ //\r
+ if (Mapping == NO_MAPPING) {\r
+ return EFI_SUCCESS;\r
+ }\r
+\r
+ MapInfo = (MAP_INFO *)Mapping;\r
+\r
+ //\r
+ // If this is a write operation from the Bus Master's point of view,\r
+ // then copy the contents of the mapped buffer into the real buffer\r
+ // so the processor can read the contents of the real buffer.\r
+ //\r
+ if (MapInfo->Operation == EdkiiIoMmuOperationBusMasterWrite ||\r
+ MapInfo->Operation == EdkiiIoMmuOperationBusMasterWrite64) {\r
+ CopyMem (\r
+ (VOID *) (UINTN) MapInfo->HostAddress,\r
+ (VOID *) (UINTN) MapInfo->DeviceAddress,\r
+ MapInfo->NumberOfBytes\r
+ );\r
+ }\r
+\r
+ DEBUG ((DEBUG_VERBOSE, "%a Device 0x%Lx Host 0x%Lx Pages 0x%Lx Bytes 0x%Lx\n",\r
+ __FUNCTION__, MapInfo->DeviceAddress, MapInfo->HostAddress,\r
+ MapInfo->NumberOfPages, MapInfo->NumberOfBytes));\r
+ //\r
+ // Restore the memory encryption mask\r
+ //\r
+ Status = MemEncryptSevSetPageEncMask (0, MapInfo->DeviceAddress, MapInfo->NumberOfPages, TRUE);\r
+ ASSERT_EFI_ERROR(Status);\r
+\r
+ //\r
+ // Free the mapped buffer and the MAP_INFO structure.\r
+ //\r
+ gBS->FreePages (MapInfo->DeviceAddress, MapInfo->NumberOfPages);\r
+ FreePool (Mapping);\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+ Allocates pages that are suitable for an OperationBusMasterCommonBuffer or\r
+ OperationBusMasterCommonBuffer64 mapping.\r
+\r
+ @param This The protocol instance pointer.\r
+ @param Type This parameter is not used and must be ignored.\r
+ @param MemoryType The type of memory to allocate, EfiBootServicesData\r
+ or EfiRuntimeServicesData.\r
+ @param Pages The number of pages to allocate.\r
+ @param HostAddress A pointer to store the base system memory address\r
+ of the allocated range.\r
+ @param Attributes The requested bit mask of attributes for the allocated range.\r
+\r
+ @retval EFI_SUCCESS The requested memory pages were allocated.\r
+ @retval EFI_UNSUPPORTED Attributes is unsupported. The only legal attribute\r
+ bits are MEMORY_WRITE_COMBINE and MEMORY_CACHED.\r
+ @retval EFI_INVALID_PARAMETER One or more parameters are invalid.\r
+ @retval EFI_OUT_OF_RESOURCES The memory pages could not be allocated.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+IoMmuAllocateBuffer (\r
+ IN EDKII_IOMMU_PROTOCOL *This,\r
+ IN EFI_ALLOCATE_TYPE Type,\r
+ IN EFI_MEMORY_TYPE MemoryType,\r
+ IN UINTN Pages,\r
+ IN OUT VOID **HostAddress,\r
+ IN UINT64 Attributes\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ EFI_PHYSICAL_ADDRESS PhysicalAddress;\r
+\r
+ //\r
+ // Validate Attributes\r
+ //\r
+ if ((Attributes & EDKII_IOMMU_ATTRIBUTE_INVALID_FOR_ALLOCATE_BUFFER) != 0) {\r
+ return EFI_UNSUPPORTED;\r
+ }\r
+\r
+ //\r
+ // Check for invalid inputs\r
+ //\r
+ if (HostAddress == NULL) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+\r
+ //\r
+ // The only valid memory types are EfiBootServicesData and\r
+ // EfiRuntimeServicesData\r
+ //\r
+ if (MemoryType != EfiBootServicesData &&\r
+ MemoryType != EfiRuntimeServicesData) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+\r
+ PhysicalAddress = (UINTN)-1;\r
+ if ((Attributes & EDKII_IOMMU_ATTRIBUTE_DUAL_ADDRESS_CYCLE) == 0) {\r
+ //\r
+ // Limit allocations to memory below 4GB\r
+ //\r
+ PhysicalAddress = SIZE_4GB - 1;\r
+ }\r
+ Status = gBS->AllocatePages (\r
+ AllocateMaxAddress,\r
+ MemoryType,\r
+ Pages,\r
+ &PhysicalAddress\r
+ );\r
+ if (!EFI_ERROR (Status)) {\r
+ *HostAddress = (VOID *) (UINTN) PhysicalAddress;\r
+\r
+ //\r
+ // Clear memory encryption mask\r
+ //\r
+ Status = MemEncryptSevClearPageEncMask (0, PhysicalAddress, Pages, TRUE);\r
+ ASSERT_EFI_ERROR(Status);\r
+ }\r
+\r
+ DEBUG ((DEBUG_VERBOSE, "%a Address 0x%Lx Pages 0x%Lx\n", __FUNCTION__, PhysicalAddress, Pages));\r
+ return Status;\r
+}\r
+\r
+/**\r
+ Frees memory that was allocated with AllocateBuffer().\r
+\r
+ @param This The protocol instance pointer.\r
+ @param Pages The number of pages to free.\r
+ @param HostAddress The base system memory address of the allocated range.\r
+\r
+ @retval EFI_SUCCESS The requested memory pages were freed.\r
+ @retval EFI_INVALID_PARAMETER The memory range specified by HostAddress and Pages\r
+ was not allocated with AllocateBuffer().\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+IoMmuFreeBuffer (\r
+ IN EDKII_IOMMU_PROTOCOL *This,\r
+ IN UINTN Pages,\r
+ IN VOID *HostAddress\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+\r
+ //\r
+ // Set memory encryption mask\r
+ //\r
+ Status = MemEncryptSevSetPageEncMask (0, (EFI_PHYSICAL_ADDRESS)(UINTN)HostAddress, Pages, TRUE);\r
+ ASSERT_EFI_ERROR(Status);\r
+\r
+ DEBUG ((DEBUG_VERBOSE, "%a Address 0x%Lx Pages 0x%Lx\n", __FUNCTION__, (UINTN)HostAddress, Pages));\r
+ return gBS->FreePages ((EFI_PHYSICAL_ADDRESS) (UINTN) HostAddress, Pages);\r
+}\r
+\r
+\r
+/**\r
+ Set IOMMU attribute for a system memory.\r
+\r
+ If the IOMMU protocol exists, the system memory cannot be used\r
+ for DMA by default.\r
+\r
+ When a device requests a DMA access for a system memory,\r
+ the device driver need use SetAttribute() to update the IOMMU\r
+ attribute to request DMA access (read and/or write).\r
+\r
+ The DeviceHandle is used to identify which device submits the request.\r
+ The IOMMU implementation need translate the device path to an IOMMU device ID,\r
+ and set IOMMU hardware register accordingly.\r
+ 1) DeviceHandle can be a standard PCI device.\r
+ The memory for BusMasterRead need set EDKII_IOMMU_ACCESS_READ.\r
+ The memory for BusMasterWrite need set EDKII_IOMMU_ACCESS_WRITE.\r
+ The memory for BusMasterCommonBuffer need set EDKII_IOMMU_ACCESS_READ|EDKII_IOMMU_ACCESS_WRITE.\r
+ After the memory is used, the memory need set 0 to keep it being protected.\r
+ 2) DeviceHandle can be an ACPI device (ISA, I2C, SPI, etc).\r
+ The memory for DMA access need set EDKII_IOMMU_ACCESS_READ and/or EDKII_IOMMU_ACCESS_WRITE.\r
+\r
+ @param[in] This The protocol instance pointer.\r
+ @param[in] DeviceHandle The device who initiates the DMA access request.\r
+ @param[in] Mapping The mapping value returned from Map().\r
+ @param[in] IoMmuAccess The IOMMU access.\r
+\r
+ @retval EFI_SUCCESS The IoMmuAccess is set for the memory range specified by DeviceAddress and Length.\r
+ @retval EFI_INVALID_PARAMETER DeviceHandle is an invalid handle.\r
+ @retval EFI_INVALID_PARAMETER Mapping is not a value that was returned by Map().\r
+ @retval EFI_INVALID_PARAMETER IoMmuAccess specified an illegal combination of access.\r
+ @retval EFI_UNSUPPORTED DeviceHandle is unknown by the IOMMU.\r
+ @retval EFI_UNSUPPORTED The bit mask of IoMmuAccess is not supported by the IOMMU.\r
+ @retval EFI_UNSUPPORTED The IOMMU does not support the memory range specified by Mapping.\r
+ @retval EFI_OUT_OF_RESOURCES There are not enough resources available to modify the IOMMU access.\r
+ @retval EFI_DEVICE_ERROR The IOMMU device reported an error while attempting the operation.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+IoMmuSetAttribute (\r
+ IN EDKII_IOMMU_PROTOCOL *This,\r
+ IN EFI_HANDLE DeviceHandle,\r
+ IN VOID *Mapping,\r
+ IN UINT64 IoMmuAccess\r
+ )\r
+{\r
+ return EFI_UNSUPPORTED;\r
+}\r
+\r
+EDKII_IOMMU_PROTOCOL mAmdSev = {\r
+ EDKII_IOMMU_PROTOCOL_REVISION,\r
+ IoMmuSetAttribute,\r
+ IoMmuMap,\r
+ IoMmuUnmap,\r
+ IoMmuAllocateBuffer,\r
+ IoMmuFreeBuffer,\r
+};\r
+\r
+/**\r
+ Initialize Iommu Protocol.\r
+\r
+**/\r
+VOID\r
+EFIAPI\r
+AmdSevInstallIoMmuProtocol (\r
+ VOID\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ EFI_HANDLE Handle;\r
+\r
+ Handle = NULL;\r
+ Status = gBS->InstallMultipleProtocolInterfaces (\r
+ &Handle,\r
+ &gEdkiiIoMmuProtocolGuid, &mAmdSev,\r
+ NULL\r
+ );\r
+ ASSERT_EFI_ERROR (Status);\r
+}\r