]> git.proxmox.com Git - mirror_edk2.git/blobdiff - ArmPkg/Library/ArmDmaLib/ArmDmaLib.c
EmbeddedPkg: MmcDxe - Recieve response was missing after CMD12
[mirror_edk2.git] / ArmPkg / Library / ArmDmaLib / ArmDmaLib.c
old mode 100755 (executable)
new mode 100644 (file)
index 12b1940..2a8cf0f
@@ -2,6 +2,7 @@
   Generic ARM implementation of DmaLib.h\r
 \r
   Copyright (c) 2008 - 2010, Apple Inc. All rights reserved.<BR>\r
+  Copyright (c) 2015 - 2017, Linaro, Ltd. All rights reserved.<BR>\r
 \r
   This program and the accompanying materials\r
   are licensed and made available under the terms and conditions of the BSD License\r
 **/\r
 \r
 #include <PiDxe.h>\r
+#include <Library/BaseLib.h>\r
 #include <Library/DebugLib.h>\r
 #include <Library/DmaLib.h>\r
 #include <Library/DxeServicesTableLib.h>\r
 #include <Library/MemoryAllocationLib.h>\r
 #include <Library/UefiBootServicesTableLib.h>\r
-#include <Library/UncachedMemoryAllocationLib.h>\r
 #include <Library/IoLib.h>\r
 #include <Library/BaseMemoryLib.h>\r
-#include <Library/ArmLib.h>\r
 \r
 #include <Protocol/Cpu.h>\r
 \r
 typedef struct {\r
   EFI_PHYSICAL_ADDRESS      HostAddress;\r
-  EFI_PHYSICAL_ADDRESS      DeviceAddress;\r
+  VOID                      *BufferAddress;\r
   UINTN                     NumberOfBytes;\r
   DMA_MAP_OPERATION         Operation;\r
   BOOLEAN                   DoubleBuffer;\r
 } MAP_INFO_INSTANCE;\r
 \r
 \r
-\r
-EFI_CPU_ARCH_PROTOCOL      *gCpu;\r
-UINTN                      gCacheAlignment = 0;\r
+typedef struct {\r
+  LIST_ENTRY          Link;\r
+  VOID                *HostAddress;\r
+  UINTN               NumPages;\r
+  UINT64              Attributes;\r
+} UNCACHED_ALLOCATION;\r
+\r
+STATIC EFI_CPU_ARCH_PROTOCOL      *mCpu;\r
+STATIC LIST_ENTRY                 UncachedAllocationList;\r
+\r
+STATIC\r
+PHYSICAL_ADDRESS\r
+HostToDeviceAddress (\r
+  IN  VOID      *Address\r
+  )\r
+{\r
+  return (PHYSICAL_ADDRESS)(UINTN)Address + PcdGet64 (PcdArmDmaDeviceOffset);\r
+}\r
 \r
 /**\r
   Provides the DMA controller-specific addresses needed to access system memory.\r
@@ -73,6 +88,7 @@ DmaMap (
   MAP_INFO_INSTANCE               *Map;\r
   VOID                            *Buffer;\r
   EFI_GCD_MEMORY_SPACE_DESCRIPTOR GcdDescriptor;\r
+  UINTN                           AllocSize;\r
 \r
   if (HostAddress == NULL || NumberOfBytes == NULL || DeviceAddress == NULL || Mapping == NULL ) {\r
     return EFI_INVALID_PARAMETER;\r
@@ -82,7 +98,7 @@ DmaMap (
     return EFI_INVALID_PARAMETER;\r
   }\r
 \r
-  *DeviceAddress = ConvertToPhysicalAddress (HostAddress);\r
+  *DeviceAddress = HostToDeviceAddress (HostAddress);\r
 \r
   // Remember range so we can flush on the other side\r
   Map = AllocatePool (sizeof (MAP_INFO_INSTANCE));\r
@@ -90,58 +106,97 @@ DmaMap (
     return  EFI_OUT_OF_RESOURCES;\r
   }\r
 \r
-  *Mapping = Map;\r
-\r
-  if ((((UINTN)HostAddress & (gCacheAlignment - 1)) != 0) ||\r
-      ((*NumberOfBytes % gCacheAlignment) != 0)) {\r
+  if (Operation != MapOperationBusMasterRead &&\r
+      ((((UINTN)HostAddress & (mCpu->DmaBufferAlignment - 1)) != 0) ||\r
+       ((*NumberOfBytes & (mCpu->DmaBufferAlignment - 1)) != 0))) {\r
 \r
     // Get the cacheability of the region\r
-    Status = gDS->GetMemorySpaceDescriptor (*DeviceAddress, &GcdDescriptor);\r
+    Status = gDS->GetMemorySpaceDescriptor ((UINTN)HostAddress, &GcdDescriptor);\r
     if (EFI_ERROR(Status)) {\r
-      return Status;\r
+      goto FreeMapInfo;\r
     }\r
 \r
     // If the mapped buffer is not an uncached buffer\r
-    if ( (GcdDescriptor.Attributes != EFI_MEMORY_WC) &&\r
-         (GcdDescriptor.Attributes != EFI_MEMORY_UC) )\r
-    {\r
+    if ((GcdDescriptor.Attributes & (EFI_MEMORY_WB | EFI_MEMORY_WT)) != 0) {\r
       //\r
-      // If the buffer does not fill entire cache lines we must double buffer into\r
-      // uncached memory. Device (PCI) address becomes uncached page.\r
+      // Operations of type MapOperationBusMasterCommonBuffer are only allowed\r
+      // on uncached buffers.\r
       //\r
-      Map->DoubleBuffer  = TRUE;\r
-      Status = DmaAllocateBuffer (EfiBootServicesData, EFI_SIZE_TO_PAGES (*NumberOfBytes), &Buffer);\r
-      if (EFI_ERROR (Status)) {\r
-        return Status;\r
+      if (Operation == MapOperationBusMasterCommonBuffer) {\r
+        DEBUG ((EFI_D_ERROR,\r
+          "%a: Operation type 'MapOperationBusMasterCommonBuffer' is only supported\n"\r
+          "on memory regions that were allocated using DmaAllocateBuffer ()\n",\r
+          __FUNCTION__));\r
+        Status = EFI_UNSUPPORTED;\r
+        goto FreeMapInfo;\r
       }\r
 \r
-      if ((Operation == MapOperationBusMasterRead) || (Operation == MapOperationBusMasterCommonBuffer)) {\r
-        CopyMem (Buffer, HostAddress, *NumberOfBytes);\r
+      //\r
+      // If the buffer does not fill entire cache lines we must double buffer\r
+      // into a suitably aligned allocation that allows us to invalidate the\r
+      // cache without running the risk of corrupting adjacent unrelated data.\r
+      // Note that pool allocations are guaranteed to be 8 byte aligned, so\r
+      // we only have to add (alignment - 8) worth of padding.\r
+      //\r
+      Map->DoubleBuffer = TRUE;\r
+      AllocSize = ALIGN_VALUE (*NumberOfBytes, mCpu->DmaBufferAlignment) +\r
+                  (mCpu->DmaBufferAlignment - 8);\r
+      Map->BufferAddress = AllocatePool (AllocSize);\r
+      if (Map->BufferAddress == NULL) {\r
+        Status = EFI_OUT_OF_RESOURCES;\r
+        goto FreeMapInfo;\r
       }\r
 \r
-      *DeviceAddress = (PHYSICAL_ADDRESS)(UINTN)Buffer;\r
+      Buffer = ALIGN_POINTER (Map->BufferAddress, mCpu->DmaBufferAlignment);\r
+      *DeviceAddress = HostToDeviceAddress (Buffer);\r
+\r
+      //\r
+      // Get rid of any dirty cachelines covering the double buffer. This\r
+      // prevents them from being written back unexpectedly, potentially\r
+      // overwriting the data we receive from the device.\r
+      //\r
+      mCpu->FlushDataCache (mCpu, (UINTN)Buffer, *NumberOfBytes,\r
+              EfiCpuFlushTypeWriteBack);\r
     } else {\r
       Map->DoubleBuffer  = FALSE;\r
     }\r
   } else {\r
     Map->DoubleBuffer  = FALSE;\r
 \r
-    // Flush the Data Cache (should not have any effect if the memory region is uncached)\r
-    gCpu->FlushDataCache (gCpu, *DeviceAddress, *NumberOfBytes, EfiCpuFlushTypeWriteBackInvalidate);\r
+    DEBUG_CODE_BEGIN ();\r
 \r
-    if ((Operation == MapOperationBusMasterRead) || (Operation == MapOperationBusMasterCommonBuffer)) {\r
-      // In case the buffer is used for instance to send command to a PCI controller, we must ensure the memory is uncached\r
-      Status = gDS->SetMemorySpaceAttributes (*DeviceAddress & ~(BASE_4KB - 1), ALIGN_VALUE (*NumberOfBytes, BASE_4KB), EFI_MEMORY_WC);\r
-      ASSERT_EFI_ERROR (Status);\r
-    }\r
+    //\r
+    // The operation type check above only executes if the buffer happens to be\r
+    // misaligned with respect to CWG, but even if it is aligned, we should not\r
+    // allow arbitrary buffers to be used for creating consistent mappings.\r
+    // So duplicate the check here when running in DEBUG mode, just to assert\r
+    // that we are not trying to create a consistent mapping for cached memory.\r
+    //\r
+    Status = gDS->GetMemorySpaceDescriptor ((UINTN)HostAddress, &GcdDescriptor);\r
+    ASSERT_EFI_ERROR(Status);\r
+\r
+    ASSERT (Operation != MapOperationBusMasterCommonBuffer ||\r
+            (GcdDescriptor.Attributes & (EFI_MEMORY_WB | EFI_MEMORY_WT)) == 0);\r
+\r
+    DEBUG_CODE_END ();\r
+\r
+    // Flush the Data Cache (should not have any effect if the memory region is uncached)\r
+    mCpu->FlushDataCache (mCpu, (UINTN)HostAddress, *NumberOfBytes,\r
+            EfiCpuFlushTypeWriteBackInvalidate);\r
   }\r
 \r
   Map->HostAddress   = (UINTN)HostAddress;\r
-  Map->DeviceAddress = *DeviceAddress;\r
   Map->NumberOfBytes = *NumberOfBytes;\r
   Map->Operation     = Operation;\r
 \r
+  *Mapping = Map;\r
+\r
   return EFI_SUCCESS;\r
+\r
+FreeMapInfo:\r
+  FreePool (Map);\r
+\r
+  return Status;\r
 }\r
 \r
 \r
@@ -153,6 +208,8 @@ DmaMap (
 \r
   @retval EFI_SUCCESS           The range was unmapped.\r
   @retval EFI_DEVICE_ERROR      The data was not committed to the target system memory.\r
+  @retval EFI_INVALID_PARAMETER An inconsistency was detected between the mapping type\r
+                                and the DoubleBuffer field\r
 \r
 **/\r
 EFI_STATUS\r
@@ -162,6 +219,8 @@ DmaUnmap (
   )\r
 {\r
   MAP_INFO_INSTANCE *Map;\r
+  EFI_STATUS        Status;\r
+  VOID              *Buffer;\r
 \r
   if (Mapping == NULL) {\r
     ASSERT (FALSE);\r
@@ -170,25 +229,35 @@ DmaUnmap (
 \r
   Map = (MAP_INFO_INSTANCE *)Mapping;\r
 \r
+  Status = EFI_SUCCESS;\r
   if (Map->DoubleBuffer) {\r
-    if ((Map->Operation == MapOperationBusMasterWrite) || (Map->Operation == MapOperationBusMasterCommonBuffer)) {\r
-      CopyMem ((VOID *)(UINTN)Map->HostAddress, (VOID *)(UINTN)Map->DeviceAddress, Map->NumberOfBytes);\r
-    }\r
+    ASSERT (Map->Operation == MapOperationBusMasterWrite);\r
 \r
-    DmaFreeBuffer (EFI_SIZE_TO_PAGES (Map->NumberOfBytes), (VOID *)(UINTN)Map->DeviceAddress);\r
+    if (Map->Operation != MapOperationBusMasterWrite) {\r
+      Status = EFI_INVALID_PARAMETER;\r
+    } else {\r
+      Buffer = ALIGN_POINTER (Map->BufferAddress, mCpu->DmaBufferAlignment);\r
+\r
+      mCpu->FlushDataCache (mCpu, (UINTN)Buffer, Map->NumberOfBytes,\r
+              EfiCpuFlushTypeInvalidate);\r
 \r
+      CopyMem ((VOID *)(UINTN)Map->HostAddress, Buffer, Map->NumberOfBytes);\r
+\r
+      FreePool (Map->BufferAddress);\r
+    }\r
   } else {\r
     if (Map->Operation == MapOperationBusMasterWrite) {\r
       //\r
       // Make sure we read buffer from uncached memory and not the cache\r
       //\r
-      gCpu->FlushDataCache (gCpu, Map->HostAddress, Map->NumberOfBytes, EfiCpuFlushTypeInvalidate);\r
+      mCpu->FlushDataCache (mCpu, Map->HostAddress, Map->NumberOfBytes,\r
+              EfiCpuFlushTypeInvalidate);\r
     }\r
   }\r
 \r
   FreePool (Map);\r
 \r
-  return EFI_SUCCESS;\r
+  return Status;\r
 }\r
 \r
 /**\r
@@ -201,7 +270,7 @@ DmaUnmap (
   @param  HostAddress           A pointer to store the base system memory address of the\r
                                 allocated range.\r
 \r
-                                @retval EFI_SUCCESS           The requested memory pages were allocated.\r
+  @retval EFI_SUCCESS           The requested memory pages were allocated.\r
   @retval EFI_UNSUPPORTED       Attributes is unsupported. The only legal attribute bits are\r
                                 MEMORY_WRITE_COMBINE and MEMORY_CACHED.\r
   @retval EFI_INVALID_PARAMETER One or more parameters are invalid.\r
@@ -216,24 +285,118 @@ DmaAllocateBuffer (
   OUT VOID                         **HostAddress\r
   )\r
 {\r
-  if (HostAddress == NULL) {\r
+  return DmaAllocateAlignedBuffer (MemoryType, Pages, 0, HostAddress);\r
+}\r
+\r
+/**\r
+  Allocates pages that are suitable for an DmaMap() of type\r
+  MapOperationBusMasterCommonBuffer mapping, at the requested alignment.\r
+\r
+  @param  MemoryType            The type of memory to allocate, EfiBootServicesData or\r
+                                EfiRuntimeServicesData.\r
+  @param  Pages                 The number of pages to allocate.\r
+  @param  Alignment             Alignment in bytes of the base of the returned\r
+                                buffer (must be a power of 2)\r
+  @param  HostAddress           A pointer to store the base system memory address of the\r
+                                allocated range.\r
+\r
+  @retval EFI_SUCCESS           The requested memory pages were allocated.\r
+  @retval EFI_UNSUPPORTED       Attributes is unsupported. The only legal attribute bits are\r
+                                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
+DmaAllocateAlignedBuffer (\r
+  IN  EFI_MEMORY_TYPE              MemoryType,\r
+  IN  UINTN                        Pages,\r
+  IN  UINTN                        Alignment,\r
+  OUT VOID                         **HostAddress\r
+  )\r
+{\r
+  EFI_GCD_MEMORY_SPACE_DESCRIPTOR   GcdDescriptor;\r
+  VOID                              *Allocation;\r
+  UINT64                            MemType;\r
+  UNCACHED_ALLOCATION               *Alloc;\r
+  EFI_STATUS                        Status;\r
+\r
+  if (Alignment == 0) {\r
+    Alignment = EFI_PAGE_SIZE;\r
+  }\r
+\r
+  if (HostAddress == NULL ||\r
+      (Alignment & (Alignment - 1)) != 0) {\r
     return EFI_INVALID_PARAMETER;\r
   }\r
 \r
-  //\r
-  // The only valid memory types are EfiBootServicesData and EfiRuntimeServicesData\r
-  //\r
-  // We used uncached memory to keep coherency\r
-  //\r
   if (MemoryType == EfiBootServicesData) {\r
-    *HostAddress = UncachedAllocatePages (Pages);\r
+    Allocation = AllocateAlignedPages (Pages, Alignment);\r
   } else if (MemoryType == EfiRuntimeServicesData) {\r
-    *HostAddress = UncachedAllocateRuntimePages (Pages);\r
+    Allocation = AllocateAlignedRuntimePages (Pages, Alignment);\r
   } else {\r
     return EFI_INVALID_PARAMETER;\r
   }\r
 \r
+  if (Allocation == NULL) {\r
+    return EFI_OUT_OF_RESOURCES;\r
+  }\r
+\r
+  // Get the cacheability of the region\r
+  Status = gDS->GetMemorySpaceDescriptor ((UINTN)Allocation, &GcdDescriptor);\r
+  if (EFI_ERROR(Status)) {\r
+    goto FreeBuffer;\r
+  }\r
+\r
+  // Choose a suitable uncached memory type that is supported by the region\r
+  if (GcdDescriptor.Capabilities & EFI_MEMORY_WC) {\r
+    MemType = EFI_MEMORY_WC;\r
+  } else if (GcdDescriptor.Capabilities & EFI_MEMORY_UC) {\r
+    MemType = EFI_MEMORY_UC;\r
+  } else {\r
+    Status = EFI_UNSUPPORTED;\r
+    goto FreeBuffer;\r
+  }\r
+\r
+  Alloc = AllocatePool (sizeof *Alloc);\r
+  if (Alloc == NULL) {\r
+    goto FreeBuffer;\r
+  }\r
+\r
+  Alloc->HostAddress = Allocation;\r
+  Alloc->NumPages = Pages;\r
+  Alloc->Attributes = GcdDescriptor.Attributes;\r
+\r
+  InsertHeadList (&UncachedAllocationList, &Alloc->Link);\r
+\r
+  // Remap the region with the new attributes\r
+  Status = gDS->SetMemorySpaceAttributes ((PHYSICAL_ADDRESS)(UINTN)Allocation,\r
+                                          EFI_PAGES_TO_SIZE (Pages),\r
+                                          MemType);\r
+  if (EFI_ERROR (Status)) {\r
+    goto FreeAlloc;\r
+  }\r
+\r
+  Status = mCpu->FlushDataCache (mCpu,\r
+                                 (PHYSICAL_ADDRESS)(UINTN)Allocation,\r
+                                 EFI_PAGES_TO_SIZE (Pages),\r
+                                 EfiCpuFlushTypeInvalidate);\r
+  if (EFI_ERROR (Status)) {\r
+    goto FreeAlloc;\r
+  }\r
+\r
+  *HostAddress = Allocation;\r
+\r
   return EFI_SUCCESS;\r
+\r
+FreeAlloc:\r
+  RemoveEntryList (&Alloc->Link);\r
+  FreePool (Alloc);\r
+\r
+FreeBuffer:\r
+  FreePages (Allocation, Pages);\r
+  return Status;\r
 }\r
 \r
 \r
@@ -255,12 +418,49 @@ DmaFreeBuffer (
   IN  VOID                         *HostAddress\r
   )\r
 {\r
+  LIST_ENTRY                       *Link;\r
+  UNCACHED_ALLOCATION              *Alloc;\r
+  BOOLEAN                          Found;\r
+  EFI_STATUS                       Status;\r
+\r
   if (HostAddress == NULL) {\r
      return EFI_INVALID_PARAMETER;\r
   }\r
 \r
-  UncachedFreePages (HostAddress, Pages);\r
-  return EFI_SUCCESS;\r
+  for (Link = GetFirstNode (&UncachedAllocationList), Found = FALSE;\r
+       !IsNull (&UncachedAllocationList, Link);\r
+       Link = GetNextNode (&UncachedAllocationList, Link)) {\r
+\r
+    Alloc = BASE_CR (Link, UNCACHED_ALLOCATION, Link);\r
+    if (Alloc->HostAddress == HostAddress && Alloc->NumPages == Pages) {\r
+      Found = TRUE;\r
+      break;\r
+    }\r
+  }\r
+\r
+  if (!Found) {\r
+    ASSERT (FALSE);\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  RemoveEntryList (&Alloc->Link);\r
+\r
+  Status = gDS->SetMemorySpaceAttributes ((PHYSICAL_ADDRESS)(UINTN)HostAddress,\r
+                                          EFI_PAGES_TO_SIZE (Pages),\r
+                                          Alloc->Attributes);\r
+  if (EFI_ERROR (Status)) {\r
+    goto FreeAlloc;\r
+  }\r
+\r
+  //\r
+  // If we fail to restore the original attributes, it is better to leak the\r
+  // memory than to return it to the heap\r
+  //\r
+  FreePages (HostAddress, Pages);\r
+\r
+FreeAlloc:\r
+  FreePool (Alloc);\r
+  return Status;\r
 }\r
 \r
 \r
@@ -271,14 +471,8 @@ ArmDmaLibConstructor (
   IN EFI_SYSTEM_TABLE *SystemTable\r
   )\r
 {\r
-  EFI_STATUS              Status;\r
+  InitializeListHead (&UncachedAllocationList);\r
 \r
   // Get the Cpu protocol for later use\r
-  Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **)&gCpu);\r
-  ASSERT_EFI_ERROR(Status);\r
-\r
-  gCacheAlignment = ArmDataCacheLineLength ();\r
-\r
-  return Status;\r
+  return gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **)&mCpu);\r
 }\r
-\r