[edk2] [PATCH 12/12] OvmfPkg/IoMmuDxe: Unmap(): recycle MAP_INFO after BusMasterCommonBuffer[64]

Laszlo Ersek posted 12 patches 7 years, 4 months ago
[edk2] [PATCH 12/12] OvmfPkg/IoMmuDxe: Unmap(): recycle MAP_INFO after BusMasterCommonBuffer[64]
Posted by Laszlo Ersek 7 years, 4 months ago
In order for Unmap() to be callable from ExitBootServices() event handler
context (for cleaning up a BusMasterCommonBuffer[64] operation), we have
to completely liberate the affected path in Unmap() from dynamic memory
management.

The last remaining piece is the release of the MAP_INFO structure. Rather
than freeing it with FreePool(), recycle it to an internal list. Elements
of this "free list" can be reused for any kind of Map() operation, and can
be freed later, or recycled again.

Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Brijesh Singh <brijesh.singh@amd.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
---
 OvmfPkg/IoMmuDxe/AmdSevIoMmu.c | 48 ++++++++++++++++----
 1 file changed, 40 insertions(+), 8 deletions(-)

diff --git a/OvmfPkg/IoMmuDxe/AmdSevIoMmu.c b/OvmfPkg/IoMmuDxe/AmdSevIoMmu.c
index ee94cd4efbe2..b2c825123fbb 100644
--- a/OvmfPkg/IoMmuDxe/AmdSevIoMmu.c
+++ b/OvmfPkg/IoMmuDxe/AmdSevIoMmu.c
@@ -20,22 +20,36 @@
 
 #include "AmdSevIoMmu.h"
 
+#define MAP_INFO_SIG SIGNATURE_64 ('M', 'A', 'P', '_', 'I', 'N', 'F', 'O')
+
 typedef struct {
+  UINT64                                    Signature;
+  LIST_ENTRY                                Link;
   EDKII_IOMMU_OPERATION                     Operation;
   UINTN                                     NumberOfBytes;
   UINTN                                     NumberOfPages;
   EFI_PHYSICAL_ADDRESS                      CryptedAddress;
   EFI_PHYSICAL_ADDRESS                      PlainTextAddress;
 } MAP_INFO;
 
+//
+// List of MAP_INFO structures recycled by Unmap().
+//
+// Recycled MAP_INFO structures are equally good for future recycling and
+// freeing.
+//
+STATIC LIST_ENTRY mRecycledMapInfos = INITIALIZE_LIST_HEAD_VARIABLE (
+                                        mRecycledMapInfos
+                                        );
+
 #define COMMON_BUFFER_SIG SIGNATURE_64 ('C', 'M', 'N', 'B', 'U', 'F', 'F', 'R')
 
 //
 // The following structure enables Map() and Unmap() to perform in-place
 // decryption and encryption, respectively, for BusMasterCommonBuffer[64]
 // operations, without dynamic memory allocation or release.
 //
 // Both COMMON_BUFFER_HEADER and COMMON_BUFFER_HEADER.StashBuffer are allocated
 // by AllocateBuffer() and released by FreeBuffer().
 //
 #pragma pack (1)
@@ -89,178 +103,190 @@ 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;
+  LIST_ENTRY                                        *RecycledMapInfo;
   MAP_INFO                                          *MapInfo;
   EFI_ALLOCATE_TYPE                                 AllocateType;
   COMMON_BUFFER_HEADER                              *CommonBufferHeader;
   VOID                                              *DecryptionSource;
 
   if (HostAddress == NULL || NumberOfBytes == NULL || DeviceAddress == NULL ||
       Mapping == NULL) {
     return EFI_INVALID_PARAMETER;
   }
 
   //
   // Allocate a MAP_INFO structure to remember the mapping when Unmap() is
   // called later.
   //
-  MapInfo = AllocatePool (sizeof (MAP_INFO));
-  if (MapInfo == NULL) {
-    Status = EFI_OUT_OF_RESOURCES;
-    goto Failed;
+  RecycledMapInfo = GetFirstNode (&mRecycledMapInfos);
+  if (RecycledMapInfo == &mRecycledMapInfos) {
+    //
+    // No recycled MAP_INFO structure, allocate a new one.
+    //
+    MapInfo = AllocatePool (sizeof (MAP_INFO));
+    if (MapInfo == NULL) {
+      Status = EFI_OUT_OF_RESOURCES;
+      goto Failed;
+    }
+  } else {
+    MapInfo = CR (RecycledMapInfo, MAP_INFO, Link, MAP_INFO_SIG);
+    RemoveEntryList (RecycledMapInfo);
   }
 
   //
   // Initialize the MAP_INFO structure, except the PlainTextAddress field
   //
+  ZeroMem (&MapInfo->Link, sizeof MapInfo->Link);
+  MapInfo->Signature         = MAP_INFO_SIG;
   MapInfo->Operation         = Operation;
   MapInfo->NumberOfBytes     = *NumberOfBytes;
   MapInfo->NumberOfPages     = EFI_SIZE_TO_PAGES (MapInfo->NumberOfBytes);
   MapInfo->CryptedAddress    = (UINTN)HostAddress;
 
   //
   // In the switch statement below, we point "MapInfo->PlainTextAddress" to the
   // plaintext buffer, according to Operation. We also set "DecryptionSource".
   //
   MapInfo->PlainTextAddress = MAX_ADDRESS;
   AllocateType = AllocateAnyPages;
   DecryptionSource = (VOID *)(UINTN)MapInfo->CryptedAddress;
   switch (Operation) {
   //
   // For BusMasterRead[64] and BusMasterWrite[64] operations, a bounce buffer
   // is necessary regardless of whether the original (crypted) buffer crosses
   // the 4GB limit or not -- we have to allocate a separate plaintext buffer.
   // The only variable is whether the plaintext buffer should be under 4GB.
   //
   case EdkiiIoMmuOperationBusMasterRead:
   case EdkiiIoMmuOperationBusMasterWrite:
     MapInfo->PlainTextAddress = BASE_4GB - 1;
     AllocateType = AllocateMaxAddress;
     //
     // fall through
     //
   case EdkiiIoMmuOperationBusMasterRead64:
   case EdkiiIoMmuOperationBusMasterWrite64:
     //
     // Allocate the implicit plaintext bounce buffer.
     //
     Status = gBS->AllocatePages (
                     AllocateType,
                     EfiBootServicesData,
                     MapInfo->NumberOfPages,
                     &MapInfo->PlainTextAddress
                     );
     if (EFI_ERROR (Status)) {
       goto FreeMapInfo;
     }
     break;
 
   //
   // For BusMasterCommonBuffer[64] operations, a to-be-plaintext buffer and a
   // stash buffer (for in-place decryption) have been allocated already, with
   // AllocateBuffer(). We only check whether the address of the to-be-plaintext
   // buffer is low enough for the requested operation.
   //
   case EdkiiIoMmuOperationBusMasterCommonBuffer:
     if ((MapInfo->CryptedAddress > BASE_4GB) ||
         (EFI_PAGES_TO_SIZE (MapInfo->NumberOfPages) >
          BASE_4GB - MapInfo->CryptedAddress)) {
       //
       // CommonBuffer operations cannot be remapped. If the common buffer is
       // above 4GB, then it is not possible to generate a mapping, so return an
       // error.
       //
       Status = EFI_UNSUPPORTED;
       goto FreeMapInfo;
     }
     //
     // fall through
     //
   case EdkiiIoMmuOperationBusMasterCommonBuffer64:
     //
     // The buffer at MapInfo->CryptedAddress comes from AllocateBuffer().
     //
     MapInfo->PlainTextAddress = MapInfo->CryptedAddress;
     //
     // Stash the crypted data.
     //
     CommonBufferHeader = (COMMON_BUFFER_HEADER *)(
                            (UINTN)MapInfo->CryptedAddress - EFI_PAGE_SIZE
                            );
     ASSERT (CommonBufferHeader->Signature == COMMON_BUFFER_SIG);
     CopyMem (
       CommonBufferHeader->StashBuffer,
       (VOID *)(UINTN)MapInfo->CryptedAddress,
       MapInfo->NumberOfBytes
       );
     //
     // Point "DecryptionSource" to the stash buffer so that we decrypt
     // it to the original location, after the switch statement.
     //
     DecryptionSource = CommonBufferHeader->StashBuffer;
     break;
 
   default:
     //
     // Operation is invalid
     //
     Status = EFI_INVALID_PARAMETER;
     goto FreeMapInfo;
   }
 
   //
   // Clear the memory encryption mask on the plaintext buffer.
   //
   Status = MemEncryptSevClearPageEncMask (
              0,
              MapInfo->PlainTextAddress,
              MapInfo->NumberOfPages,
              TRUE
              );
   ASSERT_EFI_ERROR (Status);
   if (EFI_ERROR (Status)) {
     CpuDeadLoop ();
   }
 
   //
   // 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.
   //
   // For BusMasterCommonBuffer[64] operations, the CopyMem() below will decrypt
   // the original data (from the stash buffer) back to the original location.
   //
   if (Operation == EdkiiIoMmuOperationBusMasterRead ||
       Operation == EdkiiIoMmuOperationBusMasterRead64 ||
       Operation == EdkiiIoMmuOperationBusMasterCommonBuffer ||
       Operation == EdkiiIoMmuOperationBusMasterCommonBuffer64) {
     CopyMem (
       (VOID *) (UINTN) MapInfo->PlainTextAddress,
       DecryptionSource,
       MapInfo->NumberOfBytes
       );
   }
 
   //
   // Populate output parameters.
   //
   *DeviceAddress = MapInfo->PlainTextAddress;
   *Mapping       = MapInfo;
 
   DEBUG ((
     DEBUG_VERBOSE,
     "%a PlainText 0x%Lx Crypted 0x%Lx Pages 0x%Lx Bytes 0x%Lx\n",
     __FUNCTION__,
     MapInfo->PlainTextAddress,
     MapInfo->CryptedAddress,
     (UINT64)MapInfo->NumberOfPages,
     (UINT64)MapInfo->NumberOfBytes
     ));
 
   return EFI_SUCCESS;
@@ -290,132 +316,138 @@ EFIAPI
 IoMmuUnmap (
   IN  EDKII_IOMMU_PROTOCOL                     *This,
   IN  VOID                                     *Mapping
   )
 {
   MAP_INFO                 *MapInfo;
   EFI_STATUS               Status;
   COMMON_BUFFER_HEADER     *CommonBufferHeader;
   VOID                     *EncryptionTarget;
 
   if (Mapping == NULL) {
     return EFI_INVALID_PARAMETER;
   }
 
   MapInfo = (MAP_INFO *)Mapping;
 
   //
   // For BusMasterWrite[64] operations and BusMasterCommonBuffer[64] operations
   // we have to encrypt the results, ultimately to the original place (i.e.,
   // "MapInfo->CryptedAddress").
   //
   // For BusMasterCommonBuffer[64] operations however, this encryption has to
   // land in-place, so divert the encryption to the stash buffer first.
   //
   EncryptionTarget = (VOID *)(UINTN)MapInfo->CryptedAddress;
 
   switch (MapInfo->Operation) {
   case EdkiiIoMmuOperationBusMasterCommonBuffer:
   case EdkiiIoMmuOperationBusMasterCommonBuffer64:
     ASSERT (MapInfo->PlainTextAddress == MapInfo->CryptedAddress);
 
     CommonBufferHeader = (COMMON_BUFFER_HEADER *)(
                            (UINTN)MapInfo->PlainTextAddress - EFI_PAGE_SIZE
                            );
     ASSERT (CommonBufferHeader->Signature == COMMON_BUFFER_SIG);
     EncryptionTarget = CommonBufferHeader->StashBuffer;
     //
     // fall through
     //
 
   case EdkiiIoMmuOperationBusMasterWrite:
   case EdkiiIoMmuOperationBusMasterWrite64:
     CopyMem (
       EncryptionTarget,
       (VOID *) (UINTN) MapInfo->PlainTextAddress,
       MapInfo->NumberOfBytes
       );
     break;
 
   default:
     //
     // nothing to encrypt after BusMasterRead[64] operations
     //
     break;
   }
 
   DEBUG ((
     DEBUG_VERBOSE,
     "%a PlainText 0x%Lx Crypted 0x%Lx Pages 0x%Lx Bytes 0x%Lx\n",
     __FUNCTION__,
     MapInfo->PlainTextAddress,
     MapInfo->CryptedAddress,
     (UINT64)MapInfo->NumberOfPages,
     (UINT64)MapInfo->NumberOfBytes
     ));
 
   //
   // Restore the memory encryption mask on the area we used to hold the
   // plaintext.
   //
   Status = MemEncryptSevSetPageEncMask (
              0,
              MapInfo->PlainTextAddress,
              MapInfo->NumberOfPages,
              TRUE
              );
   ASSERT_EFI_ERROR (Status);
   if (EFI_ERROR (Status)) {
     CpuDeadLoop ();
   }
 
   //
   // For BusMasterCommonBuffer[64] operations, copy the stashed data to the
   // original (now encrypted) location.
   //
   // For all other operations, fill the late bounce buffer (which existed as
   // plaintext at some point) with zeros, and then release it.
   //
   if (MapInfo->Operation == EdkiiIoMmuOperationBusMasterCommonBuffer ||
       MapInfo->Operation == EdkiiIoMmuOperationBusMasterCommonBuffer64) {
     CopyMem (
       (VOID *)(UINTN)MapInfo->CryptedAddress,
       CommonBufferHeader->StashBuffer,
       MapInfo->NumberOfBytes
       );
+
+    //
+    // Recycle the MAP_INFO structure.
+    //
+    InsertTailList (&mRecycledMapInfos, &MapInfo->Link);
   } else {
     ZeroMem (
       (VOID *)(UINTN)MapInfo->PlainTextAddress,
       EFI_PAGES_TO_SIZE (MapInfo->NumberOfPages)
       );
     gBS->FreePages (MapInfo->PlainTextAddress, MapInfo->NumberOfPages);
+
+    //
+    // Free the MAP_INFO structure.
+    //
+    FreePool (MapInfo);
   }
 
-  //
-  // Free the MAP_INFO structure.
-  //
-  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 and
                                 MEMORY_CACHED.
   @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
   @retval EFI_OUT_OF_RESOURCES  The memory pages could not be allocated.
 
 **/
-- 
2.13.1.3.g8be5a757fa67

_______________________________________________
edk2-devel mailing list
edk2-devel@lists.01.org
https://lists.01.org/mailman/listinfo/edk2-devel