]> git.proxmox.com Git - mirror_edk2.git/blame_incremental - OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c
OvmfPkg/QemuFwCfgLib: Support Tdx in QemuFwCfgDxe
[mirror_edk2.git] / OvmfPkg / Library / QemuFwCfgLib / QemuFwCfgDxe.c
... / ...
CommitLineData
1/** @file\r
2\r
3 Stateful and implicitly initialized fw_cfg library implementation.\r
4\r
5 Copyright (C) 2013, Red Hat, Inc.\r
6 Copyright (c) 2011 - 2013, Intel Corporation. All rights reserved.<BR>\r
7 Copyright (c) 2017, Advanced Micro Devices. All rights reserved.<BR>\r
8\r
9 SPDX-License-Identifier: BSD-2-Clause-Patent\r
10**/\r
11\r
12#include <Uefi.h>\r
13\r
14#include <Protocol/IoMmu.h>\r
15\r
16#include <Library/BaseLib.h>\r
17#include <Library/BaseMemoryLib.h>\r
18#include <Library/IoLib.h>\r
19#include <Library/DebugLib.h>\r
20#include <Library/QemuFwCfgLib.h>\r
21#include <Library/UefiBootServicesTableLib.h>\r
22#include <Library/MemEncryptTdxLib.h>\r
23#include <Library/MemEncryptSevLib.h>\r
24\r
25#include "QemuFwCfgLibInternal.h"\r
26\r
27STATIC BOOLEAN mQemuFwCfgSupported = FALSE;\r
28STATIC BOOLEAN mQemuFwCfgDmaSupported;\r
29\r
30STATIC EDKII_IOMMU_PROTOCOL *mIoMmuProtocol;\r
31\r
32/**\r
33 Returns a boolean indicating if the firmware configuration interface\r
34 is available or not.\r
35\r
36 This function may change fw_cfg state.\r
37\r
38 @retval TRUE The interface is available\r
39 @retval FALSE The interface is not available\r
40\r
41**/\r
42BOOLEAN\r
43EFIAPI\r
44QemuFwCfgIsAvailable (\r
45 VOID\r
46 )\r
47{\r
48 return InternalQemuFwCfgIsAvailable ();\r
49}\r
50\r
51RETURN_STATUS\r
52EFIAPI\r
53QemuFwCfgInitialize (\r
54 VOID\r
55 )\r
56{\r
57 UINT32 Signature;\r
58 UINT32 Revision;\r
59\r
60 //\r
61 // Enable the access routines while probing to see if it is supported.\r
62 // For probing we always use the IO Port (IoReadFifo8()) access method.\r
63 //\r
64 mQemuFwCfgSupported = TRUE;\r
65 mQemuFwCfgDmaSupported = FALSE;\r
66\r
67 QemuFwCfgSelectItem (QemuFwCfgItemSignature);\r
68 Signature = QemuFwCfgRead32 ();\r
69 DEBUG ((DEBUG_INFO, "FW CFG Signature: 0x%x\n", Signature));\r
70 QemuFwCfgSelectItem (QemuFwCfgItemInterfaceVersion);\r
71 Revision = QemuFwCfgRead32 ();\r
72 DEBUG ((DEBUG_INFO, "FW CFG Revision: 0x%x\n", Revision));\r
73 if ((Signature != SIGNATURE_32 ('Q', 'E', 'M', 'U')) ||\r
74 (Revision < 1)\r
75 )\r
76 {\r
77 DEBUG ((DEBUG_INFO, "QemuFwCfg interface not supported.\n"));\r
78 mQemuFwCfgSupported = FALSE;\r
79 return RETURN_SUCCESS;\r
80 }\r
81\r
82 if ((Revision & FW_CFG_F_DMA) == 0) {\r
83 DEBUG ((DEBUG_INFO, "QemuFwCfg interface (IO Port) is supported.\n"));\r
84 } else {\r
85 mQemuFwCfgDmaSupported = TRUE;\r
86 DEBUG ((DEBUG_INFO, "QemuFwCfg interface (DMA) is supported.\n"));\r
87 }\r
88\r
89 if (mQemuFwCfgDmaSupported && (MemEncryptSevIsEnabled () || (MemEncryptTdxIsEnabled ()))) {\r
90 EFI_STATUS Status;\r
91\r
92 //\r
93 // IoMmuDxe driver must have installed the IOMMU protocol. If we are not\r
94 // able to locate the protocol then something must have gone wrong.\r
95 //\r
96 Status = gBS->LocateProtocol (\r
97 &gEdkiiIoMmuProtocolGuid,\r
98 NULL,\r
99 (VOID **)&mIoMmuProtocol\r
100 );\r
101 if (EFI_ERROR (Status)) {\r
102 DEBUG ((\r
103 DEBUG_ERROR,\r
104 "QemuFwCfgDma %a:%a Failed to locate IOMMU protocol.\n",\r
105 gEfiCallerBaseName,\r
106 __FUNCTION__\r
107 ));\r
108 ASSERT (FALSE);\r
109 CpuDeadLoop ();\r
110 }\r
111 }\r
112\r
113 return RETURN_SUCCESS;\r
114}\r
115\r
116/**\r
117 Returns a boolean indicating if the firmware configuration interface is\r
118 available for library-internal purposes.\r
119\r
120 This function never changes fw_cfg state.\r
121\r
122 @retval TRUE The interface is available internally.\r
123 @retval FALSE The interface is not available internally.\r
124**/\r
125BOOLEAN\r
126InternalQemuFwCfgIsAvailable (\r
127 VOID\r
128 )\r
129{\r
130 return mQemuFwCfgSupported;\r
131}\r
132\r
133/**\r
134 Returns a boolean indicating whether QEMU provides the DMA-like access method\r
135 for fw_cfg.\r
136\r
137 @retval TRUE The DMA-like access method is available.\r
138 @retval FALSE The DMA-like access method is unavailable.\r
139**/\r
140BOOLEAN\r
141InternalQemuFwCfgDmaIsAvailable (\r
142 VOID\r
143 )\r
144{\r
145 return mQemuFwCfgDmaSupported;\r
146}\r
147\r
148/**\r
149 Function is used for allocating a bi-directional FW_CFG_DMA_ACCESS used\r
150 between Host and device to exchange the information. The buffer must be free'd\r
151 using FreeFwCfgDmaAccessBuffer ().\r
152\r
153**/\r
154STATIC\r
155VOID\r
156AllocFwCfgDmaAccessBuffer (\r
157 OUT VOID **Access,\r
158 OUT VOID **MapInfo\r
159 )\r
160{\r
161 UINTN Size;\r
162 UINTN NumPages;\r
163 EFI_STATUS Status;\r
164 VOID *HostAddress;\r
165 EFI_PHYSICAL_ADDRESS DmaAddress;\r
166 VOID *Mapping;\r
167\r
168 Size = sizeof (FW_CFG_DMA_ACCESS);\r
169 NumPages = EFI_SIZE_TO_PAGES (Size);\r
170\r
171 //\r
172 // As per UEFI spec, in order to map a host address with\r
173 // BusMasterCommonBuffer64, the buffer must be allocated using the IOMMU\r
174 // AllocateBuffer()\r
175 //\r
176 Status = mIoMmuProtocol->AllocateBuffer (\r
177 mIoMmuProtocol,\r
178 AllocateAnyPages,\r
179 EfiBootServicesData,\r
180 NumPages,\r
181 &HostAddress,\r
182 EDKII_IOMMU_ATTRIBUTE_DUAL_ADDRESS_CYCLE\r
183 );\r
184 if (EFI_ERROR (Status)) {\r
185 DEBUG ((\r
186 DEBUG_ERROR,\r
187 "%a:%a failed to allocate FW_CFG_DMA_ACCESS\n",\r
188 gEfiCallerBaseName,\r
189 __FUNCTION__\r
190 ));\r
191 ASSERT (FALSE);\r
192 CpuDeadLoop ();\r
193 }\r
194\r
195 //\r
196 // Avoid exposing stale data even temporarily: zero the area before mapping\r
197 // it.\r
198 //\r
199 ZeroMem (HostAddress, Size);\r
200\r
201 //\r
202 // Map the host buffer with BusMasterCommonBuffer64\r
203 //\r
204 Status = mIoMmuProtocol->Map (\r
205 mIoMmuProtocol,\r
206 EdkiiIoMmuOperationBusMasterCommonBuffer64,\r
207 HostAddress,\r
208 &Size,\r
209 &DmaAddress,\r
210 &Mapping\r
211 );\r
212 if (EFI_ERROR (Status)) {\r
213 mIoMmuProtocol->FreeBuffer (mIoMmuProtocol, NumPages, HostAddress);\r
214 DEBUG ((\r
215 DEBUG_ERROR,\r
216 "%a:%a failed to Map() FW_CFG_DMA_ACCESS\n",\r
217 gEfiCallerBaseName,\r
218 __FUNCTION__\r
219 ));\r
220 ASSERT (FALSE);\r
221 CpuDeadLoop ();\r
222 }\r
223\r
224 if (Size < sizeof (FW_CFG_DMA_ACCESS)) {\r
225 mIoMmuProtocol->Unmap (mIoMmuProtocol, Mapping);\r
226 mIoMmuProtocol->FreeBuffer (mIoMmuProtocol, NumPages, HostAddress);\r
227 DEBUG ((\r
228 DEBUG_ERROR,\r
229 "%a:%a failed to Map() - requested 0x%Lx got 0x%Lx\n",\r
230 gEfiCallerBaseName,\r
231 __FUNCTION__,\r
232 (UINT64)sizeof (FW_CFG_DMA_ACCESS),\r
233 (UINT64)Size\r
234 ));\r
235 ASSERT (FALSE);\r
236 CpuDeadLoop ();\r
237 }\r
238\r
239 *Access = HostAddress;\r
240 *MapInfo = Mapping;\r
241}\r
242\r
243/**\r
244 Function is to used for freeing the Access buffer allocated using\r
245 AllocFwCfgDmaAccessBuffer()\r
246\r
247**/\r
248STATIC\r
249VOID\r
250FreeFwCfgDmaAccessBuffer (\r
251 IN VOID *Access,\r
252 IN VOID *Mapping\r
253 )\r
254{\r
255 UINTN NumPages;\r
256 EFI_STATUS Status;\r
257\r
258 NumPages = EFI_SIZE_TO_PAGES (sizeof (FW_CFG_DMA_ACCESS));\r
259\r
260 Status = mIoMmuProtocol->Unmap (mIoMmuProtocol, Mapping);\r
261 if (EFI_ERROR (Status)) {\r
262 DEBUG ((\r
263 DEBUG_ERROR,\r
264 "%a:%a failed to UnMap() Mapping 0x%Lx\n",\r
265 gEfiCallerBaseName,\r
266 __FUNCTION__,\r
267 (UINT64)(UINTN)Mapping\r
268 ));\r
269 ASSERT (FALSE);\r
270 CpuDeadLoop ();\r
271 }\r
272\r
273 Status = mIoMmuProtocol->FreeBuffer (mIoMmuProtocol, NumPages, Access);\r
274 if (EFI_ERROR (Status)) {\r
275 DEBUG ((\r
276 DEBUG_ERROR,\r
277 "%a:%a failed to Free() 0x%Lx\n",\r
278 gEfiCallerBaseName,\r
279 __FUNCTION__,\r
280 (UINT64)(UINTN)Access\r
281 ));\r
282 ASSERT (FALSE);\r
283 CpuDeadLoop ();\r
284 }\r
285}\r
286\r
287/**\r
288 Function is used for mapping host address to device address. The buffer must\r
289 be unmapped with UnmapDmaDataBuffer ().\r
290\r
291**/\r
292STATIC\r
293VOID\r
294MapFwCfgDmaDataBuffer (\r
295 IN BOOLEAN IsWrite,\r
296 IN VOID *HostAddress,\r
297 IN UINT32 Size,\r
298 OUT EFI_PHYSICAL_ADDRESS *DeviceAddress,\r
299 OUT VOID **MapInfo\r
300 )\r
301{\r
302 EFI_STATUS Status;\r
303 UINTN NumberOfBytes;\r
304 VOID *Mapping;\r
305 EFI_PHYSICAL_ADDRESS PhysicalAddress;\r
306\r
307 NumberOfBytes = Size;\r
308 Status = mIoMmuProtocol->Map (\r
309 mIoMmuProtocol,\r
310 (IsWrite ?\r
311 EdkiiIoMmuOperationBusMasterRead64 :\r
312 EdkiiIoMmuOperationBusMasterWrite64),\r
313 HostAddress,\r
314 &NumberOfBytes,\r
315 &PhysicalAddress,\r
316 &Mapping\r
317 );\r
318 if (EFI_ERROR (Status)) {\r
319 DEBUG ((\r
320 DEBUG_ERROR,\r
321 "%a:%a failed to Map() Address 0x%Lx Size 0x%Lx\n",\r
322 gEfiCallerBaseName,\r
323 __FUNCTION__,\r
324 (UINT64)(UINTN)HostAddress,\r
325 (UINT64)Size\r
326 ));\r
327 ASSERT (FALSE);\r
328 CpuDeadLoop ();\r
329 }\r
330\r
331 if (NumberOfBytes < Size) {\r
332 mIoMmuProtocol->Unmap (mIoMmuProtocol, Mapping);\r
333 DEBUG ((\r
334 DEBUG_ERROR,\r
335 "%a:%a failed to Map() - requested 0x%x got 0x%Lx\n",\r
336 gEfiCallerBaseName,\r
337 __FUNCTION__,\r
338 Size,\r
339 (UINT64)NumberOfBytes\r
340 ));\r
341 ASSERT (FALSE);\r
342 CpuDeadLoop ();\r
343 }\r
344\r
345 *DeviceAddress = PhysicalAddress;\r
346 *MapInfo = Mapping;\r
347}\r
348\r
349STATIC\r
350VOID\r
351UnmapFwCfgDmaDataBuffer (\r
352 IN VOID *Mapping\r
353 )\r
354{\r
355 EFI_STATUS Status;\r
356\r
357 Status = mIoMmuProtocol->Unmap (mIoMmuProtocol, Mapping);\r
358 if (EFI_ERROR (Status)) {\r
359 DEBUG ((\r
360 DEBUG_ERROR,\r
361 "%a:%a failed to UnMap() Mapping 0x%Lx\n",\r
362 gEfiCallerBaseName,\r
363 __FUNCTION__,\r
364 (UINT64)(UINTN)Mapping\r
365 ));\r
366 ASSERT (FALSE);\r
367 CpuDeadLoop ();\r
368 }\r
369}\r
370\r
371/**\r
372 Transfer an array of bytes, or skip a number of bytes, using the DMA\r
373 interface.\r
374\r
375 @param[in] Size Size in bytes to transfer or skip.\r
376\r
377 @param[in,out] Buffer Buffer to read data into or write data from. Ignored,\r
378 and may be NULL, if Size is zero, or Control is\r
379 FW_CFG_DMA_CTL_SKIP.\r
380\r
381 @param[in] Control One of the following:\r
382 FW_CFG_DMA_CTL_WRITE - write to fw_cfg from Buffer.\r
383 FW_CFG_DMA_CTL_READ - read from fw_cfg into Buffer.\r
384 FW_CFG_DMA_CTL_SKIP - skip bytes in fw_cfg.\r
385**/\r
386VOID\r
387InternalQemuFwCfgDmaBytes (\r
388 IN UINT32 Size,\r
389 IN OUT VOID *Buffer OPTIONAL,\r
390 IN UINT32 Control\r
391 )\r
392{\r
393 volatile FW_CFG_DMA_ACCESS LocalAccess;\r
394 volatile FW_CFG_DMA_ACCESS *Access;\r
395 UINT32 AccessHigh, AccessLow;\r
396 UINT32 Status;\r
397 VOID *AccessMapping, *DataMapping;\r
398 VOID *DataBuffer;\r
399\r
400 ASSERT (\r
401 Control == FW_CFG_DMA_CTL_WRITE || Control == FW_CFG_DMA_CTL_READ ||\r
402 Control == FW_CFG_DMA_CTL_SKIP\r
403 );\r
404\r
405 if (Size == 0) {\r
406 return;\r
407 }\r
408\r
409 Access = &LocalAccess;\r
410 AccessMapping = NULL;\r
411 DataMapping = NULL;\r
412 DataBuffer = Buffer;\r
413\r
414 //\r
415 // When SEV or TDX is enabled, map Buffer to DMA address before issuing the DMA\r
416 // request\r
417 //\r
418 if (MemEncryptSevIsEnabled () || MemEncryptTdxIsEnabled ()) {\r
419 VOID *AccessBuffer;\r
420 EFI_PHYSICAL_ADDRESS DataBufferAddress;\r
421\r
422 //\r
423 // Allocate DMA Access buffer\r
424 //\r
425 AllocFwCfgDmaAccessBuffer (&AccessBuffer, &AccessMapping);\r
426\r
427 Access = AccessBuffer;\r
428\r
429 //\r
430 // Map actual data buffer\r
431 //\r
432 if (Control != FW_CFG_DMA_CTL_SKIP) {\r
433 MapFwCfgDmaDataBuffer (\r
434 Control == FW_CFG_DMA_CTL_WRITE,\r
435 Buffer,\r
436 Size,\r
437 &DataBufferAddress,\r
438 &DataMapping\r
439 );\r
440\r
441 DataBuffer = (VOID *)(UINTN)DataBufferAddress;\r
442 }\r
443 }\r
444\r
445 Access->Control = SwapBytes32 (Control);\r
446 Access->Length = SwapBytes32 (Size);\r
447 Access->Address = SwapBytes64 ((UINTN)DataBuffer);\r
448\r
449 //\r
450 // Delimit the transfer from (a) modifications to Access, (b) in case of a\r
451 // write, from writes to Buffer by the caller.\r
452 //\r
453 MemoryFence ();\r
454\r
455 //\r
456 // Start the transfer.\r
457 //\r
458 AccessHigh = (UINT32)RShiftU64 ((UINTN)Access, 32);\r
459 AccessLow = (UINT32)(UINTN)Access;\r
460 IoWrite32 (FW_CFG_IO_DMA_ADDRESS, SwapBytes32 (AccessHigh));\r
461 IoWrite32 (FW_CFG_IO_DMA_ADDRESS + 4, SwapBytes32 (AccessLow));\r
462\r
463 //\r
464 // Don't look at Access.Control before starting the transfer.\r
465 //\r
466 MemoryFence ();\r
467\r
468 //\r
469 // Wait for the transfer to complete.\r
470 //\r
471 do {\r
472 Status = SwapBytes32 (Access->Control);\r
473 ASSERT ((Status & FW_CFG_DMA_CTL_ERROR) == 0);\r
474 } while (Status != 0);\r
475\r
476 //\r
477 // After a read, the caller will want to use Buffer.\r
478 //\r
479 MemoryFence ();\r
480\r
481 //\r
482 // If Access buffer was dynamically allocated then free it.\r
483 //\r
484 if (AccessMapping != NULL) {\r
485 FreeFwCfgDmaAccessBuffer ((VOID *)Access, AccessMapping);\r
486 }\r
487\r
488 //\r
489 // If DataBuffer was mapped then unmap it.\r
490 //\r
491 if (DataMapping != NULL) {\r
492 UnmapFwCfgDmaDataBuffer (DataMapping);\r
493 }\r
494}\r