public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
From: "Ard Biesheuvel" <ard.biesheuvel@linaro.org>
To: devel@edk2.groups.io
Cc: leif.lindholm@linaro.org, Ard Biesheuvel <ard.biesheuvel@linaro.org>
Subject: [PATCH 1/2] EmbeddedPkg/NonCoherentDmaLib: implement support for DMA range limits
Date: Thu, 21 Nov 2019 09:32:26 +0100	[thread overview]
Message-ID: <20191121083227.2850-2-ard.biesheuvel@linaro.org> (raw)
In-Reply-To: <20191121083227.2850-1-ard.biesheuvel@linaro.org>

Implement support for driving peripherals with limited DMA ranges to
NonCoherentDmaLib, by adding a device address limit, and taking it,
along with the device offset, into account when allocating or mapping
DMA buffers.

Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
---
 EmbeddedPkg/EmbeddedPkg.dec                                 |   6 +
 EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.c   | 176 +++++++++++++++++---
 EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.inf |   1 +
 3 files changed, 158 insertions(+), 25 deletions(-)

diff --git a/EmbeddedPkg/EmbeddedPkg.dec b/EmbeddedPkg/EmbeddedPkg.dec
index 8812a6db7c30..69922802f473 100644
--- a/EmbeddedPkg/EmbeddedPkg.dec
+++ b/EmbeddedPkg/EmbeddedPkg.dec
@@ -186,6 +186,12 @@ [PcdsFixedAtBuild.common, PcdsDynamic.common]
   #
   gEmbeddedTokenSpaceGuid.PcdDmaDeviceOffset|0x0|UINT64|0x0000058
 
+  #
+  # Highest address value supported by the device for DMA addressing. Note
+  # that this value should be strictly greater than PcdDmaDeviceOffset.
+  #
+  gEmbeddedTokenSpaceGuid.PcdDmaDeviceLimit|0xFFFFFFFFFFFFFFFF|UINT64|0x000005A
+
   #
   # Selection between DT and ACPI as a default
   #
diff --git a/EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.c b/EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.c
index 78220f6358aa..dd3d111e7eb9 100644
--- a/EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.c
+++ b/EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.c
@@ -40,6 +40,8 @@ typedef struct {
 STATIC EFI_CPU_ARCH_PROTOCOL      *mCpu;
 STATIC LIST_ENTRY                 UncachedAllocationList;
 
+STATIC PHYSICAL_ADDRESS           mDmaHostAddressLimit;
+
 STATIC
 PHYSICAL_ADDRESS
 HostToDeviceAddress (
@@ -49,6 +51,102 @@ HostToDeviceAddress (
   return (PHYSICAL_ADDRESS)(UINTN)Address + PcdGet64 (PcdDmaDeviceOffset);
 }
 
+/**
+  Allocates one or more 4KB pages of a certain memory type at a specified
+  alignment.
+
+  Allocates the number of 4KB pages specified by Pages of a certain memory type
+  with an alignment specified by Alignment. The allocated buffer is returned.
+  If Pages is 0, then NULL is returned. If there is not enough memory at the
+  specified alignment remaining to satisfy the request, then NULL is returned.
+  If Alignment is not a power of two and Alignment is not zero, then ASSERT().
+  If Pages plus EFI_SIZE_TO_PAGES (Alignment) overflows, then ASSERT().
+
+  @param  MemoryType            The type of memory to allocate.
+  @param  Pages                 The number of 4 KB pages to allocate.
+  @param  Alignment             The requested alignment of the allocation.
+                                Must be a power of two.
+                                If Alignment is zero, then byte alignment is
+                                used.
+
+  @return A pointer to the allocated buffer or NULL if allocation fails.
+
+**/
+STATIC
+VOID *
+InternalAllocateAlignedPages (
+  IN EFI_MEMORY_TYPE  MemoryType,
+  IN UINTN            Pages,
+  IN UINTN            Alignment
+  )
+{
+  EFI_STATUS            Status;
+  EFI_PHYSICAL_ADDRESS  Memory;
+  UINTN                 AlignedMemory;
+  UINTN                 AlignmentMask;
+  UINTN                 UnalignedPages;
+  UINTN                 RealPages;
+
+  //
+  // Alignment must be a power of two or zero.
+  //
+  ASSERT ((Alignment & (Alignment - 1)) == 0);
+
+  if (Pages == 0) {
+    return NULL;
+  }
+  if (Alignment > EFI_PAGE_SIZE) {
+    //
+    // Calculate the total number of pages since alignment is larger than page
+    // size.
+    //
+    AlignmentMask  = Alignment - 1;
+    RealPages      = Pages + EFI_SIZE_TO_PAGES (Alignment);
+    //
+    // Make sure that Pages plus EFI_SIZE_TO_PAGES (Alignment) does not
+    // overflow.
+    //
+    ASSERT (RealPages > Pages);
+
+    Memory = mDmaHostAddressLimit;
+    Status = gBS->AllocatePages (AllocateMaxAddress, MemoryType, RealPages,
+                    &Memory);
+    if (EFI_ERROR (Status)) {
+      return NULL;
+    }
+    AlignedMemory  = ((UINTN)Memory + AlignmentMask) & ~AlignmentMask;
+    UnalignedPages = EFI_SIZE_TO_PAGES (AlignedMemory - (UINTN)Memory);
+    if (UnalignedPages > 0) {
+      //
+      // Free first unaligned page(s).
+      //
+      Status = gBS->FreePages (Memory, UnalignedPages);
+      ASSERT_EFI_ERROR (Status);
+    }
+    Memory         = AlignedMemory + EFI_PAGES_TO_SIZE (Pages);
+    UnalignedPages = RealPages - Pages - UnalignedPages;
+    if (UnalignedPages > 0) {
+      //
+      // Free last unaligned page(s).
+      //
+      Status = gBS->FreePages (Memory, UnalignedPages);
+      ASSERT_EFI_ERROR (Status);
+    }
+  } else {
+    //
+    // Do not over-allocate pages in this case.
+    //
+    Memory = mDmaHostAddressLimit;
+    Status = gBS->AllocatePages (AllocateMaxAddress, MemoryType, Pages,
+                    &Memory);
+    if (EFI_ERROR (Status)) {
+      return NULL;
+    }
+    AlignedMemory = (UINTN)Memory;
+  }
+  return (VOID *)AlignedMemory;
+}
+
 /**
   Provides the DMA controller-specific addresses needed to access system memory.
 
@@ -111,7 +209,22 @@ DmaMap (
     return  EFI_OUT_OF_RESOURCES;
   }
 
-  if (Operation != MapOperationBusMasterRead &&
+  if (((UINTN)HostAddress + *NumberOfBytes) > mDmaHostAddressLimit) {
+
+    if (Operation == MapOperationBusMasterCommonBuffer) {
+      goto CommonBufferError;
+    }
+
+    AllocSize = ALIGN_VALUE (*NumberOfBytes, mCpu->DmaBufferAlignment);
+    Map->BufferAddress = InternalAllocateAlignedPages (EfiBootServicesData,
+                           EFI_SIZE_TO_PAGES (AllocSize),
+                           mCpu->DmaBufferAlignment);
+    if (Map->BufferAddress == NULL) {
+      Status = EFI_OUT_OF_RESOURCES;
+      goto FreeMapInfo;
+    }
+    *DeviceAddress = HostToDeviceAddress (Map->BufferAddress);
+  } else if (Operation != MapOperationBusMasterRead &&
       ((((UINTN)HostAddress & (mCpu->DmaBufferAlignment - 1)) != 0) ||
        ((*NumberOfBytes & (mCpu->DmaBufferAlignment - 1)) != 0))) {
 
@@ -128,12 +241,7 @@ DmaMap (
       // 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;
+        goto CommonBufferError;
       }
 
       //
@@ -154,14 +262,6 @@ DmaMap (
 
       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;
     }
@@ -184,13 +284,13 @@ DmaMap (
             (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);
   }
 
+  // Flush the Data Cache (should not have any effect if the memory region is
+  // uncached)
+  mCpu->FlushDataCache (mCpu, (UINTN)HostAddress, *NumberOfBytes,
+          EfiCpuFlushTypeWriteBack);
+
   Map->HostAddress   = (UINTN)HostAddress;
   Map->NumberOfBytes = *NumberOfBytes;
   Map->Operation     = Operation;
@@ -199,6 +299,12 @@ DmaMap (
 
   return EFI_SUCCESS;
 
+CommonBufferError:
+  DEBUG ((DEBUG_ERROR,
+    "%a: Operation type 'MapOperationBusMasterCommonBuffer' is only "
+    "supported\non memory regions that were allocated using "
+    "DmaAllocateBuffer ()\n", __FUNCTION__));
+  Status = EFI_UNSUPPORTED;
 FreeMapInfo:
   FreePool (Map);
 
@@ -229,6 +335,7 @@ DmaUnmap (
   MAP_INFO_INSTANCE *Map;
   EFI_STATUS        Status;
   VOID              *Buffer;
+  UINTN             AllocSize;
 
   if (Mapping == NULL) {
     ASSERT (FALSE);
@@ -238,7 +345,18 @@ DmaUnmap (
   Map = (MAP_INFO_INSTANCE *)Mapping;
 
   Status = EFI_SUCCESS;
-  if (Map->DoubleBuffer) {
+  if (((UINTN)Map->HostAddress + Map->NumberOfBytes) > mDmaHostAddressLimit) {
+
+    mCpu->FlushDataCache (mCpu, (UINTN)Map->BufferAddress, Map->NumberOfBytes,
+            EfiCpuFlushTypeInvalidate);
+
+    CopyMem ((VOID *)(UINTN)Map->HostAddress, Map->BufferAddress,
+      Map->NumberOfBytes);
+
+    AllocSize = ALIGN_VALUE (Map->NumberOfBytes, mCpu->DmaBufferAlignment);
+    FreePages (Map->BufferAddress, EFI_SIZE_TO_PAGES (AllocSize));
+  } else if (Map->DoubleBuffer) {
+
     ASSERT (Map->Operation == MapOperationBusMasterWrite);
 
     if (Map->Operation != MapOperationBusMasterWrite) {
@@ -335,10 +453,9 @@ DmaAllocateAlignedBuffer (
     return EFI_INVALID_PARAMETER;
   }
 
-  if (MemoryType == EfiBootServicesData) {
-    Allocation = AllocateAlignedPages (Pages, Alignment);
-  } else if (MemoryType == EfiRuntimeServicesData) {
-    Allocation = AllocateAlignedRuntimePages (Pages, Alignment);
+  if (MemoryType == EfiBootServicesData ||
+      MemoryType == EfiRuntimeServicesData) {
+    Allocation = InternalAllocateAlignedPages (MemoryType, Pages, Alignment);
   } else {
     return EFI_INVALID_PARAMETER;
   }
@@ -479,6 +596,15 @@ NonCoherentDmaLibConstructor (
 {
   InitializeListHead (&UncachedAllocationList);
 
+  //
+  // Ensure that the combination of DMA addressing offset and limit produces
+  // a sane value.
+  //
+  ASSERT (PcdGet64 (PcdDmaDeviceLimit) > PcdGet64 (PcdDmaDeviceOffset));
+
+  mDmaHostAddressLimit = PcdGet64 (PcdDmaDeviceLimit) -
+                         PcdGet64 (PcdDmaDeviceOffset);
+
   // Get the Cpu protocol for later use
   return gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **)&mCpu);
 }
diff --git a/EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.inf b/EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.inf
index 2db751afee91..1a21cfe4ff23 100644
--- a/EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.inf
+++ b/EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.inf
@@ -38,6 +38,7 @@ [Protocols]
 
 [Pcd]
   gEmbeddedTokenSpaceGuid.PcdDmaDeviceOffset
+  gEmbeddedTokenSpaceGuid.PcdDmaDeviceLimit
 
 [Depex]
   gEfiCpuArchProtocolGuid
-- 
2.17.1


  reply	other threads:[~2019-11-21  8:32 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-11-21  8:32 [PATCH 0/2] EmbeddedPkg: support for RPi4 PCI and platform DMA Ard Biesheuvel
2019-11-21  8:32 ` Ard Biesheuvel [this message]
2019-11-25 12:46   ` [PATCH 1/2] EmbeddedPkg/NonCoherentDmaLib: implement support for DMA range limits Leif Lindholm
2019-11-25 12:52     ` Ard Biesheuvel
2019-11-25 12:58       ` Leif Lindholm
2019-11-25 13:13         ` Ard Biesheuvel
2019-11-25 14:38           ` Leif Lindholm
2019-11-21  8:32 ` [PATCH 2/2] EmbeddedPkg: implement EDK2 IoMmu protocol wrapping DmaLib Ard Biesheuvel
2019-11-25 12:49   ` Leif Lindholm
2019-11-25 12:53     ` Ard Biesheuvel

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-list from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20191121083227.2850-2-ard.biesheuvel@linaro.org \
    --to=devel@edk2.groups.io \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox