* [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
* Re: [RFC PATCH] ArmPkg: implement generic SMMU driver to remap DRAM for 32-bit PCI DMA
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
1 sibling, 0 replies; 6+ messages in thread
From: Ard Biesheuvel @ 2017-05-19 13:36 UTC (permalink / raw)
To: edk2-devel@lists.01.org, Leif Lindholm, Duran, Leo, Yao, Jiewen
Cc: Alexander Graf, Ard Biesheuvel
On 2 May 2017 at 11:32, Ard Biesheuvel <ard.biesheuvel@linaro.org> wrote:
> 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>
> ---
>
The prerequisites are now in place. Any comments?
> 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 [flat|nested] 6+ messages in thread
* Re: [RFC PATCH] ArmPkg: implement generic SMMU driver to remap DRAM for 32-bit PCI DMA
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
1 sibling, 1 reply; 6+ messages in thread
From: Leif Lindholm @ 2017-05-22 13:49 UTC (permalink / raw)
To: Ard Biesheuvel; +Cc: edk2-devel, leo.duran, jiewen.yao, agraf
On Tue, May 02, 2017 at 11:32:02AM +0100, Ard Biesheuvel wrote:
> 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);
So, here is the only objection I have to the patch (apart from a
question below). I am _really_ not happy about the ARM-specific
PcdSystemMemoryBase (in general), although it is less evil than
PcdSystemMemorySize.
Maybe I'm missing something, but I don't see anything ARM-specific
about this driver. If PcdSystemMemoryBase is the reason for placing it
under ArmPkg, can we migrate that option across somewhere else?
Seems an added shame to put two new generic config options
(PcdPciGenericSmmuBase/PcdPciGenericSmmuContextInterrupt) under an
architecture-specific directory.
> +
> +#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.
> + //
Request for clarification: is this because this driver is only invoked
where the root bridge is incapable? (The test looks unconditional.)
/
Leif
> + 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 [flat|nested] 6+ messages in thread
* Re: [RFC PATCH] ArmPkg: implement generic SMMU driver to remap DRAM for 32-bit PCI DMA
2017-05-22 13:49 ` Leif Lindholm
@ 2017-05-23 10:36 ` Ard Biesheuvel
2017-05-24 10:30 ` Leif Lindholm
0 siblings, 1 reply; 6+ messages in thread
From: Ard Biesheuvel @ 2017-05-23 10:36 UTC (permalink / raw)
To: Leif Lindholm
Cc: edk2-devel@lists.01.org, Duran, Leo, Yao, Jiewen, Alexander Graf
On 22 May 2017 at 06:49, Leif Lindholm <leif.lindholm@linaro.org> wrote:
> On Tue, May 02, 2017 at 11:32:02AM +0100, Ard Biesheuvel wrote:
>> 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);
>
> So, here is the only objection I have to the patch (apart from a
> question below). I am _really_ not happy about the ARM-specific
> PcdSystemMemoryBase (in general), although it is less evil than
> PcdSystemMemorySize.
>
> Maybe I'm missing something, but I don't see anything ARM-specific
> about this driver. If PcdSystemMemoryBase is the reason for placing it
> under ArmPkg, can we migrate that option across somewhere else?
>
Of course it is ARM specific. SMMUs only exist on ARM systems, and the
driver reuses ArmMmuLib defines to populate the page table entries.
In general, I agree with the tendency to keep code out of ArmPkg, but
in this case, I think it really belongs there.
> Seems an added shame to put two new generic config options
> (PcdPciGenericSmmuBase/PcdPciGenericSmmuContextInterrupt) under an
> architecture-specific directory.
>
>> +
>> +#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.
>> + //
>
> Request for clarification: is this because this driver is only invoked
> where the root bridge is incapable? (The test looks unconditional.)
>
No, the root bridge driver always invokes the IOMMU protocol if there is one.
Here we test whether the mapped buffer already lives in the first 4 GB
of DRAM, otherwise we will use a bounce buffer. This driver never maps
anything dynamically in the SMMU, it just allocates a bounce buffer
from the 32-bit addressable range if required.
>> + 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 [flat|nested] 6+ messages in thread
* Re: [RFC PATCH] ArmPkg: implement generic SMMU driver to remap DRAM for 32-bit PCI DMA
2017-05-23 10:36 ` Ard Biesheuvel
@ 2017-05-24 10:30 ` Leif Lindholm
2017-05-24 13:20 ` Ard Biesheuvel
0 siblings, 1 reply; 6+ messages in thread
From: Leif Lindholm @ 2017-05-24 10:30 UTC (permalink / raw)
To: Ard Biesheuvel
Cc: edk2-devel@lists.01.org, Duran, Leo, Yao, Jiewen, Alexander Graf
On Tue, May 23, 2017 at 03:36:24AM -0700, Ard Biesheuvel wrote:
> On 22 May 2017 at 06:49, Leif Lindholm <leif.lindholm@linaro.org> wrote:
> > On Tue, May 02, 2017 at 11:32:02AM +0100, Ard Biesheuvel wrote:
> >> 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);
> >
> > So, here is the only objection I have to the patch (apart from a
> > question below). I am _really_ not happy about the ARM-specific
> > PcdSystemMemoryBase (in general), although it is less evil than
> > PcdSystemMemorySize.
> >
> > Maybe I'm missing something, but I don't see anything ARM-specific
> > about this driver. If PcdSystemMemoryBase is the reason for placing it
> > under ArmPkg, can we migrate that option across somewhere else?
> >
>
> Of course it is ARM specific. SMMUs only exist on ARM systems,
SMMUs, yes. IOMMUs, no.
> and the
> driver reuses ArmMmuLib defines to populate the page table entries.
Ah, managed to miss that bit.
The remainder of the code looks completely generic.
> In general, I agree with the tendency to keep code out of ArmPkg, but
> in this case, I think it really belongs there.
I'll concede it makes sense to keep it in ArmPkg for now, and
re-engineering it for complete portability when there are no other
potential users is a bit premature.
> > Seems an added shame to put two new generic config options
> > (PcdPciGenericSmmuBase/PcdPciGenericSmmuContextInterrupt) under an
> > architecture-specific directory.
> >
> >> +
> >> +#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.
> >> + //
> >
> > Request for clarification: is this because this driver is only invoked
> > where the root bridge is incapable? (The test looks unconditional.)
> >
>
> No, the root bridge driver always invokes the IOMMU protocol if there is one.
>
> Here we test whether the mapped buffer already lives in the first 4 GB
> of DRAM, otherwise we will use a bounce buffer. This driver never maps
> anything dynamically in the SMMU, it just allocates a bounce buffer
> from the 32-bit addressable range if required.
Right, so could the comment be updated to say something more like
//
// If any part of the DMA transfer being mapped is above 4GB, then
// map the DMA transfer to a buffer below 4GB.
//
?
The remainder of the comment is also useful, but would it not make
more sense in the function header?
With that:
Reviewed-by: Leif Lindholm <leif.lindholm@linaro.org>
/
Leif
> >> + 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 [flat|nested] 6+ messages in thread
* Re: [RFC PATCH] ArmPkg: implement generic SMMU driver to remap DRAM for 32-bit PCI DMA
2017-05-24 10:30 ` Leif Lindholm
@ 2017-05-24 13:20 ` Ard Biesheuvel
0 siblings, 0 replies; 6+ messages in thread
From: Ard Biesheuvel @ 2017-05-24 13:20 UTC (permalink / raw)
To: Leif Lindholm
Cc: edk2-devel@lists.01.org, Duran, Leo, Yao, Jiewen, Alexander Graf
On 24 May 2017 at 03:30, Leif Lindholm <leif.lindholm@linaro.org> wrote:
> On Tue, May 23, 2017 at 03:36:24AM -0700, Ard Biesheuvel wrote:
>> On 22 May 2017 at 06:49, Leif Lindholm <leif.lindholm@linaro.org> wrote:
>> > On Tue, May 02, 2017 at 11:32:02AM +0100, Ard Biesheuvel wrote:
>> >> 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);
>> >
>> > So, here is the only objection I have to the patch (apart from a
>> > question below). I am _really_ not happy about the ARM-specific
>> > PcdSystemMemoryBase (in general), although it is less evil than
>> > PcdSystemMemorySize.
>> >
>> > Maybe I'm missing something, but I don't see anything ARM-specific
>> > about this driver. If PcdSystemMemoryBase is the reason for placing it
>> > under ArmPkg, can we migrate that option across somewhere else?
>> >
>>
>> Of course it is ARM specific. SMMUs only exist on ARM systems,
>
> SMMUs, yes. IOMMUs, no.
>
>> and the
>> driver reuses ArmMmuLib defines to populate the page table entries.
>
> Ah, managed to miss that bit.
> The remainder of the code looks completely generic.
>
>> In general, I agree with the tendency to keep code out of ArmPkg, but
>> in this case, I think it really belongs there.
>
> I'll concede it makes sense to keep it in ArmPkg for now, and
> re-engineering it for complete portability when there are no other
> potential users is a bit premature.
>
Indeed. Especially because the issue it addresses (i.e., all DRAM
above 4 GB) is not equally likely to occur on other architectures.
>> > Seems an added shame to put two new generic config options
>> > (PcdPciGenericSmmuBase/PcdPciGenericSmmuContextInterrupt) under an
>> > architecture-specific directory.
>> >
>> >> +
>> >> +#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.
>> >> + //
>> >
>> > Request for clarification: is this because this driver is only invoked
>> > where the root bridge is incapable? (The test looks unconditional.)
>> >
>>
>> No, the root bridge driver always invokes the IOMMU protocol if there is one.
>>
>> Here we test whether the mapped buffer already lives in the first 4 GB
>> of DRAM, otherwise we will use a bounce buffer. This driver never maps
>> anything dynamically in the SMMU, it just allocates a bounce buffer
>> from the 32-bit addressable range if required.
>
> Right, so could the comment be updated to say something more like
>
> //
> // If any part of the DMA transfer being mapped is above 4GB, then
> // map the DMA transfer to a buffer below 4GB.
> //
>
> ?
>
To be pedantic, it should say 'to a buffer that is 32-bit addressable
from the PCI side'. Will add that
> The remainder of the comment is also useful, but would it not make
> more sense in the function header?
>
Tbh I took Jiewen's code and added the SMMU/ARM bits. But I will
change it nonetheless.
> With that:
> Reviewed-by: Leif Lindholm <leif.lindholm@linaro.org>
>
Thanks
>> >> + 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 [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