public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
* [RFC PATCH] ArmPkg: implement generic SMMU driver to remap DRAM for 32-bit PCI DMA
@ 2017-05-02 10:32 Ard Biesheuvel
  2017-05-19 13:36 ` Ard Biesheuvel
  2017-05-22 13:49 ` Leif Lindholm
  0 siblings, 2 replies; 6+ messages in thread
From: Ard Biesheuvel @ 2017-05-02 10:32 UTC (permalink / raw)
  To: edk2-devel; +Cc: leif.lindholm, leo.duran, jiewen.yao, agraf, Ard Biesheuvel

This implements a driver that uses any SMMU compatible with the generic
ARM SMMU architecture to remap the lowest 4 GB of DRAM in a way that
makes it accessible to PCI masters that are only 32-bit DMA capable.

Note that this driver goes a bit beyond what is strictly necessary to
support 32-bit DMA, given that it also creates an identity map of the
lowest 4 GB of DRAM. This is intended for interoperability with external
drivers that may use the PCI I/O protocol incorrectly (or not at all)
and program host addresses into the DMA registers and/or rings without
any regard for translation or address size. If a platform's base of DRAM
is a power of 2, and if the platform runs UEFI entirely in the lowest
4 GB of DRAM, any host address access by a PCI master will hit the ID
mapped window, and any truncation that may occur will convert the host
address into the device address.

Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
---

This is based on Jiewen Yao's IOMMU protocol support series, v4.

https://lists.01.org/pipermail/edk2-devel/2017-April/010330.html

Tested with AMD Seattle, which has no DRAM below 4 GB.

 ArmPkg/ArmPkg.dec                                                        |   7 +
 ArmPkg/ArmPkg.dsc                                                        |   1 +
 ArmPkg/Drivers/GenericSmmuStaticPciDmaDxe/BmDma.c                        | 467 ++++++++++++++++++++
 ArmPkg/Drivers/GenericSmmuStaticPciDmaDxe/GenericSmmuStaticPciDmaDxe.c   | 323 ++++++++++++++
 ArmPkg/Drivers/GenericSmmuStaticPciDmaDxe/GenericSmmuStaticPciDmaDxe.inf |  62 +++
 5 files changed, 860 insertions(+)

diff --git a/ArmPkg/ArmPkg.dec b/ArmPkg/ArmPkg.dec
index c4b4da2f95bb..96913e3c0713 100644
--- a/ArmPkg/ArmPkg.dec
+++ b/ArmPkg/ArmPkg.dec
@@ -322,3 +322,10 @@ [PcdsFixedAtBuild.common, PcdsDynamic.common]
   #
   gArmTokenSpaceGuid.PcdPciBusMin|0x0|UINT32|0x00000059
   gArmTokenSpaceGuid.PcdPciBusMax|0x0|UINT32|0x0000005A
+
+  #
+  # Base address and context interrupt of the generic SMMU that
+  # translates memory accesses made by PCI masters
+  #
+  gArmTokenSpaceGuid.PcdPciGenericSmmuBase|0x0|UINT64|0x0000005B
+  gArmTokenSpaceGuid.PcdPciGenericSmmuContextInterrupt|0x0|UINT16|0x0000005C
diff --git a/ArmPkg/ArmPkg.dsc b/ArmPkg/ArmPkg.dsc
index 9144334cb821..9bbc71fa2479 100644
--- a/ArmPkg/ArmPkg.dsc
+++ b/ArmPkg/ArmPkg.dsc
@@ -127,6 +127,7 @@ [Components.common]
   ArmPkg/Drivers/ArmGic/ArmGicDxe.inf
   ArmPkg/Drivers/ArmGic/ArmGicLib.inf
   ArmPkg/Drivers/ArmGic/ArmGicSecLib.inf
+  ArmPkg/Drivers/GenericSmmuStaticPciDmaDxe/GenericSmmuStaticPciDmaDxe.inf
   ArmPkg/Drivers/GenericWatchdogDxe/GenericWatchdogDxe.inf
   ArmPkg/Drivers/TimerDxe/TimerDxe.inf
 
diff --git a/ArmPkg/Drivers/GenericSmmuStaticPciDmaDxe/BmDma.c b/ArmPkg/Drivers/GenericSmmuStaticPciDmaDxe/BmDma.c
new file mode 100644
index 000000000000..629209e335e5
--- /dev/null
+++ b/ArmPkg/Drivers/GenericSmmuStaticPciDmaDxe/BmDma.c
@@ -0,0 +1,467 @@
+/** @file
+  BmDma related function
+
+  Copyright (c) 2017, Intel Corporation. All rights reserved.<BR>
+  Copyright (c) 2017, Linaro Ltd. All rights reserved.<BR>
+
+  This program and the accompanying materials
+  are licensed and made available under the terms and conditions of the BSD License
+  which accompanies this distribution.  The full text of the license may be found at
+  http://opensource.org/licenses/bsd-license.php
+
+  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
+  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
+
+**/
+
+#include <PiDxe.h>
+
+#include <Protocol/IoMmu.h>
+
+#include <Library/BaseLib.h>
+#include <Library/DebugLib.h>
+#include <Library/BaseMemoryLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+
+STATIC CONST UINT64 mTranslationBase = FixedPcdGet64 (PcdSystemMemoryBase);
+
+#define MAP_INFO_SIGNATURE  SIGNATURE_32 ('D', 'M', 'A', 'P')
+typedef struct {
+  UINT32                                    Signature;
+  LIST_ENTRY                                Link;
+  EDKII_IOMMU_OPERATION                     Operation;
+  UINTN                                     NumberOfBytes;
+  UINTN                                     NumberOfPages;
+  EFI_PHYSICAL_ADDRESS                      HostAddress;
+  EFI_PHYSICAL_ADDRESS                      MappedHostAddress;
+} MAP_INFO;
+#define MAP_INFO_FROM_LINK(a) CR (a, MAP_INFO, Link, MAP_INFO_SIGNATURE)
+
+STATIC LIST_ENTRY                 mMaps = INITIALIZE_LIST_HEAD_VARIABLE(mMaps);
+
+/**
+  Provides the controller-specific addresses required to access system memory from a
+  DMA bus master.
+
+  @param  This                  The protocol instance pointer.
+  @param  Operation             Indicates if the bus master is going to read or write to system memory.
+  @param  HostAddress           The system memory address to map to the PCI controller.
+  @param  NumberOfBytes         On input the number of bytes to map. On output the number of bytes
+                                that were mapped.
+  @param  DeviceAddress         The resulting map address for the bus master PCI controller to use to
+                                access the hosts HostAddress.
+  @param  Mapping               A resulting value to pass to Unmap().
+
+  @retval EFI_SUCCESS           The range was mapped for the returned NumberOfBytes.
+  @retval EFI_UNSUPPORTED       The HostAddress cannot be mapped as a common buffer.
+  @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
+  @retval EFI_OUT_OF_RESOURCES  The request could not be completed due to a lack of resources.
+  @retval EFI_DEVICE_ERROR      The system hardware could not map the requested address.
+
+**/
+STATIC
+EFI_STATUS
+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;
+  EFI_PHYSICAL_ADDRESS                              PhysicalAddress;
+  MAP_INFO                                          *MapInfo;
+  BOOLEAN                                           NeedRemap;
+
+  if (HostAddress == NULL || NumberOfBytes == NULL || DeviceAddress == NULL ||
+      Mapping == NULL) {
+    return EFI_INVALID_PARAMETER;
+  }
+
+  //
+  // Make sure that Operation is valid
+  //
+  if ((UINT32) Operation >= EdkiiIoMmuOperationMaximum) {
+    return EFI_INVALID_PARAMETER;
+  }
+
+  NeedRemap = FALSE;
+  PhysicalAddress = (UINTN)HostAddress;
+
+  if ((PhysicalAddress + *NumberOfBytes) > mTranslationBase + SIZE_4GB) {
+    //
+    // If the root bridge or the device cannot handle performing DMA above
+    // 4GB but any part of the DMA transfer being mapped is above 4GB, then
+    // map the DMA transfer to a buffer below 4GB.
+    //
+    NeedRemap = TRUE;
+  }
+
+  if ((Operation == EdkiiIoMmuOperationBusMasterCommonBuffer ||
+       Operation == EdkiiIoMmuOperationBusMasterCommonBuffer64) &&
+      NeedRemap) {
+    //
+    // Common Buffer operations can not be remapped.  If the common buffer
+    // if above 4GB, then it is not possible to generate a mapping, so return
+    // an error.
+    //
+    return EFI_UNSUPPORTED;
+  }
+
+  //
+  // Allocate a MAP_INFO structure to remember the mapping when Unmap() is
+  // called later.
+  //
+  MapInfo = AllocatePool (sizeof (MAP_INFO));
+  if (MapInfo == NULL) {
+    *NumberOfBytes = 0;
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  //
+  // Initialize the MAP_INFO structure
+  //
+  MapInfo->Signature         = MAP_INFO_SIGNATURE;
+  MapInfo->Operation         = Operation;
+  MapInfo->NumberOfBytes     = *NumberOfBytes;
+  MapInfo->NumberOfPages     = EFI_SIZE_TO_PAGES (MapInfo->NumberOfBytes);
+  MapInfo->HostAddress       = PhysicalAddress;
+  MapInfo->MappedHostAddress = (EFI_PHYSICAL_ADDRESS)-1;
+
+  //
+  // Allocate a buffer below 4GB to map the transfer to.
+  //
+  if (NeedRemap) {
+    MapInfo->MappedHostAddress = mTranslationBase + SIZE_4GB - 1;
+    Status = gBS->AllocatePages (
+                    AllocateMaxAddress,
+                    EfiBootServicesData,
+                    MapInfo->NumberOfPages,
+                    &MapInfo->MappedHostAddress
+                    );
+    if (EFI_ERROR (Status)) {
+      FreePool (MapInfo);
+      *NumberOfBytes = 0;
+      return Status;
+    }
+
+    //
+    // 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.
+    //
+    if (Operation == EdkiiIoMmuOperationBusMasterRead ||
+        Operation == EdkiiIoMmuOperationBusMasterRead64) {
+      CopyMem (
+        (VOID *)(UINTN)MapInfo->MappedHostAddress,
+        (VOID *)(UINTN)MapInfo->HostAddress,
+        MapInfo->NumberOfBytes
+        );
+    }
+  } else {
+    MapInfo->MappedHostAddress = MapInfo->HostAddress;
+  }
+
+  InsertTailList (&mMaps, &MapInfo->Link);
+
+  //
+  // The DeviceAddress is the address of the maped buffer below 4GB
+  //
+  *DeviceAddress = MapInfo->MappedHostAddress - mTranslationBase;
+
+  //
+  // Return a pointer to the MAP_INFO structure in Mapping
+  //
+  *Mapping       = MapInfo;
+
+  return EFI_SUCCESS;
+}
+
+/**
+  Completes the Map() operation and releases any corresponding resources.
+
+  @param  This                  The protocol instance pointer.
+  @param  Mapping               The mapping value returned from Map().
+
+  @retval EFI_SUCCESS           The range was unmapped.
+  @retval EFI_INVALID_PARAMETER Mapping is not a value that was returned by Map().
+  @retval EFI_DEVICE_ERROR      The data was not committed to the target system memory.
+**/
+STATIC
+EFI_STATUS
+EFIAPI
+IoMmuUnmap (
+  IN  EDKII_IOMMU_PROTOCOL                     *This,
+  IN  VOID                                     *Mapping
+  )
+{
+  MAP_INFO                 *MapInfo;
+  LIST_ENTRY               *Link;
+
+  if (Mapping == NULL) {
+    return EFI_INVALID_PARAMETER;
+  }
+
+  MapInfo = NULL;
+  for (Link = GetFirstNode (&mMaps)
+       ; !IsNull (&mMaps, Link)
+       ; Link = GetNextNode (&mMaps, Link)
+       ) {
+    MapInfo = MAP_INFO_FROM_LINK (Link);
+    if (MapInfo == Mapping) {
+      break;
+    }
+  }
+  //
+  // Mapping is not a valid value returned by Map()
+  //
+  if (MapInfo != Mapping) {
+    return EFI_INVALID_PARAMETER;
+  }
+  RemoveEntryList (&MapInfo->Link);
+
+  if (MapInfo->MappedHostAddress != MapInfo->HostAddress) {
+    //
+    // If this is a write operation from the Bus Master's point of view,
+    // then copy the contents of the mapped buffer into the real buffer
+    // so the processor can read the contents of the real buffer.
+    //
+    if (MapInfo->Operation == EdkiiIoMmuOperationBusMasterWrite ||
+        MapInfo->Operation == EdkiiIoMmuOperationBusMasterWrite64) {
+      CopyMem (
+        (VOID *)(UINTN)MapInfo->HostAddress,
+        (VOID *)(UINTN)MapInfo->MappedHostAddress,
+        MapInfo->NumberOfBytes
+        );
+    }
+
+    //
+    // Free the mapped buffer and the MAP_INFO structure.
+    //
+    gBS->FreePages (MapInfo->MappedHostAddress, MapInfo->NumberOfPages);
+  }
+
+  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.
+
+**/
+STATIC
+EFI_STATUS
+EFIAPI
+IoMmuAllocateBuffer (
+  IN     EDKII_IOMMU_PROTOCOL                     *This,
+  IN     EFI_ALLOCATE_TYPE                        Type,
+  IN     EFI_MEMORY_TYPE                          MemoryType,
+  IN     UINTN                                    Pages,
+  IN OUT VOID                                     **HostAddress,
+  IN     UINT64                                   Attributes
+  )
+{
+  EFI_STATUS                Status;
+  EFI_PHYSICAL_ADDRESS      PhysicalAddress;
+
+  //
+  // Validate Attributes
+  //
+  if ((Attributes & EDKII_IOMMU_ATTRIBUTE_INVALID_FOR_ALLOCATE_BUFFER) != 0) {
+    return EFI_UNSUPPORTED;
+  }
+
+  //
+  // Check for invalid inputs
+  //
+  if (HostAddress == NULL) {
+    return EFI_INVALID_PARAMETER;
+  }
+
+  //
+  // The only valid memory types are EfiBootServicesData and
+  // EfiRuntimeServicesData
+  //
+  if (MemoryType != EfiBootServicesData &&
+      MemoryType != EfiRuntimeServicesData) {
+    return EFI_INVALID_PARAMETER;
+  }
+
+  //
+  // Limit allocations to memory covered by the remapped window.
+  //
+  PhysicalAddress = mTranslationBase + SIZE_4GB - 1;
+  Status = gBS->AllocatePages (
+                  AllocateMaxAddress,
+                  MemoryType,
+                  Pages,
+                  &PhysicalAddress
+                  );
+  if (!EFI_ERROR (Status)) {
+    *HostAddress = (VOID *)(UINTN)PhysicalAddress;
+  }
+
+  return Status;
+}
+
+/**
+  Frees memory that was allocated with AllocateBuffer().
+
+  @param  This                  The protocol instance pointer.
+  @param  Pages                 The number of pages to free.
+  @param  HostAddress           The base system memory address of the allocated range.
+
+  @retval EFI_SUCCESS           The requested memory pages were freed.
+  @retval EFI_INVALID_PARAMETER The memory range specified by HostAddress and Pages
+                                was not allocated with AllocateBuffer().
+
+**/
+STATIC
+EFI_STATUS
+EFIAPI
+IoMmuFreeBuffer (
+  IN  EDKII_IOMMU_PROTOCOL                     *This,
+  IN  UINTN                                    Pages,
+  IN  VOID                                     *HostAddress
+  )
+{
+  return gBS->FreePages ((EFI_PHYSICAL_ADDRESS) (UINTN) HostAddress, Pages);
+}
+
+/**
+  Set IOMMU attribute for a system memory.
+
+  If the IOMMU protocol exists, the system memory cannot be used
+  for DMA by default.
+
+  When a device requests a DMA access for a system memory,
+  the device driver need use SetAttribute() to update the IOMMU
+  attribute to request DMA access (read and/or write).
+
+  The DeviceHandle is used to identify which device submits the request.
+  The IOMMU implementation need translate the device path to an IOMMU device ID,
+  and set IOMMU hardware register accordingly.
+  1) DeviceHandle can be a standard PCI device.
+     The memory for BusMasterRead need set EDKII_IOMMU_ACCESS_READ.
+     The memory for BusMasterWrite need set EDKII_IOMMU_ACCESS_WRITE.
+     The memory for BusMasterCommonBuffer need set EDKII_IOMMU_ACCESS_READ|EDKII_IOMMU_ACCESS_WRITE.
+     After the memory is used, the memory need set 0 to keep it being protected.
+  2) DeviceHandle can be an ACPI device (ISA, I2C, SPI, etc).
+     The memory for DMA access need set EDKII_IOMMU_ACCESS_READ and/or EDKII_IOMMU_ACCESS_WRITE.
+
+  @param[in]  This              The protocol instance pointer.
+  @param[in]  DeviceHandle      The device who initiates the DMA access request.
+  @param[in]  DeviceAddress     The base of device memory address to be used as the DMA memory.
+  @param[in]  Length            The length of device memory address to be used as the DMA memory.
+  @param[in]  IoMmuAccess       The IOMMU access.
+
+  @retval EFI_SUCCESS            The IoMmuAccess is set for the memory range specified by DeviceAddress and Length.
+  @retval EFI_INVALID_PARAMETER  DeviceHandle is an invalid handle.
+  @retval EFI_INVALID_PARAMETER  DeviceAddress is not IoMmu Page size aligned.
+  @retval EFI_INVALID_PARAMETER  Length is not IoMmu Page size aligned.
+  @retval EFI_INVALID_PARAMETER  Length is 0.
+  @retval EFI_INVALID_PARAMETER  IoMmuAccess specified an illegal combination of access.
+  @retval EFI_UNSUPPORTED        DeviceHandle is unknown by the IOMMU.
+  @retval EFI_UNSUPPORTED        The bit mask of IoMmuAccess is not supported by the IOMMU.
+  @retval EFI_UNSUPPORTED        The IOMMU does not support the memory range specified by DeviceAddress and Length.
+  @retval EFI_OUT_OF_RESOURCES   There are not enough resources available to modify the IOMMU access.
+  @retval EFI_DEVICE_ERROR       The IOMMU device reported an error while attempting the operation.
+
+**/
+EFI_STATUS
+EFIAPI
+IoMmuSetAttribute (
+  IN EDKII_IOMMU_PROTOCOL  *This,
+  IN EFI_HANDLE            DeviceHandle,
+  IN EFI_PHYSICAL_ADDRESS  DeviceAddress,
+  IN UINT64                Length,
+  IN UINT64                IoMmuAccess
+  )
+{
+  return EFI_UNSUPPORTED;
+}
+
+/**
+  Set IOMMU attribute for a system memory.
+
+  If the IOMMU protocol exists, the system memory cannot be used
+  for DMA by default.
+
+  When a device requests a DMA access for a system memory,
+  the device driver need use SetAttribute() to update the IOMMU
+  attribute to request DMA access (read and/or write).
+
+  The DeviceHandle is used to identify which device submits the request.
+  The IOMMU implementation need translate the device path to an IOMMU device ID,
+  and set IOMMU hardware register accordingly.
+  1) DeviceHandle can be a standard PCI device.
+     The memory for BusMasterRead need set EDKII_IOMMU_ACCESS_READ.
+     The memory for BusMasterWrite need set EDKII_IOMMU_ACCESS_WRITE.
+     The memory for BusMasterCommonBuffer need set EDKII_IOMMU_ACCESS_READ|EDKII_IOMMU_ACCESS_WRITE.
+     After the memory is used, the memory need set 0 to keep it being protected.
+  2) DeviceHandle can be an ACPI device (ISA, I2C, SPI, etc).
+     The memory for DMA access need set EDKII_IOMMU_ACCESS_READ and/or EDKII_IOMMU_ACCESS_WRITE.
+
+  @param[in]  This              The protocol instance pointer.
+  @param[in]  DeviceHandle      The device who initiates the DMA access request.
+  @param[in]  Mapping           The mapping value returned from Map().
+  @param[in]  IoMmuAccess       The IOMMU access.
+
+  @retval EFI_SUCCESS            The IoMmuAccess is set for the memory range specified by DeviceAddress and Length.
+  @retval EFI_INVALID_PARAMETER  DeviceHandle is an invalid handle.
+  @retval EFI_INVALID_PARAMETER  Mapping is not a value that was returned by Map().
+  @retval EFI_INVALID_PARAMETER  IoMmuAccess specified an illegal combination of access.
+  @retval EFI_UNSUPPORTED        DeviceHandle is unknown by the IOMMU.
+  @retval EFI_UNSUPPORTED        The bit mask of IoMmuAccess is not supported by the IOMMU.
+  @retval EFI_UNSUPPORTED        The IOMMU does not support the memory range specified by Mapping.
+  @retval EFI_OUT_OF_RESOURCES   There are not enough resources available to modify the IOMMU access.
+  @retval EFI_DEVICE_ERROR       The IOMMU device reported an error while attempting the operation.
+
+**/
+STATIC
+EFI_STATUS
+EFIAPI
+IoMmuSetMappingAttribute (
+  IN EDKII_IOMMU_PROTOCOL  *This,
+  IN EFI_HANDLE            DeviceHandle,
+  IN VOID                  *Mapping,
+  IN UINT64                IoMmuAccess
+  )
+{
+  //
+  // We only support a static remapping of DRAM into the PCI address space
+  // so there is nothing we need to do to handle invocations of this protocol
+  // method.
+  //
+  return EFI_SUCCESS;
+}
+
+EDKII_IOMMU_PROTOCOL  mGenericSmmuIommuProtocol = {
+  EDKII_IOMMU_PROTOCOL_REVISION,
+  IoMmuSetAttribute,
+  IoMmuMap,
+  IoMmuUnmap,
+  IoMmuAllocateBuffer,
+  IoMmuFreeBuffer,
+  IoMmuSetMappingAttribute,
+};
diff --git a/ArmPkg/Drivers/GenericSmmuStaticPciDmaDxe/GenericSmmuStaticPciDmaDxe.c b/ArmPkg/Drivers/GenericSmmuStaticPciDmaDxe/GenericSmmuStaticPciDmaDxe.c
new file mode 100644
index 000000000000..8f5093af14ea
--- /dev/null
+++ b/ArmPkg/Drivers/GenericSmmuStaticPciDmaDxe/GenericSmmuStaticPciDmaDxe.c
@@ -0,0 +1,323 @@
+/** @file
+
+  Copyright (c) 2017, Linaro Ltd. All rights reserved.<BR>
+
+  This program and the accompanying materials
+  are licensed and made available under the terms and conditions of the BSD License
+  which accompanies this distribution.  The full text of the license may be found at
+  http://opensource.org/licenses/bsd-license.php
+
+  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
+  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
+
+**/
+
+#include <PiDxe.h>
+#include <Library/BaseLib.h>
+#include <Library/DebugLib.h>
+#include <Library/IoLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+
+#include <Protocol/Cpu.h>
+#include <Protocol/HardwareInterrupt.h>
+#include <Protocol/IoMmu.h>
+
+#include <Chipset/AArch64Mmu.h>
+
+#define GL_CR0                          0x0
+#define GL_CR0_CLIENTPD                 BIT0
+
+#define GL_IDR0                         0x20
+#define GL_IDR1                         0x24
+#define GL_STLBIALL                     0x60
+#define GL_TLBIALLNSNH                  0x68
+#define GL_SMR0                         0x800
+#define GL_S2CR0                        0xc00
+#define GL_CBA2R0                       0x1800
+
+#define GL_IDR0_NUMSMRG_MASK            0xff
+
+#define GL_IDR1_NUMPAGENDXB_MASK        0x7
+#define GL_IDR1_NUMPAGENDXB_SHIFT       28
+#define GL_IDR1_PAGE_SIZE_64KB          BIT31
+
+#define CB_BASE(i)                      (mContextBankOffset + ((i) * SIZE_4KB))
+
+#define CB_SCTLR_OFFSET                 0x0
+#define CB_TTBR0_OFFSET                 0x20
+#define CB_TTBCR_OFFSET                 0x30
+
+#define CB_FAR                          0x60
+#define CB_FSR                          0x58
+#define CB_FSYNR0                       0x68
+#define CB_FSYNR1                       0x6c
+
+#define TT_S2_MEMATTR_CACHED            (0xF << 2)
+#define TT_S2_AP_READ_WRITE             (0x3 << 6)
+
+#define TT_ENTRY_ATTRIBUTES             (TT_TYPE_BLOCK_ENTRY | \
+                                         TT_SH_INNER_SHAREABLE | \
+                                         TT_S2_AP_READ_WRITE | \
+                                         TT_S2_MEMATTR_CACHED | \
+                                         TT_AF)
+
+#define TCR_T0SZ(bits)                  ((UINT32)(32 - (bits)) & 0x3f)
+#define TCR_SL0_LEVEL1                  BIT6
+#define TCR_SL0_LEVEL2                  0
+
+#define SCTLR_M_ENABLE                  BIT0
+#define SCTLR_TR_ENABLE                 BIT1
+#define SCTLR_AF_ENABLE                 BIT2
+#define SCTLR_CTX_FAULT_ENABLE          BIT5
+#define SCTLR_CTX_INT_ENABLE            BIT6
+
+#define DRAM_BASE                       FixedPcdGet64 (PcdSystemMemoryBase)
+
+extern EDKII_IOMMU_PROTOCOL             mGenericSmmuIommuProtocol;
+
+//
+// Create a static stage 2 mapping of the first 4 GB of DRAM in the start
+// of the IOVA space, and as an ID mapping at the original offset.
+//
+STATIC CONST UINT64 mPciTranslation[1024] __attribute__((aligned(SIZE_8KB))) = {
+  [0] = TT_ENTRY_ATTRIBUTES | (DRAM_BASE),
+  [1] = TT_ENTRY_ATTRIBUTES | (DRAM_BASE + SIZE_1GB),
+  [2] = TT_ENTRY_ATTRIBUTES | (DRAM_BASE + SIZE_1GB * 2UL),
+  [3] = TT_ENTRY_ATTRIBUTES | (DRAM_BASE + SIZE_1GB * 3UL),
+
+  //
+  // The ID mapping of the first 4 GB of DRAM is a workaround for buggy
+  // drivers that violate the UEFI spec by ignoring the device address
+  // returned by the PCI I/O map/unmap routines, and program host
+  // addresses into the DMA h/w registers or rings instead.
+  //
+  [(DRAM_BASE >> 30)]     = TT_ENTRY_ATTRIBUTES | (DRAM_BASE),
+  [(DRAM_BASE >> 30) + 1] = TT_ENTRY_ATTRIBUTES | (DRAM_BASE + SIZE_1GB),
+  [(DRAM_BASE >> 30) + 2] = TT_ENTRY_ATTRIBUTES | (DRAM_BASE + SIZE_1GB * 2UL),
+  [(DRAM_BASE >> 30) + 3] = TT_ENTRY_ATTRIBUTES | (DRAM_BASE + SIZE_1GB * 3UL),
+};
+
+STATIC EFI_HARDWARE_INTERRUPT_PROTOCOL    *mInterrupt;
+STATIC EFI_EVENT                          mEfiExitBootServicesEvent;
+STATIC UINTN                              mContextBankOffset;
+
+STATIC
+UINT32
+ReadGlobalReg32 (
+  IN  UINT64    Offset
+  )
+{
+  return MmioRead32 (FixedPcdGet64 (PcdPciGenericSmmuBase) + Offset);
+}
+
+STATIC
+VOID
+WriteGlobalReg32 (
+  IN  UINT64    Offset,
+  IN  UINT64    Value
+  )
+{
+  MmioWrite32 (FixedPcdGet64 (PcdPciGenericSmmuBase) + Offset, Value);
+}
+
+STATIC
+UINT32
+ReadCbReg32 (
+  IN  UINTN     Bank,
+  IN  UINT64    Offset
+  )
+{
+  return MmioRead32 (FixedPcdGet64 (PcdPciGenericSmmuBase) + CB_BASE(Bank) +
+                     Offset);
+}
+
+STATIC
+VOID
+WriteCbReg32 (
+  IN  UINTN     Bank,
+  IN  UINT64    Offset,
+  IN  UINT32    Value
+  )
+{
+  MmioWrite32 (FixedPcdGet64 (PcdPciGenericSmmuBase) + CB_BASE(Bank) + Offset,
+               Value);
+}
+
+STATIC
+UINT64
+ReadCbReg64 (
+  IN  UINTN     Bank,
+  IN  UINT64    Offset
+  )
+{
+  return MmioRead64 (FixedPcdGet64 (PcdPciGenericSmmuBase) + CB_BASE(Bank) +
+                     Offset);
+}
+
+STATIC
+VOID
+WriteCbReg64 (
+  IN  UINTN     Bank,
+  IN  UINT64    Offset,
+  IN  UINT64    Value
+  )
+{
+  MmioWrite64 (FixedPcdGet64 (PcdPciGenericSmmuBase) + CB_BASE(Bank) + Offset,
+               Value);
+}
+
+STATIC
+VOID
+EFIAPI
+ContextInterruptHandler (
+  IN  HARDWARE_INTERRUPT_SOURCE   Source,
+  IN  EFI_SYSTEM_CONTEXT          SystemContext
+  )
+{
+  //
+  // Dump the SMMU context fault registers when taking a context interrupt
+  //
+  DEBUG ((DEBUG_WARN,
+    "Context interrupt asserted by SMMU:\n\n"
+    "SMMU_CB0_FAR        0x%016llx \n"
+    "SMMU_CB0_FSR        0x%08llx \n"
+    "SMMU_CB0_FSYNR0     0x%08llx \n"
+    "SMMU_CB0_FSYNR1     0x%08llx \n",
+    ReadCbReg64 (0, CB_FAR),
+    ReadCbReg32 (0, CB_FSR),
+    ReadCbReg32 (0, CB_FSYNR0),
+    ReadCbReg32 (0, CB_FSYNR1)));
+
+  mInterrupt->EndOfInterrupt (mInterrupt, Source);
+}
+
+STATIC
+VOID
+EFIAPI
+ExitBootServicesEvent (
+  IN EFI_EVENT  Event,
+  IN VOID       *Context
+  )
+{
+  //
+  // Put the SMMU back into bypass mode
+  //
+  MmioOr32 (FixedPcdGet64 (PcdPciGenericSmmuBase) + GL_CR0, GL_CR0_CLIENTPD);
+}
+
+EFI_STATUS
+GenericSmmuStaticPciDmaDxeInitialize (
+  IN EFI_HANDLE        ImageHandle,
+  IN EFI_SYSTEM_TABLE  *SystemTable
+  )
+{
+  EFI_STATUS        Status;
+  UINTN             Idx;
+  UINT32            IdVal;
+  UINTN             NumStreamMappingRegisters;
+
+  //
+  // The static mapping uses 1 GB block mappings, whose VAs and PAs should
+  // be equal modulo the block size.
+  //
+  ASSERT ((DRAM_BASE % SIZE_1GB) == 0);
+
+  if ((DRAM_BASE & (DRAM_BASE - 1)) != 0) {
+    //
+    // Buggy drivers that use truncated host addresses instead of device
+    // addresses for DMA may still work correctly if such truncation is
+    // guaranteed to produce the remapped alias. This is the case if
+    // DRAM_BASE is a power of 2.
+    //
+    DEBUG ((DEBUG_WARN,
+      "%a: this driver will work better if DRAM_BASE is a power of 2!\n",
+      __FUNCTION__));
+  }
+
+  Status = gBS->LocateProtocol (&gHardwareInterruptProtocolGuid, NULL,
+                  (VOID **)&mInterrupt);
+  ASSERT_EFI_ERROR (Status);
+
+  Status = mInterrupt->RegisterInterruptSource (mInterrupt,
+                         PcdGet16 (PcdPciGenericSmmuContextInterrupt),
+                         ContextInterruptHandler);
+  ASSERT_EFI_ERROR (Status);
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  Status = gBS->CreateEvent (EVT_SIGNAL_EXIT_BOOT_SERVICES,
+                  TPL_NOTIFY, ExitBootServicesEvent, NULL,
+                  &mEfiExitBootServicesEvent);
+  ASSERT_EFI_ERROR (Status);
+
+  Status = gBS->InstallMultipleProtocolInterfaces (
+                  &ImageHandle,
+                  &gEdkiiIoMmuProtocolGuid, &mGenericSmmuIommuProtocol,
+                  NULL
+                  );
+  ASSERT_EFI_ERROR (Status);
+
+  IdVal = ReadGlobalReg32 (GL_IDR1);
+  mContextBankOffset = (IdVal & GL_IDR1_PAGE_SIZE_64KB) ? SIZE_64KB : SIZE_4KB;
+  mContextBankOffset <<= (1 + ((IdVal >> GL_IDR1_NUMPAGENDXB_SHIFT) &
+                               GL_IDR1_NUMPAGENDXB_MASK));
+
+  //
+  // Clear all stream mappings
+  //
+  NumStreamMappingRegisters = ReadGlobalReg32 (GL_IDR0) & GL_IDR0_NUMSMRG_MASK;
+  for (Idx = 0; Idx < NumStreamMappingRegisters; Idx++) {
+    WriteGlobalReg32 (GL_SMR0 + Idx * sizeof(UINT32), 0x0);
+    WriteGlobalReg32 (GL_S2CR0 + Idx * sizeof(UINT32), 0x0);
+  }
+
+  //
+  // Set stream match register 0 to match all streams, and map onto
+  // context bank 0
+  //
+  WriteGlobalReg32 (GL_SMR0, 0xffff0000);
+  WriteGlobalReg32 (GL_S2CR0, 0x0);
+
+  //
+  // Disable the context bank
+  //
+  WriteCbReg32 (0, CB_SCTLR_OFFSET, 0);
+
+  //
+  // Assign the translation base register for context bank 0
+  //
+  WriteCbReg64 (0, CB_TTBR0_OFFSET, (UINTN)mPciTranslation);
+
+  //
+  // Flush TLBS.
+  //
+  WriteGlobalReg32 (GL_STLBIALL, 0);
+  WriteGlobalReg32 (GL_TLBIALLNSNH, 0);
+
+  //
+  // Configure the size of the translation space, the number of levels,
+  // and the cacheability attributes of the PTW memory accesses.
+  //
+  WriteCbReg32 (0, CB_TTBCR_OFFSET, TCR_T0SZ(40) |
+                                    TCR_SH_INNER_SHAREABLE |
+                                    TCR_RGN_INNER_WRITE_BACK_NO_ALLOC |
+                                    TCR_RGN_OUTER_WRITE_BACK_NO_ALLOC |
+                                    TCR_SL0_LEVEL1);
+
+  //
+  // Enable the context bank
+  //
+  WriteCbReg32 (0, CB_SCTLR_OFFSET, SCTLR_TR_ENABLE |
+                                    SCTLR_AF_ENABLE |
+                                    SCTLR_CTX_INT_ENABLE |
+                                    SCTLR_CTX_FAULT_ENABLE |
+                                    SCTLR_M_ENABLE);
+
+  //
+  // Get the SMMU out of bypass mode
+  //
+  MmioAnd32 (FixedPcdGet64 (PcdPciGenericSmmuBase) + GL_CR0, ~GL_CR0_CLIENTPD);
+
+  return EFI_SUCCESS;
+}
diff --git a/ArmPkg/Drivers/GenericSmmuStaticPciDmaDxe/GenericSmmuStaticPciDmaDxe.inf b/ArmPkg/Drivers/GenericSmmuStaticPciDmaDxe/GenericSmmuStaticPciDmaDxe.inf
new file mode 100644
index 000000000000..02c17e755c4a
--- /dev/null
+++ b/ArmPkg/Drivers/GenericSmmuStaticPciDmaDxe/GenericSmmuStaticPciDmaDxe.inf
@@ -0,0 +1,62 @@
+## @file
+#
+# Copyright (c) 2017, Linaro Ltd. All rights reserved.<BR>
+#
+# This program and the accompanying materials
+# are licensed and made available under the terms and conditions of the BSD License
+# which accompanies this distribution.  The full text of the license may be found at
+# http://opensource.org/licenses/bsd-license.php
+#
+# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
+# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
+#
+##
+
+[Defines]
+  INF_VERSION                    = 0x00010019
+  BASE_NAME                      = GenericSmmuStaticPciDmaDxe
+  FILE_GUID                      = 59b5e69f-88b7-4632-a2ab-6abe6bdedda2
+  MODULE_TYPE                    = DXE_DRIVER
+  VERSION_STRING                 = 1.0
+  ENTRY_POINT                    = GenericSmmuStaticPciDmaDxeInitialize
+
+#
+# The following information is for reference only and not required by the build tools.
+#
+#  VALID_ARCHITECTURES           = ARM AARCH64
+#
+#
+
+[Sources]
+  GenericSmmuStaticPciDmaDxe.c
+  BmDma.c
+
+[Packages]
+  ArmPkg/ArmPkg.dec
+  EmbeddedPkg/EmbeddedPkg.dec
+  MdeModulePkg/MdeModulePkg.dec
+  MdePkg/MdePkg.dec
+
+[LibraryClasses]
+  DebugLib
+  BaseLib
+  BaseMemoryLib
+  IoLib
+  MemoryAllocationLib
+  UefiBootServicesTableLib
+  UefiDriverEntryPoint
+
+[Protocols]
+  gEdkiiIoMmuProtocolGuid                     ## PRODUCES
+  gEfiPciIoProtocolGuid                       ## CONSUMES
+  gHardwareInterruptProtocolGuid              ## CONSUMES
+
+[Pcd]
+  gArmTokenSpaceGuid.PcdPciGenericSmmuBase
+  gArmTokenSpaceGuid.PcdPciGenericSmmuContextInterrupt
+
+[FixedPcd]
+  gArmTokenSpaceGuid.PcdSystemMemoryBase
+
+[Depex]
+  gHardwareInterruptProtocolGuid
-- 
2.9.3



^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2017-05-24 13:20 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2017-05-02 10:32 [RFC PATCH] ArmPkg: implement generic SMMU driver to remap DRAM for 32-bit PCI DMA Ard Biesheuvel
2017-05-19 13:36 ` Ard Biesheuvel
2017-05-22 13:49 ` Leif Lindholm
2017-05-23 10:36   ` Ard Biesheuvel
2017-05-24 10:30     ` Leif Lindholm
2017-05-24 13:20       ` Ard Biesheuvel

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox