MdeModulePkg/NonDiscoverablePciDeviceDxe: add support for non-coherent DMA
authorArd Biesheuvel <ard.biesheuvel@linaro.org>
Fri, 9 Dec 2016 15:04:34 +0000 (15:04 +0000)
committerArd Biesheuvel <ard.biesheuvel@linaro.org>
Thu, 15 Dec 2016 08:20:33 +0000 (08:20 +0000)
Add support for non-coherent DMA, either by performing explicit cache
maintenance when DMA mappings are aligned to the CPU's DMA buffer alignment,
or by bounce buffering via uncached mappings otherwise.

Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Tested-by: Marcin Wojtas <mw@semihalf.com>
Reviewed-by: Ruiyu Ni <ruiyu.ni@intel.com>
MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/NonDiscoverablePciDeviceDxe.c
MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/NonDiscoverablePciDeviceDxe.inf
MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/NonDiscoverablePciDeviceIo.c
MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/NonDiscoverablePciDeviceIo.h

index ee765d7a5d9c2422eb48ef0f26c2242c853bc03d..0fcf2b2ec1bf9ef9268c9e40d33bce66b7041433 100644 (file)
@@ -16,6 +16,8 @@
 \r
 #include <Protocol/DriverBinding.h>\r
 \r
+EFI_CPU_ARCH_PROTOCOL      *mCpu;\r
+\r
 //\r
 // We only support the following device types\r
 //\r
@@ -69,14 +71,7 @@ NonDiscoverablePciDeviceSupported (
     return Status;\r
   }\r
 \r
-  //\r
-  // Restricted to DMA coherent for now\r
-  //\r
   Status = EFI_UNSUPPORTED;\r
-  if (Device->DmaType != NonDiscoverableDeviceDmaTypeCoherent) {\r
-    goto CloseProtocol;\r
-  }\r
-\r
   for (Idx = 0; Idx < ARRAY_SIZE (SupportedNonDiscoverableDevices); Idx++) {\r
     if (CompareGuid (Device->Type, SupportedNonDiscoverableDevices [Idx])) {\r
       Status = EFI_SUCCESS;\r
@@ -224,6 +219,11 @@ NonDiscoverablePciDeviceDxeEntryPoint (
   IN EFI_SYSTEM_TABLE *SystemTable\r
   )\r
 {\r
+  EFI_STATUS      Status;\r
+\r
+  Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **)&mCpu);\r
+  ASSERT_EFI_ERROR(Status);\r
+\r
   return EfiLibInstallDriverBindingComponentName2 (\r
            ImageHandle,\r
            SystemTable,\r
index 996fe310e0e356aa38abfaf78cccce86dc36cc40..5faa8945134ce3fbb68795257597620de1b74edb 100644 (file)
@@ -32,6 +32,7 @@
 [LibraryClasses]\r
   BaseMemoryLib\r
   DebugLib\r
+  DxeServicesTableLib\r
   MemoryAllocationLib\r
   UefiBootServicesTableLib\r
   UefiDriverEntryPoint\r
@@ -40,6 +41,7 @@
 [Protocols]\r
   gEfiPciIoProtocolGuid                         ## BY_START\r
   gEdkiiNonDiscoverableDeviceProtocolGuid       ## TO_START\r
+  gEfiCpuArchProtocolGuid                       ## CONSUMES\r
 \r
 [Guids]\r
   gEdkiiNonDiscoverableAhciDeviceGuid\r
index 82ee9d119011157402bd463b19f039ec3247d15b..ce73a770c18b907630a50e2fc854ce3212b94a33 100644 (file)
@@ -15,6 +15,8 @@
 \r
 #include "NonDiscoverablePciDeviceIo.h"\r
 \r
+#include <Library/DxeServicesTableLib.h>\r
+\r
 #include <IndustryStandard/Acpi.h>\r
 \r
 #include <Protocol/PciRootBridgeIo.h>\r
@@ -537,6 +539,324 @@ CoherentPciIoFreeBuffer (
   return EFI_SUCCESS;\r
 }\r
 \r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+NonCoherentPciIoFreeBuffer (\r
+  IN  EFI_PCI_IO_PROTOCOL         *This,\r
+  IN  UINTN                       Pages,\r
+  IN  VOID                        *HostAddress\r
+  )\r
+{\r
+  NON_DISCOVERABLE_PCI_DEVICE                   *Dev;\r
+  LIST_ENTRY                                    *Entry;\r
+  EFI_STATUS                                    Status;\r
+  NON_DISCOVERABLE_DEVICE_UNCACHED_ALLOCATION   *Alloc;\r
+  BOOLEAN                                       Found;\r
+\r
+  Dev = NON_DISCOVERABLE_PCI_DEVICE_FROM_PCI_IO(This);\r
+\r
+  Found = FALSE;\r
+\r
+  //\r
+  // Find the uncached allocation list entry associated\r
+  // with this allocation\r
+  //\r
+  for (Entry = Dev->UncachedAllocationList.ForwardLink;\r
+       Entry != &Dev->UncachedAllocationList;\r
+       Entry = Entry->ForwardLink) {\r
+\r
+    Alloc = BASE_CR (Entry, NON_DISCOVERABLE_DEVICE_UNCACHED_ALLOCATION, List);\r
+    if (Alloc->HostAddress == HostAddress && Alloc->NumPages == Pages) {\r
+      //\r
+      // We are freeing the exact allocation we were given\r
+      // before by AllocateBuffer()\r
+      //\r
+      Found = TRUE;\r
+      break;\r
+    }\r
+  }\r
+\r
+  if (!Found) {\r
+    ASSERT_EFI_ERROR (EFI_NOT_FOUND);\r
+    return EFI_NOT_FOUND;\r
+  }\r
+\r
+  RemoveEntryList (&Alloc->List);\r
+\r
+  Status = gDS->SetMemorySpaceAttributes (\r
+                  (EFI_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
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+NonCoherentPciIoAllocateBuffer (\r
+  IN  EFI_PCI_IO_PROTOCOL         *This,\r
+  IN  EFI_ALLOCATE_TYPE           Type,\r
+  IN  EFI_MEMORY_TYPE             MemoryType,\r
+  IN  UINTN                       Pages,\r
+  OUT VOID                        **HostAddress,\r
+  IN  UINT64                      Attributes\r
+  )\r
+{\r
+  NON_DISCOVERABLE_PCI_DEVICE                 *Dev;\r
+  EFI_GCD_MEMORY_SPACE_DESCRIPTOR             GcdDescriptor;\r
+  EFI_STATUS                                  Status;\r
+  UINT64                                      MemType;\r
+  NON_DISCOVERABLE_DEVICE_UNCACHED_ALLOCATION *Alloc;\r
+  VOID                                        *AllocAddress;\r
+\r
+  Dev = NON_DISCOVERABLE_PCI_DEVICE_FROM_PCI_IO(This);\r
+\r
+  Status = CoherentPciIoAllocateBuffer (This, Type, MemoryType, Pages,\r
+             &AllocAddress, Attributes);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+\r
+  Status = gDS->GetMemorySpaceDescriptor (\r
+                  (EFI_PHYSICAL_ADDRESS)(UINTN)AllocAddress,\r
+                  &GcdDescriptor);\r
+  if (EFI_ERROR (Status)) {\r
+    goto FreeBuffer;\r
+  }\r
+\r
+  if ((GcdDescriptor.Capabilities & (EFI_MEMORY_WC | EFI_MEMORY_UC)) == 0) {\r
+    Status = EFI_UNSUPPORTED;\r
+    goto FreeBuffer;\r
+  }\r
+\r
+  //\r
+  // Set the preferred memory attributes\r
+  //\r
+  if ((Attributes & EFI_PCI_ATTRIBUTE_MEMORY_WRITE_COMBINE) != 0 ||\r
+      (GcdDescriptor.Capabilities & EFI_MEMORY_UC) == 0) {\r
+    //\r
+    // Use write combining if it was requested, or if it is the only\r
+    // type supported by the region.\r
+    //\r
+    MemType = EFI_MEMORY_WC;\r
+  } else {\r
+    MemType = EFI_MEMORY_UC;\r
+  }\r
+\r
+  Alloc = AllocatePool (sizeof *Alloc);\r
+  if (Alloc == NULL) {\r
+    goto FreeBuffer;\r
+  }\r
+\r
+  Alloc->HostAddress = AllocAddress;\r
+  Alloc->NumPages = Pages;\r
+  Alloc->Attributes = GcdDescriptor.Attributes;\r
+\r
+  //\r
+  // Record this allocation in the linked list, so we\r
+  // can restore the memory space attributes later\r
+  //\r
+  InsertHeadList (&Dev->UncachedAllocationList, &Alloc->List);\r
+\r
+  Status = gDS->SetMemorySpaceAttributes (\r
+                  (EFI_PHYSICAL_ADDRESS)(UINTN)AllocAddress,\r
+                  EFI_PAGES_TO_SIZE (Pages),\r
+                  MemType);\r
+  if (EFI_ERROR (Status)) {\r
+    goto RemoveList;\r
+  }\r
+\r
+  Status = mCpu->FlushDataCache (\r
+                   mCpu,\r
+                   (EFI_PHYSICAL_ADDRESS)(UINTN)AllocAddress,\r
+                   EFI_PAGES_TO_SIZE (Pages),\r
+                   EfiCpuFlushTypeInvalidate);\r
+  if (EFI_ERROR (Status)) {\r
+    goto RemoveList;\r
+  }\r
+\r
+  *HostAddress = AllocAddress;\r
+\r
+  return EFI_SUCCESS;\r
+\r
+RemoveList:\r
+  RemoveEntryList (&Alloc->List);\r
+  FreePool (Alloc);\r
+\r
+FreeBuffer:\r
+  CoherentPciIoFreeBuffer (This, Pages, AllocAddress);\r
+  return Status;\r
+}\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+NonCoherentPciIoMap (\r
+  IN     EFI_PCI_IO_PROTOCOL            *This,\r
+  IN     EFI_PCI_IO_PROTOCOL_OPERATION  Operation,\r
+  IN     VOID                           *HostAddress,\r
+  IN OUT UINTN                          *NumberOfBytes,\r
+  OUT    EFI_PHYSICAL_ADDRESS           *DeviceAddress,\r
+  OUT    VOID                           **Mapping\r
+  )\r
+{\r
+  NON_DISCOVERABLE_PCI_DEVICE           *Dev;\r
+  EFI_STATUS                            Status;\r
+  NON_DISCOVERABLE_PCI_DEVICE_MAP_INFO  *MapInfo;\r
+  UINTN                                 AlignMask;\r
+  VOID                                  *AllocAddress;\r
+  EFI_GCD_MEMORY_SPACE_DESCRIPTOR       GcdDescriptor;\r
+  BOOLEAN                               Bounce;\r
+\r
+  MapInfo = AllocatePool (sizeof *MapInfo);\r
+  if (MapInfo == NULL) {\r
+    return EFI_OUT_OF_RESOURCES;\r
+  }\r
+\r
+  MapInfo->HostAddress = HostAddress;\r
+  MapInfo->Operation = Operation;\r
+  MapInfo->NumberOfBytes = *NumberOfBytes;\r
+\r
+  Dev = NON_DISCOVERABLE_PCI_DEVICE_FROM_PCI_IO(This);\r
+\r
+  //\r
+  // If this device does not support 64-bit DMA addressing, we need to allocate\r
+  // a bounce buffer and copy over the data in case HostAddress >= 4 GB.\r
+  //\r
+  Bounce = ((Dev->Attributes & EFI_PCI_IO_ATTRIBUTE_DUAL_ADDRESS_CYCLE) == 0 &&\r
+            (UINTN)HostAddress + *NumberOfBytes > SIZE_4GB);\r
+\r
+  if (!Bounce) {\r
+    switch (Operation) {\r
+    case EfiPciIoOperationBusMasterRead:\r
+    case EfiPciIoOperationBusMasterWrite:\r
+      //\r
+      // For streaming DMA, it is sufficient if the buffer is aligned to\r
+      // the CPUs DMA buffer alignment.\r
+      //\r
+      AlignMask = mCpu->DmaBufferAlignment - 1;\r
+      if ((((UINTN) HostAddress | *NumberOfBytes) & AlignMask) == 0) {\r
+        break;\r
+      }\r
+      // fall through\r
+\r
+    case EfiPciIoOperationBusMasterCommonBuffer:\r
+      //\r
+      // Check whether the host address refers to an uncached mapping.\r
+      //\r
+      Status = gDS->GetMemorySpaceDescriptor (\r
+                      (EFI_PHYSICAL_ADDRESS)(UINTN)HostAddress,\r
+                      &GcdDescriptor);\r
+      if (EFI_ERROR (Status) ||\r
+          (GcdDescriptor.Attributes & (EFI_MEMORY_WB|EFI_MEMORY_WT)) != 0) {\r
+        Bounce = TRUE;\r
+      }\r
+      break;\r
+\r
+    default:\r
+      ASSERT (FALSE);\r
+    }\r
+  }\r
+\r
+  if (Bounce) {\r
+    if (Operation == EfiPciIoOperationBusMasterCommonBuffer) {\r
+      Status = EFI_DEVICE_ERROR;\r
+      goto FreeMapInfo;\r
+    }\r
+\r
+    Status = NonCoherentPciIoAllocateBuffer (This, AllocateAnyPages,\r
+               EfiBootServicesData, EFI_SIZE_TO_PAGES (MapInfo->NumberOfBytes),\r
+               &AllocAddress, EFI_PCI_ATTRIBUTE_MEMORY_WRITE_COMBINE);\r
+    if (EFI_ERROR (Status)) {\r
+      goto FreeMapInfo;\r
+    }\r
+    MapInfo->AllocAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)AllocAddress;\r
+    if (Operation == EfiPciIoOperationBusMasterRead) {\r
+      gBS->CopyMem (AllocAddress, HostAddress, *NumberOfBytes);\r
+    }\r
+    *DeviceAddress = MapInfo->AllocAddress;\r
+  } else {\r
+    MapInfo->AllocAddress = 0;\r
+    *DeviceAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)HostAddress;\r
+\r
+    //\r
+    // We are not using a bounce buffer: the mapping is sufficiently\r
+    // aligned to allow us to simply flush the caches. Note that cleaning\r
+    // the caches is necessary for both data directions:\r
+    // - for bus master read, we want the latest data to be present\r
+    //   in main memory\r
+    // - for bus master write, we don't want any stale dirty cachelines that\r
+    //   may be written back unexpectedly, and clobber the data written to\r
+    //   main memory by the device.\r
+    //\r
+    mCpu->FlushDataCache (mCpu, (EFI_PHYSICAL_ADDRESS)(UINTN)HostAddress,\r
+            *NumberOfBytes, EfiCpuFlushTypeWriteBack);\r
+  }\r
+\r
+  *Mapping = MapInfo;\r
+  return EFI_SUCCESS;\r
+\r
+FreeMapInfo:\r
+  FreePool (MapInfo);\r
+\r
+  return Status;\r
+}\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+NonCoherentPciIoUnmap (\r
+  IN  EFI_PCI_IO_PROTOCOL          *This,\r
+  IN  VOID                         *Mapping\r
+  )\r
+{\r
+  NON_DISCOVERABLE_PCI_DEVICE_MAP_INFO  *MapInfo;\r
+\r
+  if (Mapping == NULL) {\r
+    return EFI_DEVICE_ERROR;\r
+  }\r
+\r
+  MapInfo = Mapping;\r
+  if (MapInfo->AllocAddress != 0) {\r
+    //\r
+    // We are using a bounce buffer: copy back the data if necessary,\r
+    // and free the buffer.\r
+    //\r
+    if (MapInfo->Operation == EfiPciIoOperationBusMasterWrite) {\r
+      gBS->CopyMem (MapInfo->HostAddress, (VOID *)(UINTN)MapInfo->AllocAddress,\r
+             MapInfo->NumberOfBytes);\r
+    }\r
+    NonCoherentPciIoFreeBuffer (This,\r
+      EFI_SIZE_TO_PAGES (MapInfo->NumberOfBytes),\r
+      (VOID *)(UINTN)MapInfo->AllocAddress);\r
+  } else {\r
+    //\r
+    // We are *not* using a bounce buffer: if this is a bus master write,\r
+    // we have to invalidate the caches so the CPU will see the uncached\r
+    // data written by the device.\r
+    //\r
+    if (MapInfo->Operation == EfiPciIoOperationBusMasterWrite) {\r
+      mCpu->FlushDataCache (mCpu,\r
+              (EFI_PHYSICAL_ADDRESS)(UINTN)MapInfo->HostAddress,\r
+              MapInfo->NumberOfBytes, EfiCpuFlushTypeInvalidate);\r
+    }\r
+  }\r
+  FreePool (MapInfo);\r
+  return EFI_SUCCESS;\r
+}\r
 \r
 STATIC\r
 EFI_STATUS\r
@@ -726,12 +1046,21 @@ InitializePciIoProtocol (
   EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR   *Desc;\r
   INTN                                Idx;\r
 \r
+  InitializeListHead (&Dev->UncachedAllocationList);\r
+\r
   Dev->ConfigSpace.Hdr.VendorId = PCI_ID_VENDOR_UNKNOWN;\r
   Dev->ConfigSpace.Hdr.DeviceId = PCI_ID_DEVICE_DONTCARE;\r
 \r
   // Copy protocol structure\r
   CopyMem(&Dev->PciIo, &PciIoTemplate, sizeof PciIoTemplate);\r
 \r
+  if (Dev->Device->DmaType == NonDiscoverableDeviceDmaTypeNonCoherent) {\r
+    Dev->PciIo.AllocateBuffer   = NonCoherentPciIoAllocateBuffer;\r
+    Dev->PciIo.FreeBuffer       = NonCoherentPciIoFreeBuffer;\r
+    Dev->PciIo.Map              = NonCoherentPciIoMap;\r
+    Dev->PciIo.Unmap            = NonCoherentPciIoUnmap;\r
+  }\r
+\r
   if (CompareGuid (Dev->Device->Type, &gEdkiiNonDiscoverableAhciDeviceGuid)) {\r
     Dev->ConfigSpace.Hdr.ClassCode[0] = PCI_IF_MASS_STORAGE_AHCI;\r
     Dev->ConfigSpace.Hdr.ClassCode[1] = PCI_CLASS_MASS_STORAGE_SATADPA;\r
index bc0a3d3258f9bab4a05750fa51613d6947289514..4496148629110df64d8a4091b7ff4bede08d9df0 100644 (file)
@@ -15,6 +15,8 @@
 #ifndef __NON_DISCOVERABLE_PCI_DEVICE_IO_H__\r
 #define __NON_DISCOVERABLE_PCI_DEVICE_IO_H__\r
 \r
+#include <PiDxe.h>\r
+\r
 #include <Library/BaseMemoryLib.h>\r
 #include <Library/DebugLib.h>\r
 #include <Library/MemoryAllocationLib.h>\r
@@ -25,6 +27,7 @@
 \r
 #include <Protocol/ComponentName.h>\r
 #include <Protocol/NonDiscoverableDevice.h>\r
+#include <Protocol/Cpu.h>\r
 #include <Protocol/PciIo.h>\r
 \r
 #define NON_DISCOVERABLE_PCI_DEVICE_SIG SIGNATURE_32 ('P', 'P', 'I', 'D')\r
 \r
 #define PCI_MAX_BARS                  6\r
 \r
+extern EFI_CPU_ARCH_PROTOCOL      *mCpu;\r
+\r
+typedef struct {\r
+  //\r
+  // The linked-list next pointer\r
+  //\r
+  LIST_ENTRY          List;\r
+  //\r
+  // The address of the uncached allocation\r
+  //\r
+  VOID                *HostAddress;\r
+  //\r
+  // The number of pages in the allocation\r
+  //\r
+  UINTN               NumPages;\r
+  //\r
+  // The attributes of the allocation\r
+  //\r
+  UINT64              Attributes;\r
+} NON_DISCOVERABLE_DEVICE_UNCACHED_ALLOCATION;\r
+\r
 typedef struct {\r
   UINT32                    Signature;\r
   //\r
@@ -71,6 +95,11 @@ typedef struct {
   // Whether this device has been enabled\r
   //\r
   BOOLEAN                   Enabled;\r
+  //\r
+  // Linked list to keep track of uncached allocations performed\r
+  // on behalf of this device\r
+  //\r
+  LIST_ENTRY                UncachedAllocationList;\r
 } NON_DISCOVERABLE_PCI_DEVICE;\r
 \r
 VOID\r