From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from NAM03-DM3-obe.outbound.protection.outlook.com (mail-dm3nam03on0631.outbound.protection.outlook.com [IPv6:2a01:111:f400:fe49::631]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by ml01.01.org (Postfix) with ESMTPS id 0143821A16E27 for ; Wed, 10 May 2017 15:09:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=amdcloud.onmicrosoft.com; s=selector1-amd-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version; bh=fD0yB6D08s04cxVNl71N9U10nR+Fdv+D9W5lLVtepfc=; b=G7Md2+FHsaZnXZIyA26G0oKdfSnZ5mY3xVyvcV+KoHwMSfbgk6ISkEUgLU+6AhjSmcPt3iOLXzq1ghdg74cz/Ufml93/OmhhbCnsF1hNHVNVFP8laCwNWgrHoiyVK0awWm7bhrgomjTucwiEUBmf9QGWxTtVkpz5zGvTNf/doRc= Authentication-Results: lists.01.org; dkim=none (message not signed) header.d=none;lists.01.org; dmarc=none action=none header.from=amd.com; Received: from brijesh-build-machine.amd.com (165.204.77.1) by CY1PR12MB0149.namprd12.prod.outlook.com (10.161.173.19) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256) id 15.1.1075.11; Wed, 10 May 2017 22:09:46 +0000 From: Brijesh Singh To: CC: , , Brijesh Singh , Jordan Justen , Laszlo Ersek , Jiewen Yao Date: Wed, 10 May 2017 18:09:15 -0400 Message-ID: <1494454162-9940-7-git-send-email-brijesh.singh@amd.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1494454162-9940-1-git-send-email-brijesh.singh@amd.com> References: <1494454162-9940-1-git-send-email-brijesh.singh@amd.com> MIME-Version: 1.0 X-Originating-IP: [165.204.77.1] X-ClientProxiedBy: MWHPR21CA0051.namprd21.prod.outlook.com (10.172.93.141) To CY1PR12MB0149.namprd12.prod.outlook.com (10.161.173.19) X-MS-PublicTrafficType: Email X-MS-Office365-Filtering-Correlation-Id: a4942422-3fe9-4bd0-ceea-08d497f14547 X-MS-Office365-Filtering-HT: Tenant X-Microsoft-Antispam: UriScan:; BCL:0; PCL:0; RULEID:(22001)(48565401081)(201703131423075)(201703031133081); SRVR:CY1PR12MB0149; X-Microsoft-Exchange-Diagnostics: 1; CY1PR12MB0149; 3:E9z9Qb2Jl17acRgf2navp82jRMZUs14RXlruh+lCm/vYyHSFx2alAGPp0X/moKiRosw48J3zoMYoiLprWj4T/HCNHm9LXIldemNJ7AuUO2mr+oRDlk6hUS02+3nKyxcHmYN+BHzUAPgVNclAXaORZHJaCLkOzhmiwqlysH9SK3y+GfjSDMqgip+J2/edRM0dS4yXwLHykwc7vk8Sgfli1qkKFCXe26P6hx/Xb5GL+tNcj5tRGYzJb7pXLbvEUQ4H+Iz7GaxErXd/g33BgSpVLhkMwJvQvn3vFMWD4R4CqtTMO7L79/+1at16ZNdsJeHMO+FKJ4iWY05FIMhYObWpKq2CP7r6dpcldyXI59OsG7w=; 25:uVlxhOw61aBueeYY8Kyu1kifvgOF9DrLZsb+MjTmQN8Rliz0taV9Sz7X8+6QeHo4jzTX9o/gu9ffe/ZuvObMuupAJP2bdAeeQpgodOILmuL+03rwtqeA4I3+43J8Spr9dDrwbenPidmmd016tDbOny4GGcuKPFHJs8BZDwgtqn/2pm4ZmiI5aEtQNIOH304gSszYQpuLDY1U9BWQy5O7SMv2swl13tyAdFnS4E9EZgGM289aC98MNo2Uek2Cc3EV4D3OmwS2Ci+fKu0iqLv3mxw1wW2fcFhDmWjWlWDXsV1woEV3rHtn+U0MNM+ORrmrOrrkcGxcwoXnU60+M4w4FQq8JKqpa1US+hzgiCwBEHf4XHnD+7KCKbPAXM9pd8tBzR3vLg8QvmkXKtOlJ4pRNKwCI0j+B23oZzs3Nf1kCy9+0aClORqntlHIdKvY9+opIgTmeE3KGlZk8mClYVFmz2ha/YmF7lIM26R8o4eUqBw= X-Microsoft-Exchange-Diagnostics: 1; CY1PR12MB0149; 31:Br0hHgtuYqBdCd2bxXMRf5W0cfPCGJ0fiYmPuk50izb0aazN3f6U6W7vuxBGY6rKPRi+b7JocvSwpX5PI0NqFc3VLkpu7N8AbTJZ+3Fi5FdXddoGWoXMEwwmyAL/DYLrWJC23js2+hFOWFlzjnvi6CRbSSrpRPMWbi+FcO6S8AXutXjq7ao5HVXnTNui9rIfhP6sh79p/oV22nx+3pteEopwE7IYlrrPTKdvrjw+MyaJW2XGr78YHEuU5RgqRxSW8BE/hmYyGWKiecsnBVaxJw==; 20:zSWApdxC3eUzetRShdEuTZuYk9QR2cNgJ9A2avcRsDe8gEKZbE4oaO4eVpDfl1QiPamtP8ber67rUWHmRW8sgvI3pR7ueEZ0qG0S72r3wNxB0zJRnIg2impmuA48w1hDGmIjVjZSi+KyfsFULTKN3M/45MyavM3r/lNuosQk74estDEdsOg7XQ3fTzqrnIgrV2cZeR4DJlhcec6A7lXQfe+Qj7E1eP02VX46xpggaX32dnk9e1CRXMYIvs4UyHb9VlriV97bAZY/rAib78CeK18A1NwZHCn3xikMZloqHZvxYUGP/ZxnByIpgM/rDtaU/HVGhb+aM2a5lZmh79+sid84p+ztY8PkTNfugqVh1Dn57eVEsx1KTE/PRDmnV2VVEmg1IDuknm9pFiWWC0BvFdv9UfibBLLcSe0k4AZlCtUf46bYra36OUCvkgLWZizBDLXZaYZuZpJMUmhtgA1gcFvxhwoEz+Bb8O3giEJK2gh0pEaDSO7OZXGgVG59vEkn X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:(767451399110)(162533806227266)(228905959029699); X-Exchange-Antispam-Report-CFA-Test: BCL:0; PCL:0; RULEID:(6040450)(601004)(2401047)(5005006)(8121501046)(93006095)(93001095)(10201501046)(3002001)(6055026)(6041248)(201703131423075)(201702281528075)(201703061421075)(201703061406153)(20161123562025)(20161123558100)(20161123560025)(20161123555025)(20161123564025)(6072148); SRVR:CY1PR12MB0149; BCL:0; PCL:0; RULEID:; SRVR:CY1PR12MB0149; X-Microsoft-Exchange-Diagnostics: 1; CY1PR12MB0149; 4:uGQY7MybBOXMYlh2DJ+PimThAjDlKdboLxkhSX/EZ9PnHeqebpCVcT4N4y1gNpJ0zYBYitdJOLCngsqDLsMhtNm/mv2n6FTrSiGU7zi+KnqCJ1NXfvZB3iXOhGBARTxF5/6vhaxFHr7TezryPy/GINzbxSdhzPpnQ8qP7tuorZ0OEjJgHO9vAJtsnbxy/YYxmbFeKA6f6l/kFBYlijCoNWJVmgMCqQiXwsRTp5e3Q4JQbei7ygyvU4MvITD3SHEiUHroFOTYvrkfJtQBnjBFLyU8RY7s4BvqkTFIlZzAInDibYn7fM1a5ouDecx8J3bJvJ7w/Eqy7IVy5E7e/1l5IGqTc9hxSbeLXWP8aYz6GlkbGPFXi7uqVbw4GLWw6u/vDE/tt1Um8a9TlaenaRIXShXS49E0hLcxgxQ33O7bZhKS6mfQyrx98n7sr6uF5M/Uaetb/0FR97jiHnm23EYKtgEjSt139rpNplDJfZMyd4AmACtIjewRJL4qnmeDGw0mIjM+uP6iXsZppKD/50TK3H8AAFYqqNGuL0JbqWvBENpmwfylcJdoz+SNC4KqJ7xyR8a+jmm2v/8nz3y6AiHzu3ILtk+LJc/b8MXomZn6qtBlpM8jsDeceD/EqmE+s9i5+nHy+vIyBqDyhkUZZeM8QCu96g+8ouhk1rkpPybb6CY6DShdfVKKbpFESCNM8Xw3RSSEYKIrLXnGo8sTC6xXDsbeFTcfGohIRwFrn/8WIJ0M8RaCgt8ETQi7n33MNgs9biQ7aPxKc8nrl7Jm6mIazqA7mM5xXF1Ax/eKybvi8jh+87XD2vu34uhziBZsWQSXsKhWYauAzFC9FGJ4CjOPmarw16k/v9yAPHOxhZ5zYQ+xH6DCuS/D7dWgohFpRytFhy2kVorNmjC42tHBAtkkNxHgH0cnzhYa5uFgarMkzGE= X-Forefront-PRVS: 03030B9493 X-Forefront-Antispam-Report: SFV:NSPM; SFS:(10009020)(4630300001)(6009001)(39840400002)(39400400002)(39410400002)(39850400002)(50986999)(2950100002)(6666003)(6916009)(6486002)(76176999)(50466002)(33646002)(478600001)(86362001)(189998001)(48376002)(110136004)(8676002)(4326008)(2906002)(6306002)(54906002)(38730400002)(53376002)(53936002)(966004)(3846002)(6116002)(50226002)(25786009)(81166006)(5003940100001)(5660300001)(53416004)(36756003)(42186005)(66066001)(7736002)(15188155005)(305945005)(2351001)(16799955002)(19627235001); DIR:OUT; SFP:1101; SCL:1; SRVR:CY1PR12MB0149; H:brijesh-build-machine.amd.com; FPR:; SPF:None; MLV:sfv; LANG:en; X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1; CY1PR12MB0149; 23:WMjLJYs6WcJtJAqG0AeuxL+22a61kLUvs/e7uzSZ6?= =?us-ascii?Q?0F6XHOppaXdIe3RMhjsNU7wWbzNpOKjkPITSraFHv4rZfkvEFv5lsPCBkQtU?= =?us-ascii?Q?YFJhv5xiKtjVsJu1rL14Z/flA+p+pqW71FU7OMgPyItncYfSEyWh5VnEazLk?= =?us-ascii?Q?Pi5p5YYhuY1ZoDnIo8DkZcd1B8SqxcrQZcL1nrGZ51KQ993ozDkg/Nz6W4tH?= =?us-ascii?Q?WVGHd8vxc9BSLS2W2tJRyqMuYAjLuH5YTunfsDF5Nrg9U0gHB2KyBaSN60oE?= =?us-ascii?Q?aV4pMzUWVvKrQ1Ko97qHjaW5MqPcm+CqNyXLQjHSYgEjHSVBMzwSXigxzlOd?= =?us-ascii?Q?ENQ+WvGKUSiazHPAINIGWuDanGuL7d3BCkgfGchRmkxzntnMFyftqmcj+/1d?= =?us-ascii?Q?ztj7ULFc+Gi+hcCI0MuPqSU5px5/x/sm6LbYbeEzqJMB6SQ/q5ca8G827CEo?= =?us-ascii?Q?bler2UHEICx0DUPuwgp7n+tJkqP0JeambXyaqaadoZgxtQ/3ozpwE73e/HRd?= =?us-ascii?Q?YEt3F1Wx3p0xxexRjpiBlpc447GWkU7wMyERv20DyJjulF5yg/LvnIceAFMw?= =?us-ascii?Q?FK9aiZLHcK4fGc4AU38w8MxB8oZZx51tPjuhSeRRcyMP8lCIg0rAlSSSWKyK?= =?us-ascii?Q?3iUnTKgiToVduv3CgrnYSrd/HqQ9ZVSTGzy0D9JHAON8Y9E7pKv+3O4weMqq?= =?us-ascii?Q?lbdd8een3QhaU0qRZ3reJpBKzAT4gC3M187kQOvKFaD1WLypYGhzpOlTl3F9?= =?us-ascii?Q?/O5pqzjDdC4qaiIR3TQEVZAP40zHxXmqng1vvvm6cqrKnCYKfTw9ukNEZOeG?= =?us-ascii?Q?go27pandetHcJGagE1SUA1ppnaz6GgntkSqZ1Clact80+t2pR6h447naJqQQ?= =?us-ascii?Q?DEzj2SxeagONB79Glwen1RyJCVEC/nLWbwm4yZEuuJOe/UM9ad5X6IPHrpnJ?= =?us-ascii?Q?pxIba+5Sp9egZwqwVfuH9MMFhMK/WRG1xiwdoMRHAwBmsnfJCq3Ovq3evwtA?= =?us-ascii?Q?/MYMcRnZLIuuLYbLzxY27KmYxGfNv12zpbqFEkQ+hu7IpIP1m/GcFk/5j45E?= =?us-ascii?Q?CPCQGjuLvqwDMLxUYDvvRZJi63ElNu5gjh0JnCTrfR+ugKR5iyIO4sAQgEln?= =?us-ascii?Q?JBpcZvlgVIOHWHCP37PzLrgj9qjhNeq?= X-Microsoft-Exchange-Diagnostics: 1; CY1PR12MB0149; 6:a8wIAk4ffw+afLkd6g6h84VL/YZBR4eVRlnyzxrFqTBRKcCyqSwMyonLhVOAoIJOK0U3/h8i+8EB1ODLEBJRBMl3kCZUgsQZEaWC2a6mA6EUyFNFPdecMbuMP4FlXjVUx4gxHr3EPfPtYTw9xUZN2rOLCtByuva5J/+xk9d8BiVrtVvYV6eT1tQ7BRr7tgSl9FTTB14amweoEPXbbk9ju4udHswpbdYQUoDyqCHE1+4rFxeS7kLayn1CzEgZTtyZXZsicVgKW5Thm3IXd3kiEo8E3DRK1Xgrqj5o0nDsypBpCEz8gZ5tgm/TnUcObTLFIvBVfJ4GGwyBeHNBX6CXjeV6EXNufW/t5XE6ZY71BDKWQzYwKuXQl926D/HHCrdzDWlvrAh3Tb21EpRnA0TZ+07qzXJM+ktcoSDoD+E7WBaYqACM1XYaeM8yS+CL15ThqevsVEwZh+54s+BVOiFr4yjamhqhqaVhcIb5XQj6FKgF0epNb+owj6WjDsVP11eAnb/JMI9LXNHQBLVNrDWZeYNCPENlEGkDDH3iN0X/5Dg=; 5:yKCRV+2BzvuJvpgOiJyl4jNq4VAR+Wm1LCcfLLiz2sn7b6+Jb59gkeMmDM962qjGwf+vXg01jRvldq+4M4Ffz4LgmT/aFRM7qUzJV5z2ySQ+MYQC1ND5IkVhBnOLd/X4KIvpvD2iL/FMT11cr4GOjg==; 24:BrWhLXURmmJC/SWo8xWPBBcAN3kAMd4lJ7mgCbnVfqn1q6zq5OnmaytJsMeGy3J3mZbwYQCf8j7EzxFYFLhOYpZ1HheO0ar0tf1tBoj/jaw= SpamDiagnosticOutput: 1:99 SpamDiagnosticMetadata: NSPM X-Microsoft-Exchange-Diagnostics: 1; CY1PR12MB0149; 7:GEUEW9IfL3lqAJCd8v908bGhFainPXiFxm/mWvuywtze9AfdJnyivYcuKbwOLcZEZQwOLVqt6vZxvdbOI+ehhjk+hxcyNT8QHan02dx8omxRRQbiOg1YfP2Kd8eC6ourqkTSnmy7pMTv6ZziT8ibmi9tkicuMDXT9eixpvl+Mva3mZ4plo42obGEsVdn8y6XlOxzgprm23x8p68lzaZZe5haxu/UwERXENtRo3yQpEe8i3exHOUeTDHa+cJeEMJ0L6y3FKZD95n8/I1MYXg4eOWAXz+h5OupQfSfZZ2Kwk/HylJ6Y4y2sS5hdyEB6ZAIhKpcf9LIhJOo5oocttlf8g==; 20:PE6U6SUS4L4QQZnM8k34auqX41x0tRq2LwvqvuRGFDJg9vFcPx4mzBMH9GBZ4TftKEgRU9iiwn6RK+wCv8zmTtrlLGRCT1GOxnjax1iMXDtJtk/M20ytBZRbx3eiREpUJZu85g8WH4ihFWzddGIK1SXG6b2XyZRxhNlXF+G/MWc3JzS6Lfb2mVd6dCUAbF1MglEwEmOg797ucUvCWlvu3sinWCqxBgw+AgFGmQBd63CnOuFn0YFg8XtkM45RjJQG X-OriginatorOrg: amd.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 10 May 2017 22:09:46.3843 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-Transport-CrossTenantHeadersStamped: CY1PR12MB0149 Subject: [RFC v4 06/13] OvmfPkg:AmdSevDxe: add AmdSevDxe driver 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: , X-List-Received-Date: Wed, 10 May 2017 22:09:49 -0000 Content-Type: text/plain When SEV is enabled, the MMIO memory range must be mapped as unencrypted (i.e C-bit cleared) and DMA must be performed on unencrypted memory. The patch adds a DXE driver that runs early in boot and clears the memory encryption attribute from MMIO/NonExistent memory ranges and installs a IOMMU protocol to provide the DMA support for PCIHostBridge and other drivers. The driver produces IOMMU protocol introduce by Jiewen https://lists.01.org/pipermail/edk2-devel/2017-May/010462.html Cc: Jordan Justen Cc: Laszlo Ersek Cc: Leo Duran Cc: Jiewen Yao Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Brijesh Singh --- OvmfPkg/OvmfPkgIa32X64.dsc | 1 + OvmfPkg/OvmfPkgX64.dsc | 1 + OvmfPkg/OvmfPkgIa32X64.fdf | 2 + OvmfPkg/OvmfPkgX64.fdf | 2 + OvmfPkg/AmdSevDxe/AmdSevDxe.inf | 49 +++ OvmfPkg/AmdSevDxe/AmdSevIommu.h | 43 ++ OvmfPkg/AmdSevDxe/AmdSevMmio.h | 41 ++ OvmfPkg/AmdSevDxe/AmdSevDxe.c | 52 +++ OvmfPkg/AmdSevDxe/AmdSevIommu.c | 459 ++++++++++++++++++++ OvmfPkg/AmdSevDxe/AmdSevMmio.c | 50 +++ 10 files changed, 700 insertions(+) diff --git a/OvmfPkg/OvmfPkgIa32X64.dsc b/OvmfPkg/OvmfPkgIa32X64.dsc index 9403f76ce862..ee6f98d68b73 100644 --- a/OvmfPkg/OvmfPkgIa32X64.dsc +++ b/OvmfPkg/OvmfPkgIa32X64.dsc @@ -827,6 +827,7 @@ [Components.X64] !endif OvmfPkg/PlatformDxe/Platform.inf + OvmfPkg/AmdSevDxe/AmdSevDxe.inf !if $(SMM_REQUIRE) == TRUE OvmfPkg/SmmAccess/SmmAccess2Dxe.inf diff --git a/OvmfPkg/OvmfPkgX64.dsc b/OvmfPkg/OvmfPkgX64.dsc index e137143f7afa..b5f26e06e60b 100644 --- a/OvmfPkg/OvmfPkgX64.dsc +++ b/OvmfPkg/OvmfPkgX64.dsc @@ -825,6 +825,7 @@ [Components] !endif OvmfPkg/PlatformDxe/Platform.inf + OvmfPkg/AmdSevDxe/AmdSevDxe.inf !if $(SMM_REQUIRE) == TRUE OvmfPkg/SmmAccess/SmmAccess2Dxe.inf diff --git a/OvmfPkg/OvmfPkgIa32X64.fdf b/OvmfPkg/OvmfPkgIa32X64.fdf index 5233314139bc..12871860d001 100644 --- a/OvmfPkg/OvmfPkgIa32X64.fdf +++ b/OvmfPkg/OvmfPkgIa32X64.fdf @@ -190,6 +190,7 @@ [FV.DXEFV] APRIORI DXE { INF MdeModulePkg/Universal/DevicePathDxe/DevicePathDxe.inf INF MdeModulePkg/Universal/PCD/Dxe/Pcd.inf + INF OvmfPkg/AmdSevDxe/AmdSevDxe.inf !if $(SMM_REQUIRE) == FALSE INF OvmfPkg/QemuFlashFvbServicesRuntimeDxe/FvbServicesRuntimeDxe.inf !endif @@ -351,6 +352,7 @@ [FV.DXEFV] INF OvmfPkg/QemuVideoDxe/QemuVideoDxe.inf INF OvmfPkg/VirtioGpuDxe/VirtioGpu.inf INF OvmfPkg/PlatformDxe/Platform.inf +INF OvmfPkg/AmdSevDxe/AmdSevDxe.inf !if $(SMM_REQUIRE) == TRUE INF OvmfPkg/SmmAccess/SmmAccess2Dxe.inf diff --git a/OvmfPkg/OvmfPkgX64.fdf b/OvmfPkg/OvmfPkgX64.fdf index 36150101e784..ae6e66a1c08d 100644 --- a/OvmfPkg/OvmfPkgX64.fdf +++ b/OvmfPkg/OvmfPkgX64.fdf @@ -190,6 +190,7 @@ [FV.DXEFV] APRIORI DXE { INF MdeModulePkg/Universal/DevicePathDxe/DevicePathDxe.inf INF MdeModulePkg/Universal/PCD/Dxe/Pcd.inf + INF OvmfPkg/AmdSevDxe/AmdSevDxe.inf !if $(SMM_REQUIRE) == FALSE INF OvmfPkg/QemuFlashFvbServicesRuntimeDxe/FvbServicesRuntimeDxe.inf !endif @@ -351,6 +352,7 @@ [FV.DXEFV] INF OvmfPkg/QemuVideoDxe/QemuVideoDxe.inf INF OvmfPkg/VirtioGpuDxe/VirtioGpu.inf INF OvmfPkg/PlatformDxe/Platform.inf +INF OvmfPkg/AmdSevDxe/AmdSevDxe.inf !if $(SMM_REQUIRE) == TRUE INF OvmfPkg/SmmAccess/SmmAccess2Dxe.inf diff --git a/OvmfPkg/AmdSevDxe/AmdSevDxe.inf b/OvmfPkg/AmdSevDxe/AmdSevDxe.inf new file mode 100644 index 000000000000..775dda9be386 --- /dev/null +++ b/OvmfPkg/AmdSevDxe/AmdSevDxe.inf @@ -0,0 +1,49 @@ +#/** @file +# +# Driver clears the encryption attribute from MMIO regions and installs IOMMU +# protcol to provides DMA support for PciHostBridge and others +# +# Copyright (c) 2017, AMD Inc. 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 = 1.25 + BASE_NAME = AmdSevDxe + FILE_GUID = 2ec9da37-ee35-4de9-86c5-6d9a81dc38a7 + MODULE_TYPE = DXE_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = AmdSevDxeEntryPoint + +[Sources] + AmdSevDxe.c + AmdSevIommu.c + AmdSevMmio.c + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + OvmfPkg/OvmfPkg.dec + +[LibraryClasses] + BaseLib + UefiLib + UefiDriverEntryPoint + UefiBootServicesTableLib + DxeServicesTableLib + DebugLib + MemEncryptSevLib + +[Protocols] + gEdkiiIoMmuProtocolGuid ## PRODUCES + +[Depex] + TRUE diff --git a/OvmfPkg/AmdSevDxe/AmdSevIommu.h b/OvmfPkg/AmdSevDxe/AmdSevIommu.h new file mode 100644 index 000000000000..5712cb57052d --- /dev/null +++ b/OvmfPkg/AmdSevDxe/AmdSevIommu.h @@ -0,0 +1,43 @@ +/** @file + + The protocol provides support to allocate, free, map and umap a DMA buffer for + bus master (e.g PciHostBridge). When SEV is enabled, the DMA operations must + be performed on unencrypted buffer hence protocol clear the encryption bit + from the DMA buffer. + + Copyright (c) 2017, Intel Corporation. All rights reserved.
+ Copyright (c) 2017, AMD Inc. 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. + +**/ + +#ifndef __AMDSEVIOMMU_H_ +#define __AMDSEVIOMMU_H + +#include + +#include +#include +#include +#include +#include +#include + +/** + Install IOMMU protocol to provide the DMA support for PciHostBridge and + MemEncryptSevLib. + +**/ +VOID +EFIAPI +AmdSevInstallIommuProtocol ( + VOID + ); + +#endif diff --git a/OvmfPkg/AmdSevDxe/AmdSevMmio.h b/OvmfPkg/AmdSevDxe/AmdSevMmio.h new file mode 100644 index 000000000000..c6191025d921 --- /dev/null +++ b/OvmfPkg/AmdSevDxe/AmdSevMmio.h @@ -0,0 +1,41 @@ +/** @file + + Implements routines to clear C-bit from MMIO Memory Range + + Copyright (c) 2017, AMD Inc. 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. + +**/ + +#ifndef __AMDSEVMMIO_H_ +#define __AMDSEVMMIO_H + +#include +#include +#include +#include +#include +#include +#include + +/** + + Iterate through the GCD map and clear the C-bit from MMIO and NonExistent + memory space. The NonExistent memory space will be used for mapping the MMIO + space added later (eg PciRootBridge). By clearing both known NonExistent + memory space can gurantee that any MMIO mapped later will have C-bit cleared. +*/ +VOID +EFIAPI +AmdSevClearEncMaskMmioRange ( + VOID + ); + +#endif diff --git a/OvmfPkg/AmdSevDxe/AmdSevDxe.c b/OvmfPkg/AmdSevDxe/AmdSevDxe.c new file mode 100644 index 000000000000..e22e7ef7314f --- /dev/null +++ b/OvmfPkg/AmdSevDxe/AmdSevDxe.c @@ -0,0 +1,52 @@ +/** @file + + AMD Sev Dxe driver. The driver runs early in DXE phase and clears C-bit from + MMIO space and installs EDKII_IOMMU_PROTOCOL to provide the support for DMA + operations when SEV is enabled. + + Copyright (c) 2017, AMD Inc. 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 "AmdSevMmio.h" +#include "AmdSevIommu.h" + +EFI_STATUS +EFIAPI +AmdSevDxeEntryPoint ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + // + // Do nothing when SEV is not enabled + // + if (!MemEncryptSevIsEnabled ()) { + return EFI_SUCCESS; + } + + // + // Clear C-bit from MMIO Memory Range + // + AmdSevClearEncMaskMmioRange (); + + // + // Install IOMMU protocol to provide DMA support for PCIHostBridgeIo and + // AmdSevMemEncryptLib. + // + AmdSevInstallIommuProtocol (); + + return EFI_SUCCESS; +} diff --git a/OvmfPkg/AmdSevDxe/AmdSevIommu.c b/OvmfPkg/AmdSevDxe/AmdSevIommu.c new file mode 100644 index 000000000000..9b35469ca34f --- /dev/null +++ b/OvmfPkg/AmdSevDxe/AmdSevIommu.c @@ -0,0 +1,459 @@ +/** @file + AmdSevIommu related function + + The protocol provides support to allocate, free, map and umap a DMA buffer for + bus master (e.g PciHostBridge). When SEV is enabled, the DMA operations must + be performed on unencrypted buffer hence we use a bounce buffer to map the host + buffer into an unencrypted buffer. + + Copyright (c) 2017, AMD Inc. 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 "AmdSevIommu.h" + +typedef struct { + EDKII_IOMMU_OPERATION Operation; + UINTN NumberOfBytes; + UINTN NumberOfPages; + EFI_PHYSICAL_ADDRESS HostAddress; + EFI_PHYSICAL_ADDRESS DeviceAddress; +} MAP_INFO; + +#define NO_MAPPING (VOID *) (UINTN) -1 + +/** + Provides the controller-specific addresses required to access system memory from a + DMA bus master. On SEV guest, the DMA operations must be performed on shared + buffer hence we allocate a bounce buffer to map the HostAddress to a DeviceAddress. + The Encryption attribute is removed from the DeviceAddress buffer. + + @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; + EFI_PHYSICAL_ADDRESS PhysicalAddress; + MAP_INFO *MapInfo; + EFI_PHYSICAL_ADDRESS DmaMemoryTop; + EFI_ALLOCATE_TYPE AllocateType; + + 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; + } + PhysicalAddress = (EFI_PHYSICAL_ADDRESS) (UINTN) HostAddress; + + DmaMemoryTop = (UINTN)-1; + AllocateType = AllocateAnyPages; + + if (((Operation != EdkiiIoMmuOperationBusMasterRead64 && + Operation != EdkiiIoMmuOperationBusMasterWrite64 && + Operation != EdkiiIoMmuOperationBusMasterCommonBuffer64)) && + ((PhysicalAddress + *NumberOfBytes) > 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. + // + DmaMemoryTop = SIZE_4GB - 1; + AllocateType = AllocateMaxAddress; + + if (Operation == EdkiiIoMmuOperationBusMasterCommonBuffer || + Operation == EdkiiIoMmuOperationBusMasterCommonBuffer64) { + // + // 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; + } + } + + // + // CommandBuffer was allocated by us (AllocateBuffer) and is already in + // unencryted buffer so no need to create bounce buffer + // + if (Operation == EdkiiIoMmuOperationBusMasterCommonBuffer || + Operation == EdkiiIoMmuOperationBusMasterCommonBuffer64) { + *Mapping = NO_MAPPING; + *DeviceAddress = PhysicalAddress; + + return EFI_SUCCESS; + } + + // + // 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->Operation = Operation; + MapInfo->NumberOfBytes = *NumberOfBytes; + MapInfo->NumberOfPages = EFI_SIZE_TO_PAGES (MapInfo->NumberOfBytes); + MapInfo->HostAddress = PhysicalAddress; + MapInfo->DeviceAddress = DmaMemoryTop; + + // + // Allocate a buffer to map the transfer to. + // + Status = gBS->AllocatePages ( + AllocateType, + EfiBootServicesData, + MapInfo->NumberOfPages, + &MapInfo->DeviceAddress + ); + if (EFI_ERROR (Status)) { + FreePool (MapInfo); + *NumberOfBytes = 0; + return Status; + } + + // + // Clear the memory encryption mask from the device buffer + // + Status = MemEncryptSevClearPageEncMask (0, MapInfo->DeviceAddress, MapInfo->NumberOfPages, TRUE); + ASSERT_EFI_ERROR(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->DeviceAddress, + (VOID *) (UINTN) MapInfo->HostAddress, + MapInfo->NumberOfBytes + ); + } + + // + // The DeviceAddress is the address of the maped buffer below 4GB + // + *DeviceAddress = MapInfo->DeviceAddress; + + // + // Return a pointer to the MAP_INFO structure in Mapping + // + *Mapping = MapInfo; + + DEBUG ((DEBUG_VERBOSE, "%a Host 0x%Lx Device 0x%Lx Pages 0x%Lx Bytes 0x%Lx\n", + __FUNCTION__, MapInfo->DeviceAddress, MapInfo->HostAddress, + MapInfo->NumberOfPages, MapInfo->NumberOfBytes)); + + 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. +**/ +EFI_STATUS +EFIAPI +IoMmuUnmap ( + IN EDKII_IOMMU_PROTOCOL *This, + IN VOID *Mapping + ) +{ + MAP_INFO *MapInfo; + EFI_STATUS Status; + + if (Mapping == NULL) { + return EFI_INVALID_PARAMETER; + } + + // + // See if the Map() operation associated with this Unmap() required a mapping + // buffer. If a mapping buffer was not required, then this function simply + // buffer. If a mapping buffer was not required, then this function simply + // + if (Mapping == NO_MAPPING) { + return EFI_SUCCESS; + } + + MapInfo = (MAP_INFO *)Mapping; + + // + // 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->DeviceAddress, + MapInfo->NumberOfBytes + ); + } + + DEBUG ((DEBUG_VERBOSE, "%a Host 0x%Lx Device 0x%Lx Pages 0x%Lx Bytes 0x%Lx\n", + __FUNCTION__, MapInfo->DeviceAddress, MapInfo->HostAddress, + MapInfo->NumberOfPages, MapInfo->NumberOfBytes)); + // + // Restore the memory encryption mask + // + Status = MemEncryptSevSetPageEncMask (0, MapInfo->DeviceAddress, MapInfo->NumberOfPages, TRUE); + ASSERT_EFI_ERROR(Status); + + // + // Free the mapped buffer and the MAP_INFO structure. + // + gBS->FreePages (MapInfo->DeviceAddress, 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. + +**/ +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; + } + + PhysicalAddress = (UINTN)-1; + if ((Attributes & EDKII_IOMMU_ATTRIBUTE_DUAL_ADDRESS_CYCLE) == 0) { + // + // Limit allocations to memory below 4GB + // + PhysicalAddress = SIZE_4GB - 1; + } + Status = gBS->AllocatePages ( + AllocateMaxAddress, + MemoryType, + Pages, + &PhysicalAddress + ); + if (!EFI_ERROR (Status)) { + *HostAddress = (VOID *) (UINTN) PhysicalAddress; + + // + // Clear memory encryption mask + // + Status = MemEncryptSevClearPageEncMask (0, PhysicalAddress, Pages, TRUE); + ASSERT_EFI_ERROR(Status); + } + + DEBUG ((DEBUG_VERBOSE, "%a Address 0x%Lx Pages 0x%Lx\n", __FUNCTION__, PhysicalAddress, Pages)); + 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; + + // + // Set memory encryption mask + // + Status = MemEncryptSevSetPageEncMask (0, (EFI_PHYSICAL_ADDRESS)(UINTN)HostAddress, Pages, TRUE); + ASSERT_EFI_ERROR(Status); + + DEBUG ((DEBUG_VERBOSE, "%a Address 0x%Lx Pages 0x%Lx\n", __FUNCTION__, (UINTN)HostAddress, Pages)); + 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] 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. + +**/ +EFI_STATUS +EFIAPI +IoMmuSetAttribute ( + IN EDKII_IOMMU_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN VOID *Mapping, + IN UINT64 IoMmuAccess + ) +{ + return EFI_UNSUPPORTED; +} + +EDKII_IOMMU_PROTOCOL mAmdSev = { + EDKII_IOMMU_PROTOCOL_REVISION, + IoMmuSetAttribute, + IoMmuMap, + IoMmuUnmap, + IoMmuAllocateBuffer, + IoMmuFreeBuffer, +}; + +/** + Initialize Iommu Protocol. + +**/ +VOID +EFIAPI +AmdSevInstallIommuProtocol ( + VOID + ) +{ + EFI_STATUS Status; + EFI_HANDLE Handle; + + Handle = NULL; + Status = gBS->InstallMultipleProtocolInterfaces ( + &Handle, + &gEdkiiIoMmuProtocolGuid, &mAmdSev, + NULL + ); + ASSERT_EFI_ERROR (Status); +} diff --git a/OvmfPkg/AmdSevDxe/AmdSevMmio.c b/OvmfPkg/AmdSevDxe/AmdSevMmio.c new file mode 100644 index 000000000000..b623f82b7baa --- /dev/null +++ b/OvmfPkg/AmdSevDxe/AmdSevMmio.c @@ -0,0 +1,50 @@ +/** @file + + Implements routines to clear C-bit from MMIO Memory Range + + Copyright (c) 2017, AMD Inc. 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 "AmdSevMmio.h" + +/** + + Iterate through the GCD map and clear the C-bit from MMIO and NonExistent + memory space. The NonExistent memory space will be used for mapping the MMIO + space added later (eg PciRootBridge). By clearing both known NonExistent + memory space can gurantee that any MMIO mapped later will have C-bit cleared. +*/ +VOID +EFIAPI +AmdSevClearEncMaskMmioRange ( + VOID + ) +{ + EFI_STATUS Status; + EFI_GCD_MEMORY_SPACE_DESCRIPTOR *AllDescMap; + UINTN NumEntries; + UINTN Index; + + Status = gDS->GetMemorySpaceMap (&NumEntries, &AllDescMap); + if (Status == EFI_SUCCESS) { + for (Index = 0; Index < NumEntries; Index++) { + CONST EFI_GCD_MEMORY_SPACE_DESCRIPTOR *Desc; + + Desc = &AllDescMap[Index]; + if (Desc->GcdMemoryType == EfiGcdMemoryTypeMemoryMappedIo || + Desc->GcdMemoryType == EfiGcdMemoryTypeNonExistent) { + Status = MemEncryptSevClearPageEncMask (0, Desc->BaseAddress, EFI_SIZE_TO_PAGES(Desc->Length), FALSE); + ASSERT_EFI_ERROR(Status); + } + } + } +} -- 2.7.4