From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail03.groups.io (mail03.groups.io [45.79.227.220]) by spool.mail.gandi.net (Postfix) with ESMTPS id BE8977803D0 for ; Fri, 12 Apr 2024 14:34:15 +0000 (UTC) DKIM-Signature: a=rsa-sha256; bh=Y9SRPkWplfaG+6F+6eGY1vCBn9ntHKz4CjVO7/Cor38=; c=relaxed/simple; d=groups.io; h=Received-SPF:Received-SPF:From:To:CC:Subject:Date:Message-ID:In-Reply-To:References:MIME-Version:NoDisclaimer:Precedence:List-Subscribe:List-Help:Sender:List-Id:Mailing-List:Delivered-To:Resent-Date:Resent-From:Reply-To:List-Unsubscribe-Post:List-Unsubscribe:Content-Type; s=20240206; t=1712932454; v=1; b=ZkLqsCAL47nLd8dgvNw+8MdXb3n2mfVpnpPunM0kqcOazOKKrh+ORlFZZYzWz3G1rbf029Vv nzc0gDus6p/sEaW31msJRjQ0xkoJZS+DwoDgQ2/SGEdzF8IzBjo28V5E+vAcIk+771TCCu7+C1t f1ELFF/u9YdQj25Gl7wPTL+L6kB/l5XD/v6Mzu0amWkkbdwLN+AWDkDHUn4//oK0qoqBr8Cpc5u DB0rArX58flGo+BLpoRFCP8fMWuMyULg0vGt+mDu7ptzjO6jcdNG8Sg2ufzadA+TbVErYp/Cm/n wC4cTPg45fgCzZOHoqwyFxpqgnbsZJLnH0v3YFCzreR1g== X-Received: by 127.0.0.2 with SMTP id 8Op3YY7687511xVdNxvvL4uu; Fri, 12 Apr 2024 07:34:14 -0700 X-Received: from EUR04-DB3-obe.outbound.protection.outlook.com (EUR04-DB3-obe.outbound.protection.outlook.com [40.107.6.54]) by mx.groups.io with SMTP id smtpd.web10.48556.1712932445632609105 for ; Fri, 12 Apr 2024 07:34:06 -0700 X-Received: from AS9P194CA0021.EURP194.PROD.OUTLOOK.COM (2603:10a6:20b:46d::11) by DB4PR08MB9357.eurprd08.prod.outlook.com (2603:10a6:10:3f3::9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7409.55; Fri, 12 Apr 2024 14:33:58 +0000 X-Received: from AMS0EPF00000196.eurprd05.prod.outlook.com (2603:10a6:20b:46d:cafe::2c) by AS9P194CA0021.outlook.office365.com (2603:10a6:20b:46d::11) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7472.26 via Frontend Transport; Fri, 12 Apr 2024 14:33:58 +0000 X-MS-Exchange-Authentication-Results: spf=pass (sender IP is 63.35.35.123) smtp.mailfrom=arm.com; dkim=pass (signature was verified) header.d=armh.onmicrosoft.com;dmarc=pass action=none header.from=arm.com; Received-SPF: Pass (protection.outlook.com: domain of arm.com designates 63.35.35.123 as permitted sender) receiver=protection.outlook.com; client-ip=63.35.35.123; helo=64aa7808-outbound-1.mta.getcheckrecipient.com; pr=C X-Received: from 64aa7808-outbound-1.mta.getcheckrecipient.com (63.35.35.123) by AMS0EPF00000196.mail.protection.outlook.com (10.167.16.217) with Microsoft SMTP Server (version=TLS1_3, cipher=TLS_AES_256_GCM_SHA384) id 15.20.7452.22 via Frontend Transport; Fri, 12 Apr 2024 14:33:58 +0000 X-Received: ("Tessian outbound 9fd7e4b543e6:v313"); Fri, 12 Apr 2024 14:33:58 +0000 X-CheckRecipientChecked: true X-CR-MTA-CID: ec87958b50028dcb X-CR-MTA-TID: 64aa7808 X-Received: from 65be7a1567a4.1 by 64aa7808-outbound-1.mta.getcheckrecipient.com id 7C8741D4-FCD5-4612-882C-E1B713561487.1; Fri, 12 Apr 2024 14:33:45 +0000 X-Received: from EUR04-VI1-obe.outbound.protection.outlook.com by 64aa7808-outbound-1.mta.getcheckrecipient.com with ESMTPS id 65be7a1567a4.1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384); Fri, 12 Apr 2024 14:33:45 +0000 X-Received: from DUZPR01CA0013.eurprd01.prod.exchangelabs.com (2603:10a6:10:3c3::6) by AS8PR08MB9907.eurprd08.prod.outlook.com (2603:10a6:20b:563::16) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7409.46; Fri, 12 Apr 2024 14:33:42 +0000 X-Received: from DU2PEPF00028D01.eurprd03.prod.outlook.com (2603:10a6:10:3c3:cafe::af) by DUZPR01CA0013.outlook.office365.com (2603:10a6:10:3c3::6) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7472.26 via Frontend Transport; Fri, 12 Apr 2024 14:33:42 +0000 X-MS-Exchange-Authentication-Results: spf=pass (sender IP is 40.67.248.234) smtp.mailfrom=arm.com; dkim=none (message not signed) header.d=none;dmarc=pass action=none header.from=arm.com; Received-SPF: Pass (protection.outlook.com: domain of arm.com designates 40.67.248.234 as permitted sender) receiver=protection.outlook.com; client-ip=40.67.248.234; helo=nebula.arm.com; pr=C X-Received: from nebula.arm.com (40.67.248.234) by DU2PEPF00028D01.mail.protection.outlook.com (10.167.242.185) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.20.7452.22 via Frontend Transport; Fri, 12 Apr 2024 14:33:42 +0000 X-Received: from AZ-NEU-EX04.Arm.com (10.251.24.32) by AZ-NEU-EX03.Arm.com (10.251.24.31) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.35; Fri, 12 Apr 2024 14:33:38 +0000 X-Received: from E114225.Arm.com (10.1.196.56) by mail.arm.com (10.251.24.32) with Microsoft SMTP Server id 15.1.2507.35 via Frontend Transport; Fri, 12 Apr 2024 14:33:37 +0000 From: "Sami Mujawar" To: CC: Sami Mujawar , , , , , , , Subject: [edk2-devel] [PATCH v2 26/45] ArmVirtPkg: IoMMU driver to DMA from Realms Date: Fri, 12 Apr 2024 15:33:03 +0100 Message-ID: <20240412143322.5244-27-sami.mujawar@arm.com> In-Reply-To: <20240412143322.5244-1-sami.mujawar@arm.com> References: <20240412143322.5244-1-sami.mujawar@arm.com> MIME-Version: 1.0 X-EOPAttributedMessage: 1 X-MS-TrafficTypeDiagnostic: DU2PEPF00028D01:EE_|AS8PR08MB9907:EE_|AMS0EPF00000196:EE_|DB4PR08MB9357:EE_ X-MS-Office365-Filtering-Correlation-Id: a001dbf6-dbe4-4f6b-047c-08dc5afd96d9 x-checkrecipientrouted: true NoDisclaimer: true X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam-Untrusted: BCL:0; X-Microsoft-Antispam-Message-Info-Original: qVkDk3IFjqWYgVIclgIdfH56pAME68XqQmGOxEwSL/VkbDItAH5BBGvkfKlDSKMlDNc8FCZ//q8yaxAWw7HM65LZS+W5zF0W0z3FGEgqI6XsEx766yBBBHGKfsm367XXimwpIqVSU1bOM+xi8szOxf6g6B4k9BIW6C1yS55tY6yXesFY/uAbKFBusrEbEH3VH4C4SF1ngdhw8XTjrTFB/01puvG/ikZ2kdn17MAuJuJBflOWpGlcakTNAFzZARWxieCNgvNoLVB7yVpdWVCaASN4yEBU+Lr/YFK6wCG8RXVHkAtkJhLN9929nJJ9/albOhGFIE0MhDI2gqcJpVEbuKl/Rvs5Gu0NhNpRGBCa2rtm9gI72qagbRdQHo9nWksp38VHuvP87+zw8XSYXNnolZQj2LBHbVwvrbl5x9aLTEbE1Sp4bn01gMQUdHKU1wMZSRIv+aQGa/UHYbzyxLfDWwczBQHiAdMaab1/WCoIO056SJLPL+5+Ubkg6mEz9wJBkpOPJkx7W/TLbT9t+aYROWX7KrYKdY40aDJ2lyr5sCJlJaz0W2/1ZdKJ521SoqNzw2ywyZ8WE77ebWQ1qd5ChR+V6YfBUZ+atBtoCh9eTz6JBY9a8+M1uWbUvRDdNpi4Xx4C83+nc6DNZfAwIRhG1FZ5w0OBYC9oKqv327/55nm8kRIBMA89BNfh/ZFQdJxamoNK5jM+s3ituepsHeFk/IpIu1mfUSYjKDLF2lrNDO5BQarFRBgx9NMOJ6GsTfSj X-Forefront-Antispam-Report-Untrusted: CIP:40.67.248.234;CTRY:IE;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:nebula.arm.com;PTR:InfoDomainNonexistent;CAT:NONE;SFS:(13230031)(376005)(36860700004)(1800799015)(82310400014);DIR:OUT;SFP:1101; X-MS-Exchange-Transport-CrossTenantHeadersStamped: AS8PR08MB9907 X-MS-Exchange-Transport-CrossTenantHeadersStripped: AMS0EPF00000196.eurprd05.prod.outlook.com X-MS-PublicTrafficType: Email X-MS-Office365-Filtering-Correlation-Id-Prvs: ddc95dae-5841-4c70-73ae-08dc5afd8d9e X-Microsoft-Antispam-Message-Info: 82rwf2WzKYQH2eQd412Gazo2dO9GBCne33wx6GItr3MWINc4HkZB/8uwwqHHcSibIx2HTDjf6qpZBVpKmyZPlLHT7sVbF0bAJSpb35I86SBtsjvYJ+qTy8xRIBGjJVnabHNe2reiJ75xSdZa7W5THkFA8moVI4tO4/sSmnqVIYHh941Bv7A2GqCP5fJ8LPXAqXylalJtmi+/rR/aIINSa+HTVVgNtWyfQuud0tObiyJWixhx6yp3iPdsR4DLMMceCDYUNy3de+vscPAHu4GU6iDMGcVFmIwrx2X6z1EVUTFUdz1lB4n0K8JpMP6Mu+pzFUPKiSqHE78GntxJKyobZCaJ/SHn8SFxmegqDtRi1hadkm3GkYFaceUDnkBNVxRar+svWQ8DQZCE8LjPEJJ6pLJMpSjY2/a948VHTPaFX5LWWEUtGCea5Xx2j5njUjIw+dSViLr0412TQ5JmzW3F6zyhF6NzXCw9nlCRAWMrDnYvqKaXzSxQdJf6TGmm7cV5yAGMnVP3qd4SPS7q7G473xBKi42SVYlu4APzmGOOc64azdG+LjA0nr+Ehdu/HP65WK56gnHMbJh/i4kYRblbDnTWcGOAUyzWQbJTE8RMqCvj7OHKwbIqBk625csRNc2e7+lUyQ4Nzm4fH0Vo5zMFZVbyGIWk6ymiEOfO6jMEgpoWTRNwWXn1XggGnAoSN73y+/7fGzB1EznAz5GVplp14YD/7gclgwRzCuw1q0jwI261lgCO/berMMHFyXznECMc X-OriginatorOrg: arm.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 12 Apr 2024 14:33:58.2860 (UTC) X-MS-Exchange-CrossTenant-Network-Message-Id: a001dbf6-dbe4-4f6b-047c-08dc5afd96d9 X-MS-Exchange-CrossTenant-Id: f34e5979-57d9-4aaa-ad4d-b122a662184d X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=f34e5979-57d9-4aaa-ad4d-b122a662184d;Ip=[63.35.35.123];Helo=[64aa7808-outbound-1.mta.getcheckrecipient.com] X-MS-Exchange-CrossTenant-AuthSource: AMS0EPF00000196.eurprd05.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Anonymous X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: DB4PR08MB9357 Precedence: Bulk List-Subscribe: List-Help: Sender: devel@edk2.groups.io List-Id: Mailing-List: list devel@edk2.groups.io; contact devel+owner@edk2.groups.io Resent-Date: Fri, 12 Apr 2024 07:34:06 -0700 Resent-From: sami.mujawar@arm.com Reply-To: devel@edk2.groups.io,sami.mujawar@arm.com List-Unsubscribe-Post: List-Unsubscribe=One-Click List-Unsubscribe: X-Gm-Message-State: GRGsv1YZQye1E9tt0JNTnCyCx7686176AA= Content-Type: text/plain X-GND-Status: LEGIT Authentication-Results: spool.mail.gandi.net; dkim=pass header.d=groups.io header.s=20240206 header.b=ZkLqsCAL; dmarc=fail reason="SPF not aligned (relaxed), DKIM not aligned (relaxed)" header.from=arm.com (policy=none); spf=pass (spool.mail.gandi.net: domain of bounce@groups.io designates 45.79.227.220 as permitted sender) smtp.mailfrom=bounce@groups.io On Arm CCA systems the access to pages inside the Realm is protected. However, software executing in a Realm needs to interact with the external world. This may be done using para virtualisation of the disk, network interfaces, etc. For this to work the buffers in the Realm need to be shared with the Host. The sharing and management of the Realm buffers is done by the Realm Aperture Management Protocol, which invokes the necessary Realm Service Interfaces to transition the buffers from Protected IPA to Unprotected IPA. The ArmCcaIoMmu driver provides the necessary hooks so that DMA operations can be performed by bouncing buffers using pages shared with the Host. It uses the Realm Aperture Management protocol to share the buffers with the Host. Cc: Ard Biesheuvel Cc: Leif Lindholm Cc: Gerd Hoffmann Signed-off-by: Sami Mujawar --- ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmu.c | 813 ++++++++++++++++++++ ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmu.h | 66 ++ ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.c | 59 ++ ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.inf | 45 ++ 4 files changed, 983 insertions(+) diff --git a/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmu.c b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmu.c new file mode 100644 index 0000000000000000000000000000000000000000..cf52b82218bb9ece7bfedcb6e3a2ced00eff5e92 --- /dev/null +++ b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmu.c @@ -0,0 +1,813 @@ +/** @file + The protocol provides support to allocate, free, map and umap a DMA buffer + for bus master (e.g PciHostBridge). When the execution context is a Realm, + the DMA operations must be performed on buffers that are shared with the Host. + Hence the RAMP protocol is used to manage the sharing of the DMA buffers or + in some cases to bounce the buffers. + + Copyright (c) 2017, AMD Inc. All rights reserved.
+ Copyright (c) 2017, Intel Corporation. All rights reserved.
+ Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include "ArmCcaIoMmu.h" + +/** List of the MAP_INFO structures that have been set up by IoMmuMap() and not + yet torn down by IoMmuUnmap(). The list represents the full set of mappings + currently in effect. +*/ +STATIC LIST_ENTRY mMapInfos = INITIALIZE_LIST_HEAD_VARIABLE (mMapInfos); + +#if !defined (MDEPKG_NDEBUG) + +/** ASCII names for EDKII_IOMMU_OPERATION constants, for debug logging. +*/ +STATIC CONST CHAR8 *CONST +mBusMasterOperationName[EdkiiIoMmuOperationMaximum] = { + "Read", + "Write", + "CommonBuffer", + "Read64", + "Write64", + "CommonBuffer64" +}; +#endif + +/** Pointer to the Realm Aperture Management Protocol +*/ +extern EDKII_REALM_APERTURE_MANAGEMENT_PROTOCOL *mRamp; + +/** + Given the host address find a mapping node in the linked list. + + @param [in] HostAddress Host address. + + @return Pointer to the MapInfo node if found, otherwise NULL. +**/ +STATIC +MAP_INFO * +EFIAPI +FindMappingByHostAddress ( + IN VOID *HostAddress + ) +{ + LIST_ENTRY *Node; + LIST_ENTRY *NextNode; + MAP_INFO *MapInfo; + + for (Node = GetFirstNode (&mMapInfos); Node != &mMapInfos; Node = NextNode) { + NextNode = GetNextNode (&mMapInfos, Node); + MapInfo = CR (Node, MAP_INFO, Link, MAP_INFO_SIG); + if (MapInfo->HostAddress == HostAddress) { + return MapInfo; + } + } + + return NULL; +} + +/** + Map a shared buffer + + @param [in] Operation IoMMU operation to perform. + @param [in] HostAddress Pointer to the Host buffer. + @param [in] NumberOfBytes Number of bytes to map. + @param [in] BbAddress Bounce buffer address. + @param [in] BbPages Number of pages covering the bounce buffer. + @param [out] Mapping Pointer to the MapInfo node. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. +**/ +STATIC +EFI_STATUS +MapSharedBuffer ( + IN EDKII_IOMMU_OPERATION Operation, + IN VOID *HostAddress, + IN UINTN NumberOfBytes, + IN EFI_PHYSICAL_ADDRESS BbAddress, + IN UINTN BbPages, + OUT MAP_INFO **Mapping + ) +{ + EFI_STATUS Status; + MAP_INFO *MapInfo; + + if (BbPages != EFI_SIZE_TO_PAGES (NumberOfBytes)) { + return EFI_INVALID_PARAMETER; + } + + // Allocate a MAP_INFO structure to remember the mapping when Unmap() is + // called later. + MapInfo = AllocateZeroPool (sizeof (MAP_INFO)); + if (MapInfo == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + InitializeListHead (&MapInfo->Link); + + // Initialize the MAP_INFO structure, except the NonParAddress field + MapInfo->Signature = MAP_INFO_SIG; + MapInfo->Operation = Operation; + MapInfo->NumberOfBytes = NumberOfBytes; + MapInfo->NumberOfPages = BbPages; + MapInfo->HostAddress = HostAddress; + MapInfo->BbAddress = BbAddress; + + // Open aperture here + Status = mRamp->OpenAperture ( + BbAddress, + BbPages, + &MapInfo->ApertureRef + ); + if (EFI_ERROR (Status)) { + goto FreeMapInfo; + } + + // Track all MAP_INFO structures. + InsertHeadList (&mMapInfos, &MapInfo->Link); + *Mapping = MapInfo; + return Status; + +FreeMapInfo: + FreePool (MapInfo); + return Status; +} + +/** + Unmap a shared buffer. + + @param [in] MapInfo Pointer to the MapInfo node. + @param [in] MemoryMapLocked The function is executing on the stack of + gBS->ExitBootServices(); changes to the UEFI + memory map are forbidden. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +STATIC +EFI_STATUS +EFIAPI +UnMapSharedBuffer ( + IN MAP_INFO *MapInfo, + IN BOOLEAN MemoryMapLocked + ) +{ + EFI_STATUS Status; + + if (MapInfo == NULL) { + return EFI_INVALID_PARAMETER; + } + + DEBUG (( + DEBUG_VERBOSE, + "%a: HostAddress = 0x%p, BbAddress = 0x%p\n", + __func__, + MapInfo->HostAddress, + MapInfo->BbAddress + )); + Status = mRamp->CloseAperture (MapInfo->ApertureRef); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "Failed to close aperture. Status = %r\n", + Status + )); + } + + RemoveEntryList (&MapInfo->Link); + + if (!MemoryMapLocked) { + FreePool (MapInfo); + } + + return Status; +} + +/** + Provides the controller-specific addresses required to access system memory + from a DMA bus master. On guest Realms, the DMA operations must be performed + on shared buffer hence we allocate a bounce buffer to map the HostAddress to + a DeviceAddress. The Realm Aperture Management protocol is then involved to + open the aperture for sharing the buffer pages with the Host OS. + + @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. + +**/ +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; + MAP_INFO *MapInfo; + EFI_PHYSICAL_ADDRESS BbAddress; + UINTN Pages; + EFI_ALLOCATE_TYPE AllocateType; + + DEBUG (( + DEBUG_VERBOSE, + "%a: Operation=%a Host=0x%p Bytes=0x%lx\n", + __func__, + ((Operation >= 0 && + Operation < ARRAY_SIZE (mBusMasterOperationName)) ? + mBusMasterOperationName[Operation] : + "Invalid"), + HostAddress, + (UINT64)((NumberOfBytes == NULL) ? 0 : *NumberOfBytes) + )); + + if ((HostAddress == NULL) || + (NumberOfBytes == NULL) || + (DeviceAddress == NULL) || + (Mapping == NULL) || + (Operation >= EdkiiIoMmuOperationMaximum) || + (Operation < EdkiiIoMmuOperationBusMasterRead)) + { + return EFI_INVALID_PARAMETER; + } + + BbAddress = MAX_ADDRESS; + Pages = EFI_SIZE_TO_PAGES (*NumberOfBytes); + AllocateType = AllocateAnyPages; + switch (Operation) { + // For BusMasterRead[64] and BusMasterWrite[64] operations, a bounce buffer + // is necessary as the original buffer may not meet the page start/end and + // page size alignment requirements. Also we need to consider the case where + // the original buffer crosses the 4GB limit. + case EdkiiIoMmuOperationBusMasterRead: + case EdkiiIoMmuOperationBusMasterWrite: + BbAddress = BASE_4GB - 1; + AllocateType = AllocateMaxAddress; + // fall through + case EdkiiIoMmuOperationBusMasterRead64: + case EdkiiIoMmuOperationBusMasterWrite64: + // Allocate a bounce buffer. + Status = gBS->AllocatePages ( + AllocateType, + EfiBootServicesData, + Pages, + &BbAddress + ); + if (EFI_ERROR (Status)) { + goto Failed; + } + + // Open aperture here + Status = MapSharedBuffer ( + Operation, + HostAddress, + *NumberOfBytes, + BbAddress, + Pages, + &MapInfo + ); + if (EFI_ERROR (Status)) { + goto FreeBounceBuffer; + } + + break; + + // For BusMasterCommonBuffer[64] operations, the buffer is already allocated + // and mapped in a call to AllocateBuffer(). So, we only need to return the + // device address and the mapping info + case EdkiiIoMmuOperationBusMasterCommonBuffer: + // fall through + case EdkiiIoMmuOperationBusMasterCommonBuffer64: + MapInfo = FindMappingByHostAddress (HostAddress); + if (MapInfo == NULL) { + ASSERT (MapInfo == NULL); + goto Failed; + } + + BbAddress = MapInfo->BbAddress; + break; + + default: + // Operation is invalid + Status = EFI_INVALID_PARAMETER; + goto Failed; + } // switch + + // 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. + // No special action is needed for BusMasterCommonBuffer[64] operations. + if ((Operation == EdkiiIoMmuOperationBusMasterRead) || + (Operation == EdkiiIoMmuOperationBusMasterRead64)) + { + CopyMem ( + (VOID *)(UINTN)BbAddress, + (VOID *)(UINTN)HostAddress, + MapInfo->NumberOfBytes + ); + } + + // Populate output parameters. + *DeviceAddress = BbAddress; + *Mapping = MapInfo; + + DEBUG (( + DEBUG_VERBOSE, + "%a: Mapping=0x%p HostAddress = 0x%p BBAddress = 0x%Lx Pages=0x%Lx\n", + __func__, + MapInfo, + HostAddress, + MapInfo->BbAddress, + MapInfo->NumberOfPages + )); + + return EFI_SUCCESS; + +FreeBounceBuffer: + gBS->FreePages (BbAddress, Pages); + +Failed: + *NumberOfBytes = 0; + return Status; +} + +/** + Completes the Map() operation and releases any corresponding resources. + + This is an internal worker function that only extends the Map() API with + the MemoryMapLocked parameter. + + @param This The protocol instance pointer. + @param MapInfo The mapping value returned from Map(). + @param MemoryMapLocked The function is executing on the stack of + gBS->ExitBootServices(); changes to the UEFI + memory map are forbidden. + + @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 +IoMmuUnmapWorker ( + IN EDKII_IOMMU_PROTOCOL *This, + IN MAP_INFO *MapInfo, + IN BOOLEAN MemoryMapLocked + ) +{ + EFI_STATUS Status; + PHYSICAL_ADDRESS BbAddress; + UINTN Pages; + + DEBUG (( + DEBUG_VERBOSE, + "%a: MapInfo=0x%p MemoryMapLocked=%d\n", + __func__, + MapInfo, + MemoryMapLocked + )); + + if (MapInfo == NULL) { + return EFI_INVALID_PARAMETER; + } + + BbAddress = MapInfo->BbAddress; + Pages = MapInfo->NumberOfPages; + + // For BusMasterWrite[64] operations and BusMasterCommonBuffer[64] operations + // we have to copy the results, ultimately to the original place (i.e., + // "MapInfo->HostAddress"). + // No special operaton is needed for BusMasterCommonBuffer[64] operations. + switch (MapInfo->Operation) { + case EdkiiIoMmuOperationBusMasterCommonBuffer: + case EdkiiIoMmuOperationBusMasterCommonBuffer64: + ASSERT (BbAddress == (PHYSICAL_ADDRESS)MapInfo->HostAddress); + break; + case EdkiiIoMmuOperationBusMasterWrite: + case EdkiiIoMmuOperationBusMasterWrite64: + CopyMem ( + (VOID *)(UINTN)MapInfo->HostAddress, + (VOID *)(UINTN)BbAddress, + MapInfo->NumberOfBytes + ); + break; + + default: + // nothing to do for BusMasterRead[64] operations + break; + } + + // For all other operations, fill the late bounce buffer with zeros, and + // then release it (unless the UEFI memory map is locked). + if ((MapInfo->Operation != EdkiiIoMmuOperationBusMasterCommonBuffer) && + (MapInfo->Operation != EdkiiIoMmuOperationBusMasterCommonBuffer64)) + { + ZeroMem ( + (VOID *)(UINTN)BbAddress, + EFI_PAGES_TO_SIZE (Pages) + ); + + // UnMapSharedPages + Status = UnMapSharedBuffer (MapInfo, MemoryMapLocked); + ASSERT_EFI_ERROR (Status); + + if (!MemoryMapLocked) { + gBS->FreePages (BbAddress, Pages); + } + } + + return Status; +} + +/** + 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. +**/ +EFI_STATUS +EFIAPI +IoMmuUnmap ( + IN EDKII_IOMMU_PROTOCOL *This, + IN VOID *Mapping + ) +{ + return IoMmuUnmapWorker ( + This, + (MAP_INFO *)Mapping, + FALSE // MemoryMapLocked + ); +} + +/** + 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. + +**/ +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 BbAddress; + MAP_INFO *MapInfo; + + // 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 + if (MemoryType != EfiBootServicesData) { + return EFI_INVALID_PARAMETER; + } + + if (Pages >= MAX_UINTN) { + return EFI_INVALID_PARAMETER; + } + + BbAddress = (UINTN)-1; + if ((Attributes & EDKII_IOMMU_ATTRIBUTE_DUAL_ADDRESS_CYCLE) == 0) { + // Limit allocations to memory below 4GB + BbAddress = SIZE_4GB - 1; + } + + Status = gBS->AllocatePages ( + AllocateMaxAddress, + MemoryType, + Pages, + &BbAddress + ); + if (EFI_ERROR (Status)) { + // Set the host address to NULL in case of error + *HostAddress = NULL; + } else { + *HostAddress = (VOID *)(UINTN)BbAddress; + Status = MapSharedBuffer ( + EdkiiIoMmuOperationBusMasterCommonBuffer, + *HostAddress, + EFI_PAGES_TO_SIZE (Pages), + BbAddress, + Pages, + &MapInfo + ); + ASSERT_EFI_ERROR (Status); + } + + 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(). + +**/ +EFI_STATUS +EFIAPI +IoMmuFreeBuffer ( + IN EDKII_IOMMU_PROTOCOL *This, + IN UINTN Pages, + IN VOID *HostAddress + ) +{ + EFI_STATUS Status; + MAP_INFO *MapInfo; + + // Release the common buffer itself. Unmap() has re-encrypted it in-place, so + // no need to zero it. + MapInfo = FindMappingByHostAddress (HostAddress); + if (MapInfo == NULL) { + ASSERT (0); + return EFI_NOT_FOUND; + } else { + // UnMapSharedPages + Status = UnMapSharedBuffer (MapInfo, FALSE); + ASSERT_EFI_ERROR (Status); + } + + return gBS->FreePages ((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 to 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 to translate the device path to an IOMMU device + ID, and set the IOMMU hardware register accordingly. + 1) DeviceHandle can be a standard PCI device. + The memory for BusMasterRead needs EDKII_IOMMU_ACCESS_READ set. + The memory for BusMasterWrite needs EDKII_IOMMU_ACCESS_WRITE set. + The memory for BusMasterCommonBuffer needs + EDKII_IOMMU_ACCESS_READ|EDKII_IOMMU_ACCESS_WRITE set. + 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 initiating the DMA access + request. + @param[in] Mapping The mapping value returned from Map(). + @param[in] IoMmuAccess The IOMMU access. + + @retval EFI_UNSUPPORTED Operation not supported by IOMMU. + +**/ +EFI_STATUS +EFIAPI +IoMmuSetAttribute ( + IN EDKII_IOMMU_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN VOID *Mapping, + IN UINT64 IoMmuAccess + ) +{ + return EFI_UNSUPPORTED; +} + +/** Arm CCA IoMMU protocol +*/ +EDKII_IOMMU_PROTOCOL mArmCcaIoMmu = { + EDKII_IOMMU_PROTOCOL_REVISION, + IoMmuSetAttribute, + IoMmuMap, + IoMmuUnmap, + IoMmuAllocateBuffer, + IoMmuFreeBuffer, +}; + +/** + Notification function that is queued when gBS->ExitBootServices() signals the + EFI_EVENT_GROUP_EXIT_BOOT_SERVICES event group. This function signals another + event, received as Context, and returns. + + Signaling an event in this context is safe. The UEFI spec allows + gBS->SignalEvent() to return EFI_SUCCESS only; EFI_OUT_OF_RESOURCES is not + listed, hence memory is not allocated. The edk2 implementation also does not + release memory (and we only have to care about the edk2 implementation + because EDKII_IOMMU_PROTOCOL is edk2-specific anyway). + + @param[in] Event Event whose notification function is being invoked. + Event is permitted to request the queueing of this + function at TPL_CALLBACK or TPL_NOTIFY task + priority level. + + @param[in] EventToSignal Identifies the EFI_EVENT to signal. EventToSignal + is permitted to request the queueing of its + notification function only at TPL_CALLBACK level. +**/ +STATIC +VOID +EFIAPI +ArmCcaIoMmuExitBoot ( + IN EFI_EVENT Event, + IN VOID *EventToSignal + ) +{ + // (1) The NotifyFunctions of all the events in + // EFI_EVENT_GROUP_EXIT_BOOT_SERVICES will have been queued before + // ArmCcaIoMmuExitBoot() is entered. + // + // (2) ArmCcaIoMmuExitBoot() is executing minimally at TPL_CALLBACK. + // + // (3) ArmCcaIoMmuExitBoot() has been queued in unspecified order relative + // to the NotifyFunctions of all the other events in + // EFI_EVENT_GROUP_EXIT_BOOT_SERVICES whose NotifyTpl is the same as + // Event's. + // + // Consequences: + // + // - If Event's NotifyTpl is TPL_CALLBACK, then some other NotifyFunctions + // queued at TPL_CALLBACK may be invoked after ArmCcaIoMmuExitBoot() + // returns. + // + // - If Event's NotifyTpl is TPL_NOTIFY, then some other NotifyFunctions + // queued at TPL_NOTIFY may be invoked after ArmCcaIoMmuExitBoot() returns; + // plus *all* NotifyFunctions queued at TPL_CALLBACK will be invoked + // strictly after all NotifyFunctions queued at TPL_NOTIFY, including + // ArmCcaIoMmuExitBoot(), have been invoked. + // + // - By signaling EventToSignal here, whose NotifyTpl is TPL_CALLBACK, we + // queue EventToSignal's NotifyFunction after the NotifyFunctions of *all* + // events in EFI_EVENT_GROUP_EXIT_BOOT_SERVICES. + gBS->SignalEvent (EventToSignal); +} + +/** + Notification function that is queued after the notification functions of all + events in the EFI_EVENT_GROUP_EXIT_BOOT_SERVICES event group. The same memory + map restrictions apply. + + This function unmaps all currently existing IOMMU mappings. + + @param[in] Event Event whose notification function is being invoked. Event + is permitted to request the queueing of this function + only at TPL_CALLBACK task priority level. + + @param[in] Context Ignored. +**/ +STATIC +VOID +EFIAPI +ArmCcaIoMmuUnmapAllMappings ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + LIST_ENTRY *Node; + LIST_ENTRY *NextNode; + MAP_INFO *MapInfo; + + // All drivers that had set up IOMMU mappings have halted their respective + // controllers by now; tear down the mappings. + for (Node = GetFirstNode (&mMapInfos); Node != &mMapInfos; Node = NextNode) { + NextNode = GetNextNode (&mMapInfos, Node); + MapInfo = CR (Node, MAP_INFO, Link, MAP_INFO_SIG); + IoMmuUnmapWorker ( + &mArmCcaIoMmu, // This + MapInfo, // Mapping + TRUE // MemoryMapLocked + ); + } +} + +/** + Initialize and install the ArmCca IoMmu Protocol. + + @return RETURN_SUCCESS if successful, otherwise any other error. +**/ +EFI_STATUS +EFIAPI +ArmCcaInstallIoMmuProtocol ( + VOID + ) +{ + EFI_STATUS Status; + EFI_EVENT UnmapAllMappingsEvent; + EFI_EVENT ExitBootEvent; + EFI_HANDLE Handle; + + // Create the "late" event whose notification function will tear down all + // left-over IOMMU mappings. + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, // Type + TPL_CALLBACK, // NotifyTpl + ArmCcaIoMmuUnmapAllMappings, // NotifyFunction + NULL, // NotifyContext + &UnmapAllMappingsEvent // Event + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // Create the event whose notification function will be queued by + // gBS->ExitBootServices() and will signal the event created above. + Status = gBS->CreateEvent ( + EVT_SIGNAL_EXIT_BOOT_SERVICES, // Type + TPL_CALLBACK, // NotifyTpl + ArmCcaIoMmuExitBoot, // NotifyFunction + UnmapAllMappingsEvent, // NotifyContext + &ExitBootEvent // Event + ); + if (EFI_ERROR (Status)) { + goto CloseUnmapAllMappingsEvent; + } + + Handle = NULL; + Status = gBS->InstallMultipleProtocolInterfaces ( + &Handle, + &gEdkiiIoMmuProtocolGuid, + &mArmCcaIoMmu, + NULL + ); + if (!EFI_ERROR (Status)) { + return Status; + } + + // cleanup on error + gBS->CloseEvent (ExitBootEvent); + +CloseUnmapAllMappingsEvent: + gBS->CloseEvent (UnmapAllMappingsEvent); + + return Status; +} diff --git a/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmu.h b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmu.h new file mode 100644 index 0000000000000000000000000000000000000000..070f7bebf5bff84fc3e530e434d62c1205bfb70a --- /dev/null +++ b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmu.h @@ -0,0 +1,66 @@ +/** @file + The protocol provides support to allocate, free, map and umap a DMA buffer + for bus master (e.g PciHostBridge). When the execution context is a Realm, + the DMA operations must be performed on buffers that are shared with the HOST, + hence the RAMP protocol is used to manage the sharing of the DMA buffers or in + some cases bounce the buffers. + + Copyright (c) 2017, Intel Corporation. All rights reserved.
+ Copyright (c) 2017, AMD Inc. All rights reserved.
+ (C) Copyright 2017 Hewlett Packard Enterprise Development LP
+ Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#ifndef ARM_CCA_IOMMU_H_ +#define ARM_CCA_IOMMU_H_ + +#include + +#include +#include +#include +#include +#include +#include + +/** + A macro defning the signature for the MAP_INFO structure. +*/ +#define MAP_INFO_SIG SIGNATURE_64 ('M', 'A', 'P', '_', 'I', 'N', 'F', 'O') + +/** A structure describing the mapping for the buffers shared with the host. +*/ +typedef struct { + /// Signature. + UINT64 Signature; + /// Linked List node entry. + LIST_ENTRY Link; + /// IoMMU operation. + EDKII_IOMMU_OPERATION Operation; + /// Number of bytes. + UINTN NumberOfBytes; + /// Number of pages. + UINTN NumberOfPages; + /// Address of the Host buffer. + VOID *HostAddress; + + /// Address for the Bounce Buffer. + EFI_PHYSICAL_ADDRESS BbAddress; + /// Handle to the Aperture. + EFI_HANDLE ApertureRef; +} MAP_INFO; + +/** + Install IOMMU protocol to provide the DMA support for PciHostBridge and + RAMP. + +**/ +EFI_STATUS +EFIAPI +ArmCcaInstallIoMmuProtocol ( + VOID + ); + +#endif diff --git a/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.c b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.c new file mode 100644 index 0000000000000000000000000000000000000000..deba9dd5e72041f318336141ca8095b4a43d8b9b --- /dev/null +++ b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.c @@ -0,0 +1,59 @@ +/** @file + + IoMmuArmBowDxe driver installs EDKII_IOMMU_PROTOCOL to support + DMA operations when the execution context is a Realm. + + Copyright (c) 2017, AMD Inc. All rights reserved.
+ Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include "ArmCcaIoMmu.h" + +/** Pointer to the Realm Aperture Management Protocol +*/ +EDKII_REALM_APERTURE_MANAGEMENT_PROTOCOL *mRamp = NULL; + +/** Entrypoint of Arm CCA IoMMU Dxe. + + @param [in] ImageHandle Image handle of this driver. + @param [in] SystemTable Pointer to the EFI System Table. + + @return RETURN_SUCCESS if successful, otherwise any other error. +**/ +EFI_STATUS +EFIAPI +ArmCcaIoMmuDxeEntryPoint ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + EFI_HANDLE Handle; + + // When the execution context is a Realm, install ArmCcaIoMmu protocol + // otherwise install the placeholder protocol so that other dependent + // module can run. + Status = gBS->LocateProtocol ( + &gEfiRealmApertureManagementProtocolGuid, + NULL, + (VOID **)&mRamp + ); + if (!EFI_ERROR (Status)) { + // If the Realm Aperture Management Protocol is present + // then the execution context is a Realm. + Status = ArmCcaInstallIoMmuProtocol (); + } else { + DEBUG ((DEBUG_INFO, "Execution context is not a Realm.\n")); + Handle = NULL; + Status = gBS->InstallMultipleProtocolInterfaces ( + &Handle, + &gIoMmuAbsentProtocolGuid, + NULL, + NULL + ); + } + + return Status; +} diff --git a/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.inf b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.inf new file mode 100644 index 0000000000000000000000000000000000000000..b8e125296f4da417a7a07ccbaebce33c29d411e5 --- /dev/null +++ b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.inf @@ -0,0 +1,45 @@ +## @file +# Driver provides the IOMMU protcol support for PciHostBridgeIo and others +# drivers. +# +# Copyright (c) 2017, AMD Inc. All rights reserved.
+# Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x0001001B + BASE_NAME = IoMmuDxe + FILE_GUID = AA6C1A48-A341-439C-950E-CC394FDFE144 + MODULE_TYPE = DXE_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = ArmCcaIoMmuDxeEntryPoint + +[Sources] + ArmCcaIoMmu.c + ArmCcaIoMmu.h + ArmCcaIoMmuDxe.c + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + OvmfPkg/OvmfPkg.dec + ArmVirtPkg/ArmVirtPkg.dec + +[LibraryClasses] + BaseLib + BaseMemoryLib + DebugLib + MemoryAllocationLib + UefiBootServicesTableLib + UefiDriverEntryPoint + +[Protocols] + gEdkiiIoMmuProtocolGuid ## SOMETIME_PRODUCES + gIoMmuAbsentProtocolGuid ## SOMETIME_PRODUCES + gEfiRealmApertureManagementProtocolGuid + +[Depex] + gEfiRealmApertureManagementProtocolGuid -- 'Guid(CE165669-3EF3-493F-B85D-6190EE5B9759)' -=-=-=-=-=-=-=-=-=-=-=- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#117700): https://edk2.groups.io/g/devel/message/117700 Mute This Topic: https://groups.io/mt/105483443/7686176 Group Owner: devel+owner@edk2.groups.io Unsubscribe: https://edk2.groups.io/g/devel/unsub [rebecca@openfw.io] -=-=-=-=-=-=-=-=-=-=-=-