From patchwork Tue May 2 10:32:02 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ard Biesheuvel X-Patchwork-Id: 98439 Delivered-To: patch@linaro.org Received: by 10.140.109.52 with SMTP id k49csp1802204qgf; Tue, 2 May 2017 03:32:39 -0700 (PDT) X-Received: by 10.84.230.135 with SMTP id e7mr41129088plk.20.1493721158913; Tue, 02 May 2017 03:32:38 -0700 (PDT) Return-Path: Received: from ml01.01.org (ml01.01.org. [198.145.21.10]) by mx.google.com with ESMTPS id u11si8425989pgo.283.2017.05.02.03.32.38 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 02 May 2017 03:32:38 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of edk2-devel-bounces@lists.01.org designates 198.145.21.10 as permitted sender) client-ip=198.145.21.10; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@linaro.org; spf=pass (google.com: best guess record for domain of edk2-devel-bounces@lists.01.org designates 198.145.21.10 as permitted sender) smtp.mailfrom=edk2-devel-bounces@lists.01.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: from [127.0.0.1] (localhost [IPv6:::1]) by ml01.01.org (Postfix) with ESMTP id 241F121951CB5; Tue, 2 May 2017 03:32:38 -0700 (PDT) X-Original-To: edk2-devel@lists.01.org Delivered-To: edk2-devel@lists.01.org Received: from mail-wm0-x22b.google.com (mail-wm0-x22b.google.com [IPv6:2a00:1450:400c:c09::22b]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by ml01.01.org (Postfix) with ESMTPS id E20C321A04830 for ; Tue, 2 May 2017 03:32:35 -0700 (PDT) Received: by mail-wm0-x22b.google.com with SMTP id u65so107519686wmu.1 for ; Tue, 02 May 2017 03:32:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id; bh=7I6CKfKmEY+uwC1iTe8Jryj1N5uFF5vD2QQX7o0kM+E=; b=M2CCcQKoDgm9kq1K8Ng99I/lcpQ3Gx1zQ3ft0qL73FTqi7ypnKfWSf/AASO02ObB7J lTHWmMdq57SPPcBK4wb28AZilNmaNGZ7eFVCriD0nPYUSgO3qGEs7CWSD0MzkF7A0SDt 0p11cyM1ziPEKrhQlmoLHwyMOZFbK8YbRussk= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=7I6CKfKmEY+uwC1iTe8Jryj1N5uFF5vD2QQX7o0kM+E=; b=bLkqnmrAMWhGQAjxkTbZQAYHsThLNdlsJ+fOXmZrt1+AhUvSJmn+Wv9e0E3DAJ+nhn Di3zQxVDhPhtLFWzu9mHIChdi6DGJrOV9v0CQLhJi4QjY3vaCyb7PMRAMkAWioGsbJ0m p8FaebsqRhODa3zwrgO4yHkT/uK2fvPecQrdvInW/2wAieFaP3bT1+hPLPt79QU1+CRZ sK9eR5Tx6L0V++znzwGChht30F7XjZjCWyCA6qrVrUyQdGrasnyTFHyi+EcvTzT8jINR CHrv4yoqyuwdLJRaKnH1VkqllVbFJsEE2xTA/bJtiOGsN4Qy55S0b8tvv5Sek6274q7+ cNTw== X-Gm-Message-State: AN3rC/6R5JwTnQW4lmYpckd0vhm8lXDJplN8whaBIyE9ScW9L+M1kxkm VZNfiD2VLJkPvNMl X-Received: by 10.28.175.211 with SMTP id y202mr1685209wme.105.1493721153363; Tue, 02 May 2017 03:32:33 -0700 (PDT) Received: from localhost.localdomain ([105.138.21.12]) by smtp.gmail.com with ESMTPSA id k4sm454635wmf.12.2017.05.02.03.32.29 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 02 May 2017 03:32:31 -0700 (PDT) From: Ard Biesheuvel To: edk2-devel@lists.01.org Date: Tue, 2 May 2017 11:32:02 +0100 Message-Id: <20170502103202.4943-1-ard.biesheuvel@linaro.org> X-Mailer: git-send-email 2.9.3 Subject: [edk2] [RFC PATCH] ArmPkg: implement generic SMMU driver to remap DRAM for 32-bit PCI DMA X-BeenThere: edk2-devel@lists.01.org X-Mailman-Version: 2.1.22 Precedence: list List-Id: EDK II Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: jiewen.yao@intel.com, leo.duran@amd.com, agraf@suse.de, leif.lindholm@linaro.org, Ard Biesheuvel MIME-Version: 1.0 Errors-To: edk2-devel-bounces@lists.01.org Sender: "edk2-devel" 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 --- 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(+) -- 2.9.3 _______________________________________________ edk2-devel mailing list edk2-devel@lists.01.org https://lists.01.org/mailman/listinfo/edk2-devel Reviewed-by: Leif Lindholm 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.
+ Copyright (c) 2017, Linaro Ltd. All rights reserved.
+ + 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 + +#include + +#include +#include +#include +#include +#include + +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.
+ + 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 +#include +#include +#include +#include + +#include +#include +#include + +#include + +#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.
+# +# 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