* [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
@ 2017-07-19 22:09 Brijesh Singh
2017-07-19 22:09 ` [RFC v1 1/3] OvmfPkg/Include/Virtio10: Define VIRTIO_F_IOMMU_PLATFORM feature bit Brijesh Singh
` (4 more replies)
0 siblings, 5 replies; 28+ messages in thread
From: Brijesh Singh @ 2017-07-19 22:09 UTC (permalink / raw)
To: edk2-devel
Cc: Tom Lendacky, Brijesh Singh, Jordan Justen, Laszlo Ersek,
Jason Wang, Michael S . Tsirkin
I have found that OVMF fails to detect the disk when iommu_platform is set from
qemu cli. The failure occurs during the feature bit negotiation.
Recently, EDKII introduced IOMMU protocol d1fddc4533bf. SEV patch series introduced
a IoMmu protocol driver f9d129e68a45 to set a DMA access attribute and methods to
allocate, free, map and unmap the DMA memory for the master bus devices
In this patch series, I have tried to enable the IOMMU_PLATFORM feature for
VirtioBlkDevice. I am sending this as RFC to seek feedback before I extend the support
for other Virtio devices. The patch has been tested in SEV guest - mainly because
IoMmuDxe driver installs the IOMMU protocol for SEV guest only. If needed, I can
extend the IoMmuDxe driver to install IOMMU protocol for non SEV guests.
qemu cli used for testing:
# $QEMU \
...
-drive file=${HDA_FILE},if=none,id=disk0,format=qcow2 \
-device virtio-blk-pci,drive=disk0,disable-legacy=on,iommu_platform=true,disable-modern=off,scsi=off
...
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Laszlo Ersek <lersek@redhat.com>
Cc: Jason Wang <jasowang@redhat.com>
Cc: Michael S. Tsirkin <mst@redhat.com>
Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
Brijesh Singh (3):
OvmfPkg/Include/Virtio10: Define VIRTIO_F_IOMMU_PLATFORM feature bit
OvmfPkg/VirtioLib: Add IOMMU_PLATFORM support
OvmfPkg/VirtioBlkDxe: Add VIRITO_F_IOMMU_PLATFORM support
OvmfPkg/Library/VirtioLib/VirtioLib.inf | 1 +
OvmfPkg/VirtioBlkDxe/VirtioBlk.inf | 5 +
OvmfPkg/VirtioGpuDxe/VirtioGpu.inf | 1 +
OvmfPkg/VirtioNetDxe/VirtioNet.inf | 1 +
OvmfPkg/VirtioRngDxe/VirtioRng.inf | 1 +
OvmfPkg/VirtioScsiDxe/VirtioScsi.inf | 1 +
OvmfPkg/Include/IndustryStandard/Virtio095.h | 1 +
OvmfPkg/Include/IndustryStandard/Virtio10.h | 5 +
OvmfPkg/Include/Library/VirtioLib.h | 20 ++++
OvmfPkg/Library/VirtioLib/VirtioLib.c | 96 ++++++++++++++-
OvmfPkg/VirtioBlkDxe/VirtioBlk.c | 125 ++++++++++++++++++--
11 files changed, 244 insertions(+), 13 deletions(-)
--
Brijesh Singh
2.7.4
^ permalink raw reply [flat|nested] 28+ messages in thread
* [RFC v1 1/3] OvmfPkg/Include/Virtio10: Define VIRTIO_F_IOMMU_PLATFORM feature bit
2017-07-19 22:09 [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support Brijesh Singh
@ 2017-07-19 22:09 ` Brijesh Singh
2017-07-19 22:09 ` [RFC v1 2/3] OvmfPkg/VirtioLib: Add IOMMU_PLATFORM support Brijesh Singh
` (3 subsequent siblings)
4 siblings, 0 replies; 28+ messages in thread
From: Brijesh Singh @ 2017-07-19 22:09 UTC (permalink / raw)
To: edk2-devel
Cc: Tom Lendacky, Brijesh Singh, Jordan Justen, Laszlo Ersek,
Jason Wang, Michael S . Tsirkin
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Laszlo Ersek <lersek@redhat.com>
Cc: Jason Wang <jasowang@redhat.com>
Cc: Michael S. Tsirkin <mst@redhat.com>
Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
---
OvmfPkg/Include/IndustryStandard/Virtio10.h | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/OvmfPkg/Include/IndustryStandard/Virtio10.h b/OvmfPkg/Include/IndustryStandard/Virtio10.h
index 4c9b62a3cf59..5948b9ef1d9f 100644
--- a/OvmfPkg/Include/IndustryStandard/Virtio10.h
+++ b/OvmfPkg/Include/IndustryStandard/Virtio10.h
@@ -83,4 +83,9 @@ typedef struct {
//
#define VIRTIO_F_VERSION_1 BIT32
+//
+// VirtIo 1.0 IOMMU feature bits
+//
+#define VIRTIO_F_IOMMU_PLATFORM BIT33
+
#endif // _VIRTIO_1_0_H_
--
2.7.4
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC v1 2/3] OvmfPkg/VirtioLib: Add IOMMU_PLATFORM support
2017-07-19 22:09 [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support Brijesh Singh
2017-07-19 22:09 ` [RFC v1 1/3] OvmfPkg/Include/Virtio10: Define VIRTIO_F_IOMMU_PLATFORM feature bit Brijesh Singh
@ 2017-07-19 22:09 ` Brijesh Singh
2017-07-19 22:09 ` [RFC v1 3/3] OvmfPkg/VirtioBlkDxe: Add VIRITO_F_IOMMU_PLATFORM support Brijesh Singh
` (2 subsequent siblings)
4 siblings, 0 replies; 28+ messages in thread
From: Brijesh Singh @ 2017-07-19 22:09 UTC (permalink / raw)
To: edk2-devel
Cc: Tom Lendacky, Brijesh Singh, Jordan Justen, Laszlo Ersek,
Jason Wang, Michael S . Tsirkin
When iommu_platform is set, use IOMMU protocols buffer allocation
and free routines to allocate and free vring buffer (guest-host
communication buffer).
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Laszlo Ersek <lersek@redhat.com>
Cc: Jason Wang <jasowang@redhat.com>
Cc: Michael S. Tsirkin <mst@redhat.com>
Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
---
OvmfPkg/Library/VirtioLib/VirtioLib.inf | 1 +
OvmfPkg/VirtioBlkDxe/VirtioBlk.inf | 1 +
OvmfPkg/VirtioGpuDxe/VirtioGpu.inf | 1 +
OvmfPkg/VirtioNetDxe/VirtioNet.inf | 1 +
OvmfPkg/VirtioRngDxe/VirtioRng.inf | 1 +
OvmfPkg/VirtioScsiDxe/VirtioScsi.inf | 1 +
OvmfPkg/Include/IndustryStandard/Virtio095.h | 1 +
OvmfPkg/Include/Library/VirtioLib.h | 20 ++++
OvmfPkg/Library/VirtioLib/VirtioLib.c | 96 +++++++++++++++++++-
9 files changed, 121 insertions(+), 2 deletions(-)
diff --git a/OvmfPkg/Library/VirtioLib/VirtioLib.inf b/OvmfPkg/Library/VirtioLib/VirtioLib.inf
index fb5897a88ecf..6629d0d52b04 100644
--- a/OvmfPkg/Library/VirtioLib/VirtioLib.inf
+++ b/OvmfPkg/Library/VirtioLib/VirtioLib.inf
@@ -26,6 +26,7 @@ [Sources]
[Packages]
MdePkg/MdePkg.dec
+ MdeModulePkg/MdeModulePkg.dec
OvmfPkg/OvmfPkg.dec
[LibraryClasses]
diff --git a/OvmfPkg/VirtioBlkDxe/VirtioBlk.inf b/OvmfPkg/VirtioBlkDxe/VirtioBlk.inf
index d5975b74eb05..53d5a164a0b8 100644
--- a/OvmfPkg/VirtioBlkDxe/VirtioBlk.inf
+++ b/OvmfPkg/VirtioBlkDxe/VirtioBlk.inf
@@ -26,6 +26,7 @@ [Sources]
[Packages]
MdePkg/MdePkg.dec
+ MdeModulePkg/MdeModulePkg.dec
OvmfPkg/OvmfPkg.dec
[LibraryClasses]
diff --git a/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf b/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf
index 04bc2964c223..a6d4cbbd191a 100644
--- a/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf
+++ b/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf
@@ -31,6 +31,7 @@ [Sources]
[Packages]
MdePkg/MdePkg.dec
+ MdeModulePkg/MdeModulePkg.dec
OvmfPkg/OvmfPkg.dec
[LibraryClasses]
diff --git a/OvmfPkg/VirtioNetDxe/VirtioNet.inf b/OvmfPkg/VirtioNetDxe/VirtioNet.inf
index a855ad4ac154..2b8996ed366e 100644
--- a/OvmfPkg/VirtioNetDxe/VirtioNet.inf
+++ b/OvmfPkg/VirtioNetDxe/VirtioNet.inf
@@ -42,6 +42,7 @@ [Sources]
[Packages]
MdePkg/MdePkg.dec
+ MdeModulePkg/MdeModulePkg.dec
OvmfPkg/OvmfPkg.dec
[LibraryClasses]
diff --git a/OvmfPkg/VirtioRngDxe/VirtioRng.inf b/OvmfPkg/VirtioRngDxe/VirtioRng.inf
index 471beb37bc7f..898bfb7158c2 100644
--- a/OvmfPkg/VirtioRngDxe/VirtioRng.inf
+++ b/OvmfPkg/VirtioRngDxe/VirtioRng.inf
@@ -26,6 +26,7 @@ [Sources]
[Packages]
MdePkg/MdePkg.dec
+ MdeModulePkg/MdeModulePkg.dec
OvmfPkg/OvmfPkg.dec
[LibraryClasses]
diff --git a/OvmfPkg/VirtioScsiDxe/VirtioScsi.inf b/OvmfPkg/VirtioScsiDxe/VirtioScsi.inf
index 75581123930b..082f9a12bb09 100644
--- a/OvmfPkg/VirtioScsiDxe/VirtioScsi.inf
+++ b/OvmfPkg/VirtioScsiDxe/VirtioScsi.inf
@@ -27,6 +27,7 @@ [Sources]
[Packages]
MdePkg/MdePkg.dec
+ MdeModulePkg/MdeModulePkg.dec
OvmfPkg/OvmfPkg.dec
[LibraryClasses]
diff --git a/OvmfPkg/Include/IndustryStandard/Virtio095.h b/OvmfPkg/Include/IndustryStandard/Virtio095.h
index 6bf77cb32075..c81b29c6d9d0 100644
--- a/OvmfPkg/Include/IndustryStandard/Virtio095.h
+++ b/OvmfPkg/Include/IndustryStandard/Virtio095.h
@@ -154,6 +154,7 @@ typedef struct {
VRING_AVAIL Avail;
VRING_USED Used;
UINT16 QueueSize;
+ VOID *Iommu;
} VRING;
//
diff --git a/OvmfPkg/Include/Library/VirtioLib.h b/OvmfPkg/Include/Library/VirtioLib.h
index 5badfb32917f..92a3d5e10f7b 100644
--- a/OvmfPkg/Include/Library/VirtioLib.h
+++ b/OvmfPkg/Include/Library/VirtioLib.h
@@ -17,10 +17,30 @@
#ifndef _VIRTIO_LIB_H_
#define _VIRTIO_LIB_H_
+#include <Protocol/IoMmu.h>
#include <Protocol/VirtioDevice.h>
#include <IndustryStandard/Virtio.h>
+/**
+
+ Configure a virtio ring to use IOMMU protocol
+
+ This function tells vring to use the given IOMMU protocol interface
+ for allocating and freeing the vring buffer (this guest-host communication
+ area)
+
+ @param[in] The virtio ring to set up.
+
+ @param[in] IOMMU protocol to use.
+
+**/
+VOID
+EFIAPI
+VirtioRingUseIommu (
+ IN VRING *Ring,
+ IN EDKII_IOMMU_PROTOCOL *Iommu
+ );
/**
diff --git a/OvmfPkg/Library/VirtioLib/VirtioLib.c b/OvmfPkg/Library/VirtioLib/VirtioLib.c
index 845f206369a3..d8fdb6310d52 100644
--- a/OvmfPkg/Library/VirtioLib/VirtioLib.c
+++ b/OvmfPkg/Library/VirtioLib/VirtioLib.c
@@ -26,6 +26,69 @@
/**
+ Configure a virtio ring to use IOMMU protocol
+
+ This function tells vring to use the given IOMMU protocol interface
+ for allocating and freeing the vring buffer (this guest-host communication
+ area)
+
+ @param[in] The virtio ring to set up.
+
+ @param[in] IOMMU protocol to use.
+
+**/
+VOID
+EFIAPI
+VirtioRingUseIommu (
+ IN VRING *Ring,
+ IN EDKII_IOMMU_PROTOCOL *Iommu
+ )
+{
+ Ring->Iommu = Iommu;
+}
+
+/**
+
+ Allocate vring (the guest-host communication area)
+
+ This function checks if host has requested the Iommu support then it uses
+ the IOMMU protocol allocator for allocating vring otherwise uses AllocatePages
+
+**/
+STATIC
+VOID *
+VirtioRingAllocatePages (
+ IN VRING *Ring
+ )
+{
+ UINT32 NumPages;
+ EDKII_IOMMU_PROTOCOL *Iommu;
+
+ NumPages = Ring->NumPages;
+ Iommu = (EDKII_IOMMU_PROTOCOL *)Ring->Iommu;
+
+ //
+ // If IOMMU protocol is set then use IOMMU allocator otherwise
+ // fallback to standard allocator
+ //
+ if (Iommu) {
+ EFI_STATUS Status;
+ VOID *Buffer;
+
+ Status = Iommu->AllocateBuffer (Iommu, 0, EfiBootServicesData,
+ NumPages, &Buffer, EDKII_IOMMU_ATTRIBUTE_MEMORY_CACHED);
+ if (EFI_ERROR (Status)) {
+ return NULL;
+ }
+
+ return Buffer;
+ }
+
+ return AllocatePages (NumPages);
+}
+
+/**
+
Configure a virtio ring.
This function sets up internal storage (the guest-host communication area)
@@ -76,7 +139,7 @@ VirtioRingInit (
EFI_PAGE_SIZE);
Ring->NumPages = EFI_SIZE_TO_PAGES (RingSize);
- Ring->Base = AllocatePages (Ring->NumPages);
+ Ring->Base = VirtioRingAllocatePages (Ring);
if (Ring->Base == NULL) {
return EFI_OUT_OF_RESOURCES;
}
@@ -118,6 +181,35 @@ VirtioRingInit (
return EFI_SUCCESS;
}
+/**
+
+ Free vring (the guest-host communication area)
+
+ This function checks if host has requested the Iommu support then it uses
+ the IOMMU protocol free callback otherwise uses FreePages
+
+**/
+STATIC
+VOID
+VirtioRingFreePages (
+ IN VRING *Ring
+ )
+{
+ EDKII_IOMMU_PROTOCOL *Iommu;
+
+ Iommu = (EDKII_IOMMU_PROTOCOL *)Ring->Iommu;
+
+ //
+ // If IOMMU protocol is set then use IOMMU FreeBuffer otherwise
+ // fallback to FreePages
+ //
+ if (Iommu) {
+ Iommu->FreeBuffer (Iommu, Ring->NumPages, Ring->Base);
+ } else {
+ FreePages (Ring->Base, Ring->NumPages);
+ }
+}
+
/**
@@ -136,7 +228,7 @@ VirtioRingUninit (
IN OUT VRING *Ring
)
{
- FreePages (Ring->Base, Ring->NumPages);
+ VirtioRingFreePages (Ring);
SetMem (Ring, sizeof *Ring, 0x00);
}
--
2.7.4
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC v1 3/3] OvmfPkg/VirtioBlkDxe: Add VIRITO_F_IOMMU_PLATFORM support
2017-07-19 22:09 [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support Brijesh Singh
2017-07-19 22:09 ` [RFC v1 1/3] OvmfPkg/Include/Virtio10: Define VIRTIO_F_IOMMU_PLATFORM feature bit Brijesh Singh
2017-07-19 22:09 ` [RFC v1 2/3] OvmfPkg/VirtioLib: Add IOMMU_PLATFORM support Brijesh Singh
@ 2017-07-19 22:09 ` Brijesh Singh
[not found] ` <62320c1a-0cec-947c-8c63-5eb0416e4e33@redhat.com>
2017-07-25 18:17 ` Laszlo Ersek
4 siblings, 0 replies; 28+ messages in thread
From: Brijesh Singh @ 2017-07-19 22:09 UTC (permalink / raw)
To: edk2-devel
Cc: Tom Lendacky, Brijesh Singh, Jordan Justen, Laszlo Ersek,
Jason Wang, Michael S . Tsirkin
When VIRTIO_F_IOMMU_PLATFORM feature bit in set then try locating
IOMMU protocol. If IOMMU protocol is available, then use the IOMMU
protocols functions to allocate, free, map and unmap the host buffer
to device buffer.
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Laszlo Ersek <lersek@redhat.com>
Cc: Jason Wang <jasowang@redhat.com>
Cc: Michael S. Tsirkin <mst@redhat.com>
Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
---
OvmfPkg/VirtioBlkDxe/VirtioBlk.inf | 4 +
OvmfPkg/VirtioBlkDxe/VirtioBlk.c | 125 ++++++++++++++++++--
2 files changed, 118 insertions(+), 11 deletions(-)
diff --git a/OvmfPkg/VirtioBlkDxe/VirtioBlk.inf b/OvmfPkg/VirtioBlkDxe/VirtioBlk.inf
index 53d5a164a0b8..12577c5f700f 100644
--- a/OvmfPkg/VirtioBlkDxe/VirtioBlk.inf
+++ b/OvmfPkg/VirtioBlkDxe/VirtioBlk.inf
@@ -41,3 +41,7 @@ [LibraryClasses]
[Protocols]
gEfiBlockIoProtocolGuid ## BY_START
gVirtioDeviceProtocolGuid ## TO_START
+ gEdkiiIoMmuProtocolGuid ## SOMETIMES_CONSUMES
+
+[Depex]
+ gEdkiiIoMmuProtocolGuid OR gIoMmuAbsentProtocolGuid
diff --git a/OvmfPkg/VirtioBlkDxe/VirtioBlk.c b/OvmfPkg/VirtioBlkDxe/VirtioBlk.c
index 3ce72281c204..f78052c4ecc0 100644
--- a/OvmfPkg/VirtioBlkDxe/VirtioBlk.c
+++ b/OvmfPkg/VirtioBlkDxe/VirtioBlk.c
@@ -23,6 +23,8 @@
**/
+#include <Protocol/IoMmu.h>
+
#include <IndustryStandard/VirtioBlk.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
@@ -33,6 +35,8 @@
#include "VirtioBlk.h"
+STATIC EDKII_IOMMU_PROTOCOL *mIoMmuProtocol;
+
/**
Convenience macros to read and write region 0 IO space elements of the
@@ -247,13 +251,49 @@ SynchronousRequest (
)
{
UINT32 BlockSize;
- volatile VIRTIO_BLK_REQ Request;
- volatile UINT8 HostStatus;
+ volatile VIRTIO_BLK_REQ LocalRequest;
+ volatile UINT8 LocalHostStatus;
+ volatile VIRTIO_BLK_REQ *Request;
+ volatile UINT8 *HostStatus;
DESC_INDICES Indices;
+ VOID *IommuBuffer;
+ UINT32 NumPages;
+ VOID *Mapping;
+ EFI_STATUS Status;
+ EFI_PHYSICAL_ADDRESS DeviceBuffer;
BlockSize = Dev->BlockIoMedia.BlockSize;
//
+ // set NumPages to suppress incorrect compiler/analyzer warnings
+ //
+ NumPages = 0;
+
+ //
+ // If IOMMU platform is enabled for this device then allocate Request and
+ // HostStatus variable dynamically using IOMMU allocator
+ //
+ if (mIoMmuProtocol) {
+ EFI_STATUS Status;
+
+ NumPages = (UINT32)EFI_SIZE_TO_PAGES (sizeof (*Request) + sizeof (*HostStatus));
+ Status = mIoMmuProtocol->AllocateBuffer (mIoMmuProtocol, 0,
+ EfiBootServicesData, NumPages, &IommuBuffer,
+ EDKII_IOMMU_ATTRIBUTE_MEMORY_CACHED);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ Request = IommuBuffer;
+ HostStatus = IommuBuffer + sizeof(*Request);
+ } else {
+ Request = &LocalRequest;
+ HostStatus = &LocalHostStatus;
+ IommuBuffer = NULL;
+ Mapping = NULL;
+ }
+
+ //
// ensured by VirtioBlkInit()
//
ASSERT (BlockSize > 0);
@@ -268,18 +308,18 @@ SynchronousRequest (
// Prepare virtio-blk request header, setting zero size for flush.
// IO Priority is homogeneously 0.
//
- Request.Type = RequestIsWrite ?
+ Request->Type = RequestIsWrite ?
(BufferSize == 0 ? VIRTIO_BLK_T_FLUSH : VIRTIO_BLK_T_OUT) :
VIRTIO_BLK_T_IN;
- Request.IoPrio = 0;
- Request.Sector = MultU64x32(Lba, BlockSize / 512);
+ Request->IoPrio = 0;
+ Request->Sector = MultU64x32(Lba, BlockSize / 512);
VirtioPrepare (&Dev->Ring, &Indices);
//
// preset a host status for ourselves that we do not accept as success
//
- HostStatus = VIRTIO_BLK_S_IOERR;
+ *HostStatus = VIRTIO_BLK_S_IOERR;
//
// ensured by VirtioBlkInit() -- this predicate, in combination with the
@@ -290,7 +330,7 @@ SynchronousRequest (
//
// virtio-blk header in first desc
//
- VirtioAppendDesc (&Dev->Ring, (UINTN) &Request, sizeof Request,
+ VirtioAppendDesc (&Dev->Ring, (UINTN) Request, sizeof *Request,
VRING_DESC_F_NEXT, &Indices);
//
@@ -308,6 +348,42 @@ SynchronousRequest (
ASSERT (BufferSize <= SIZE_1GB);
//
+ // When IOMMU platform is enabled, map the host buffer to device
+ //
+ if (mIoMmuProtocol) {
+ EDKII_IOMMU_OPERATION Operation;
+ UINTN NumBytes;
+
+ NumBytes = BufferSize;
+
+ if (RequestIsWrite) {
+ Operation = EdkiiIoMmuOperationBusMasterRead;
+ } else {
+ Operation = EdkiiIoMmuOperationBusMasterWrite;
+ }
+
+ Status = mIoMmuProtocol->Map (mIoMmuProtocol, Operation, (VOID *)Buffer,
+ &NumBytes, &DeviceBuffer, &Mapping);
+ if (EFI_ERROR (Status)) {
+ Status = EFI_DEVICE_ERROR;
+ goto HandleExit;
+ }
+
+ //
+ // Verify that we are able to map the required size
+ //
+ if (NumBytes < BufferSize) {
+ DEBUG ((DEBUG_ERROR, "%a: request %d got %d\n", __FUNCTION__,
+ BufferSize, NumBytes));
+
+ Status = EFI_DEVICE_ERROR;
+ mIoMmuProtocol->Unmap (mIoMmuProtocol, Mapping);
+ goto HandleExit;
+ }
+ Buffer = (VOID *)DeviceBuffer;
+ }
+
+ //
// VRING_DESC_F_WRITE is interpreted from the host's point of view.
//
VirtioAppendDesc (&Dev->Ring, (UINTN) Buffer, (UINT32) BufferSize,
@@ -318,7 +394,7 @@ SynchronousRequest (
//
// host status in last (second or third) desc
//
- VirtioAppendDesc (&Dev->Ring, (UINTN) &HostStatus, sizeof HostStatus,
+ VirtioAppendDesc (&Dev->Ring, (UINTN) HostStatus, sizeof *HostStatus,
VRING_DESC_F_WRITE, &Indices);
//
@@ -326,11 +402,23 @@ SynchronousRequest (
//
if (VirtioFlush (Dev->VirtIo, 0, &Dev->Ring, &Indices,
NULL) == EFI_SUCCESS &&
- HostStatus == VIRTIO_BLK_S_OK) {
- return EFI_SUCCESS;
+ *HostStatus == VIRTIO_BLK_S_OK) {
+ Status = EFI_SUCCESS;
+ goto HandleExit;
}
- return EFI_DEVICE_ERROR;
+ Status = EFI_DEVICE_ERROR;
+
+HandleExit:
+ if (Mapping) {
+ mIoMmuProtocol->Unmap (mIoMmuProtocol, Mapping);
+ }
+
+ if (IommuBuffer) {
+ mIoMmuProtocol->FreeBuffer (mIoMmuProtocol, NumPages, IommuBuffer);
+ }
+
+ return Status;
}
@@ -692,6 +780,21 @@ VirtioBlkInit (
}
}
+ //
+ // If IOMMU_PLATFORM feature is requested then try to locate IOMMU
+ // protocol before acking that we support IOMMU_PLATFORM feature
+ //
+ if (Features & VIRTIO_F_IOMMU_PLATFORM) {
+ Status = gBS->LocateProtocol (&gEdkiiIoMmuProtocolGuid, NULL, (VOID **)&mIoMmuProtocol);
+ if (EFI_ERROR (Status)) {
+ DEBUG ((DEBUG_ERROR, "%a: Failed to locate IOMMU protocol\n", __FUNCTION__));
+ goto Failed;
+ }
+
+ Features &= VIRTIO_F_IOMMU_PLATFORM;
+ VirtioRingUseIommu (&Dev->Ring, mIoMmuProtocol);
+ }
+
Features &= VIRTIO_BLK_F_BLK_SIZE | VIRTIO_BLK_F_TOPOLOGY | VIRTIO_BLK_F_RO |
VIRTIO_BLK_F_FLUSH | VIRTIO_F_VERSION_1;
--
2.7.4
^ permalink raw reply related [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
[not found] ` <62320c1a-0cec-947c-8c63-5eb0416e4e33@redhat.com>
@ 2017-07-21 11:17 ` Brijesh Singh
[not found] ` <20170722024318-mutt-send-email-mst@kernel.org>
0 siblings, 1 reply; 28+ messages in thread
From: Brijesh Singh @ 2017-07-21 11:17 UTC (permalink / raw)
To: Jason Wang, edk2-devel
Cc: brijesh.singh, Tom Lendacky, Jordan Justen, Laszlo Ersek,
Michael S . Tsirkin
On 7/20/17 10:24 PM, Jason Wang wrote:
>
>
> On 2017年07月20日 06:09, Brijesh Singh wrote:
>> I have found that OVMF fails to detect the disk when iommu_platform
>> is set from
>> qemu cli. The failure occurs during the feature bit negotiation.
>>
>> Recently, EDKII introduced IOMMU protocol d1fddc4533bf. SEV patch
>> series introduced
>> a IoMmu protocol driver f9d129e68a45 to set a DMA access attribute
>> and methods to
>> allocate, free, map and unmap the DMA memory for the master bus devices
>>
>> In this patch series, I have tried to enable the IOMMU_PLATFORM
>> feature for
>> VirtioBlkDevice. I am sending this as RFC to seek feedback before I
>> extend the support
>> for other Virtio devices. The patch has been tested in SEV guest -
>> mainly because
>> IoMmuDxe driver installs the IOMMU protocol for SEV guest only. If
>> needed, I can
>> extend the IoMmuDxe driver to install IOMMU protocol for non SEV guests.
>>
>> qemu cli used for testing:
>>
>> # $QEMU \
>> ...
>> -drive file=${HDA_FILE},if=none,id=disk0,format=qcow2 \
>> -device
>> virtio-blk-pci,drive=disk0,disable-legacy=on,iommu_platform=true,disable-modern=off,scsi=off
>> ...
>>
>> Cc: Jordan Justen <jordan.l.justen@intel.com>
>> Cc: Laszlo Ersek <lersek@redhat.com>
>> Cc: Jason Wang <jasowang@redhat.com>
>> Cc: Michael S. Tsirkin <mst@redhat.com>
>> Contributed-under: TianoCore Contribution Agreement 1.0
>> Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
>>
>> Brijesh Singh (3):
>> OvmfPkg/Include/Virtio10: Define VIRTIO_F_IOMMU_PLATFORM feature bit
>> OvmfPkg/VirtioLib: Add IOMMU_PLATFORM support
>> OvmfPkg/VirtioBlkDxe: Add VIRITO_F_IOMMU_PLATFORM support
>
> Hi, do we need change virtio-scsi driver as well?
>
I see that OVMF has the following virtio drivers, we need to update them
all:
VirtioBlkDxe
VirtioGpuDxe
VirtioNetDxe
VirtioRngDxe
VirtioScsiDxe
I will wait for Laszlo and Jordan's initial feedback before changing
other drivers.
> Thanks
>
>>
>> OvmfPkg/Library/VirtioLib/VirtioLib.inf | 1 +
>> OvmfPkg/VirtioBlkDxe/VirtioBlk.inf | 5 +
>> OvmfPkg/VirtioGpuDxe/VirtioGpu.inf | 1 +
>> OvmfPkg/VirtioNetDxe/VirtioNet.inf | 1 +
>> OvmfPkg/VirtioRngDxe/VirtioRng.inf | 1 +
>> OvmfPkg/VirtioScsiDxe/VirtioScsi.inf | 1 +
>> OvmfPkg/Include/IndustryStandard/Virtio095.h | 1 +
>> OvmfPkg/Include/IndustryStandard/Virtio10.h | 5 +
>> OvmfPkg/Include/Library/VirtioLib.h | 20 ++++
>> OvmfPkg/Library/VirtioLib/VirtioLib.c | 96 ++++++++++++++-
>> OvmfPkg/VirtioBlkDxe/VirtioBlk.c | 125
>> ++++++++++++++++++--
>> 11 files changed, 244 insertions(+), 13 deletions(-)
>>
>
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
[not found] ` <20170722024318-mutt-send-email-mst@kernel.org>
@ 2017-07-24 8:25 ` Gerd Hoffmann
0 siblings, 0 replies; 28+ messages in thread
From: Gerd Hoffmann @ 2017-07-24 8:25 UTC (permalink / raw)
To: Michael S. Tsirkin, Brijesh Singh
Cc: Jason Wang, edk2-devel, Tom Lendacky, Jordan Justen, Laszlo Ersek,
David Airlie, dri-devel, virtualization
Hi,
> > I see that OVMF has the following virtio drivers, we need to update
> > them
> > all:
> >
> > VirtioBlkDxe
> > VirtioGpuDxe
> > VirtioNetDxe
> > VirtioRngDxe
> > VirtioScsiDxe
> >
> > I will wait for Laszlo and Jordan's initial feedback before
> > changing
> > other drivers.
>
> I'm not sure about the GPU. Cc relevant maintainers -
> can virtio GPU work from behind an IOMMU?
GPU uses main memory as backing storage for framebuffers, and this is
passed as guest physical address (scatterlist of addresses to be exact)
to the host.
So, I think no, this isn't going to work with the current code.
Should be possible to fix though. We need to define what "guest
physical address" should be with VIRTIO_F_IOMMU_PLATFORM enabled
(probably guest pci bus address) and add support for proper iommu
lookups.
cheers,
Gerd
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-19 22:09 [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support Brijesh Singh
` (3 preceding siblings ...)
[not found] ` <62320c1a-0cec-947c-8c63-5eb0416e4e33@redhat.com>
@ 2017-07-25 18:17 ` Laszlo Ersek
2017-07-25 23:42 ` Brijesh Singh
` (2 more replies)
4 siblings, 3 replies; 28+ messages in thread
From: Laszlo Ersek @ 2017-07-25 18:17 UTC (permalink / raw)
To: Brijesh Singh, edk2-devel
Cc: Tom Lendacky, Jordan Justen, Jason Wang, Michael S . Tsirkin,
Gerd Hoffmann, Ard Biesheuvel
Adding Ard
On 07/20/17 00:09, Brijesh Singh wrote:
> I have found that OVMF fails to detect the disk when iommu_platform is set from
> qemu cli. The failure occurs during the feature bit negotiation.
>
> Recently, EDKII introduced IOMMU protocol d1fddc4533bf. SEV patch series introduced
> a IoMmu protocol driver f9d129e68a45 to set a DMA access attribute and methods to
> allocate, free, map and unmap the DMA memory for the master bus devices
>
> In this patch series, I have tried to enable the IOMMU_PLATFORM feature for
> VirtioBlkDevice. I am sending this as RFC to seek feedback before I extend the support
> for other Virtio devices. The patch has been tested in SEV guest - mainly because
> IoMmuDxe driver installs the IOMMU protocol for SEV guest only. If needed, I can
> extend the IoMmuDxe driver to install IOMMU protocol for non SEV guests.
>
> qemu cli used for testing:
>
> # $QEMU \
> ...
> -drive file=${HDA_FILE},if=none,id=disk0,format=qcow2 \
> -device virtio-blk-pci,drive=disk0,disable-legacy=on,iommu_platform=true,disable-modern=off,scsi=off
> ...
>
> Cc: Jordan Justen <jordan.l.justen@intel.com>
> Cc: Laszlo Ersek <lersek@redhat.com>
> Cc: Jason Wang <jasowang@redhat.com>
> Cc: Michael S. Tsirkin <mst@redhat.com>
> Contributed-under: TianoCore Contribution Agreement 1.0
> Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
>
> Brijesh Singh (3):
> OvmfPkg/Include/Virtio10: Define VIRTIO_F_IOMMU_PLATFORM feature bit
> OvmfPkg/VirtioLib: Add IOMMU_PLATFORM support
> OvmfPkg/VirtioBlkDxe: Add VIRITO_F_IOMMU_PLATFORM support
In the course of processing my post-PTO email backlog, yesterday I
skimmed this topic very quickly, and thought about it for an hour or so
(while away for the computer). Here's my take on it.
(1) The closest to any formal language that I found for
VIRTIO_F_IOMMU_PLATFORM are:
https://issues.oasis-open.org/browse/VIRTIO-154
https://lists.oasis-open.org/archives/virtio-dev/201610/msg00121.html
Are these references up-to-date? The ticket also references SVN r587 as
the resolution, but that link is dead.
(2) A few weeks back I saw Jason's SeaBIOS patch (also pointed out to me
more recently by Brijesh, in private):
[SeaBIOS] [PATCH] virtio: IOMMU support
I don't understand this patch. (I also don't understand the
"iommu_platform" device property on the QEMU command line.) According to
the spec language quoted from the mailing list above, we have four cases:
(2.1) device does not offer VIRITO_F_IOMMU_PLATFORM --> everything works
like before
(2.2) device offers VIRITO_F_IOMMU_PLATFORM, but the driver does not
negotiate it --> device is allowed to reject functioning
* device offers VIRITO_F_IOMMU_PLATFORM and the driver negotiates it:
there are two possibilities:
(2.3) the driver *disables* the IOMMU, and works like before
(2.4) the driver *configures* the IOMMU and works accordingly
The SeaBIOS patch negotiates VIRITO_F_IOMMU_PLATFORM, but it *neither*
disables *nor* configures the IOMMU. It simply *ignores* the IOMMU. I
don't see how that is any different *in effect* from simply not
negotiating VIRITO_F_IOMMU_PLATFORM -- case (2.2) --, and I don't
understand why QEMU tolerates "ignoring the IOMMU" differently from "not
negotiating the flag".
(3) *If* we indeed just wanted to follow the SeaBIOS patch, then
VIRTIO_F_IOMMU_PLATFORM should be treated in OVMF *precisely* in
parallel with VIRTIO_F_VERSION_1.
(4) I'm confused by the intersection of the SEV and
VIRTIO_F_IOMMU_PLATFORM use cases. The IoMmu DXE driver added in edk2
commit f9d129e68a45 ("OvmfPkg: Add IoMmuDxe driver", 2017-07-06) is
specific to SEV.
In SEV-less guests, the IoMmu DXE driver will not install the IOMMU
protocol, but the QEMU command line may still contain the
"iommu_platform=true" property. For example, as far as I recall, DPDK
guest payloads use an emulated "viommu" device. For this OVMF, would
have to provide a separate IOMMU DXE driver, one that could actually
interact with the "viommu" device. And there should be some coordination
that exactly one instance of gEdkiiIoMmuProtocolGuid OR
gIoMmuAbsentProtocolGuid be installed.
I see that the SEV references are only in the blurb of the patch set; no
actual commit message refers to SEV. That's OK; I just think the blurb
is confusing.
(5) Now I'm coming to my main point. The virtio device drivers in
OvmfPkg are UEFI_DRIVER modules that conform to the UEFI driver model.
They consume our home-grown the VIRTIO_DEVICE_PROTOCOL, and produce
whatever UEFI protocol is appropriate on top (for example,
EFI_BLOCK_IO_PROTOCOL in VirtioBlkDxe).
Such a driver has no business talking to the platform's IOMMU protocol
directly (even if there is one). Instead:
(5.1) The VIRTIO_DEVICE_PROTOCOL has to be extended with the necessary
AllocateSharedPages, FreeSharedPages, MapSharedPages, UnmapSharedPages
functions.
(5.2) All top-level virtio driver code, including VirtioLib, has to be
rebased to the new VIRTIO_DEVICE_PROTOCOL member functions. This covers
the ring memory itself, the memory pointed-to by the descriptors placed
on the ring(s) -- i.e., the device specific requests --, and all further
memory that is referenced by those device specific requests.
This will result in a larger memory footprint, as all current pool
allocations will be turned into page allocations, but I guess that is
tolerable.
(5.3) All virtio driver code should treat VIRTIO_F_IOMMU_PLATFORM simply
in parallel with VIRTIO_F_VERSION_1, and don't act upon
VIRTIO_F_IOMMU_PLATFORM in any special shape or form. So basically this
is just my point (3) from above.
(5.4) There are three VIRTIO_DEVICE_PROTOCOL implementations in edk2:
- "OvmfPkg/VirtioPciDeviceDxe" binds legacy-only and transitional
virtio-pci devices, and offers virtio 0.9.5 semantics.
- "ArmVirtPkg/VirtioFdtDxe" (via "OvmfPkg/Library/VirtioMmioDeviceLib")
binds virtio-mmio devices, and offers virtio 0.9.5 semantics.
- "OvmfPkg/Virtio10Dxe" binds modern-only virtio-pci devices, and offers
virtio 1.0.0 semantics.
The first two drivers should implement the AllocateSharedPages() and
FreeSharedPages() member functions simply with the corresponding
MemoryAllocationLib functions (using BootServicesData type memory), and
implement the MapSharedPages() and UnmapSharedPages() member functions
as no-ops (return the input addresses transparently).
The third driver should implement all four new member functions by
respectively delegating the job to:
- EFI_PCI_IO_PROTOCOL.AllocateBuffer() -- with BootServicesData --
- EFI_PCI_IO_PROTOCOL.FreeBuffer()
- EFI_PCI_IO_PROTOCOL.Map() -- with BusMasterCommonBuffer64 --
- EFI_PCI_IO_PROTOCOL.Unmap()
The EFI_PCI_IO_PROTOCOL implementation will delegate these calls to the
platform-specific PCI host bridge / root bridge driver, and *that*
driver in turn is allowed to talk to an IOMMU protocol (if any).
(This last step is already covered by the following edk2 commits:
- generally, c15da8eb3587 ("MdeModulePkg/PciHostBridge: Add IOMMU
support.", 2017-04-29),
- specifically for SEV in OVMF, c6ab9aecb71b ("OvmfPkg: update
PciHostBridgeDxe to use PlatformHasIoMmuLib", 2017-07-06).)
(5.5) There's a delicate question that has to be considered with care;
namely the ExitBootServices() callbacks in the virtio drivers.
Currently these functions perform virtio resets. They cause the devices
to forget all their configuration (including guest RAM references).
Aborting in-flight DMA (e.g. in VirtioNetDxe) is a practice that is
specifically recommended in the UEFI Driver Writers' Guide; plus at some
point the guest kernel will reclaim and overwrite BootServicesData type
memory. If at that point QEMU is still looking at some (originally
firmware-allocated) areas as virtio rings, the results won't be amusing.
(Speaking from experience.) Hence the resetting of virtio devices upon
ExitBootServices().
Now, remember that ExitBootServices() callbacks *must not* change the
UEFI memory map, so *no* memory allocations *must* be released in the
callbacks. The virtio resets performed in the callbacks are surgical for
this reason; nothing else is being done. The memory will be reclaimed by
the OS, later.
With an IOMMU in the picture, further actions become necessary: *after*
the virtio reset, any buffers previously in use (including rings, device
specific requests pointed-to by descriptors, and any further memory
referenced by those requests) must be *unmapped*, but *not freed*.
(Speaking in SEV terms, this will result in those memory areas seeing
their C bits restored, without changing the UEFI memmap.)
This means that:
- the new VIRTIO_DEVICE_PROTOCOL.UnmapSharedPages() function has to be
called judiciously from these callbacks, after the virtio reset,
- *and* that the *entire* call chain originating from
UnmapSharedPages(), through PciIo, through PciRootBridgeIo, to
EdkiiIoMmu, *must* not call gBS->FreePool() or gBS->FreePages() (or the
equivalent MemoryAllocationLib functions).
Note that if the last requirement is currently violated (outside of
OvmfPkg), then that is a general problem for physical platforms as well
-- IMO, a physical NIC driver too should be able to abort DMA in its
exit-boot-services callback and then unmap any relevant IOMMU mappings
(via PciIo->Unmap().)
Thanks
Laszlo
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-25 18:17 ` Laszlo Ersek
@ 2017-07-25 23:42 ` Brijesh Singh
[not found] ` <904dae9f-e515-01ba-e16f-6561616c78af@redhat.com>
2017-07-27 14:21 ` Brijesh Singh
2 siblings, 0 replies; 28+ messages in thread
From: Brijesh Singh @ 2017-07-25 23:42 UTC (permalink / raw)
To: Laszlo Ersek, edk2-devel
Cc: brijesh.singh, Tom Lendacky, Jordan Justen, Jason Wang,
Michael S . Tsirkin, Gerd Hoffmann, Ard Biesheuvel
Hi Laszlo,
On 07/25/2017 01:17 PM, Laszlo Ersek wrote:
> Adding Ard
>
> On 07/20/17 00:09, Brijesh Singh wrote:
>> I have found that OVMF fails to detect the disk when iommu_platform is set from
>> qemu cli. The failure occurs during the feature bit negotiation.
>>
>> Recently, EDKII introduced IOMMU protocol d1fddc4533bf. SEV patch series introduced
>> a IoMmu protocol driver f9d129e68a45 to set a DMA access attribute and methods to
>> allocate, free, map and unmap the DMA memory for the master bus devices
>>
>> In this patch series, I have tried to enable the IOMMU_PLATFORM feature for
>> VirtioBlkDevice. I am sending this as RFC to seek feedback before I extend the support
>> for other Virtio devices. The patch has been tested in SEV guest - mainly because
>> IoMmuDxe driver installs the IOMMU protocol for SEV guest only. If needed, I can
>> extend the IoMmuDxe driver to install IOMMU protocol for non SEV guests.
>>
>> qemu cli used for testing:
>>
>> # $QEMU \
>> ...
>> -drive file=${HDA_FILE},if=none,id=disk0,format=qcow2 \
>> -device virtio-blk-pci,drive=disk0,disable-legacy=on,iommu_platform=true,disable-modern=off,scsi=off
>> ...
>>
>> Cc: Jordan Justen <jordan.l.justen@intel.com>
>> Cc: Laszlo Ersek <lersek@redhat.com>
>> Cc: Jason Wang <jasowang@redhat.com>
>> Cc: Michael S. Tsirkin <mst@redhat.com>
>> Contributed-under: TianoCore Contribution Agreement 1.0
>> Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
>>
>> Brijesh Singh (3):
>> OvmfPkg/Include/Virtio10: Define VIRTIO_F_IOMMU_PLATFORM feature bit
>> OvmfPkg/VirtioLib: Add IOMMU_PLATFORM support
>> OvmfPkg/VirtioBlkDxe: Add VIRITO_F_IOMMU_PLATFORM support
>
> In the course of processing my post-PTO email backlog, yesterday I
> skimmed this topic very quickly, and thought about it for an hour or so
> (while away for the computer). Here's my take on it.
>
>
> (1) The closest to any formal language that I found for
> VIRTIO_F_IOMMU_PLATFORM are:
>
> https://issues.oasis-open.org/browse/VIRTIO-154
> https://lists.oasis-open.org/archives/virtio-dev/201610/msg00121.html
>
> Are these references up-to-date? The ticket also references SVN r587 as
> the resolution, but that link is dead.
>
>
> (2) A few weeks back I saw Jason's SeaBIOS patch (also pointed out to me
> more recently by Brijesh, in private):
>
> [SeaBIOS] [PATCH] virtio: IOMMU support
>
> I don't understand this patch. (I also don't understand the
> "iommu_platform" device property on the QEMU command line.) According to
> the spec language quoted from the mailing list above, we have four cases:
>
> (2.1) device does not offer VIRITO_F_IOMMU_PLATFORM --> everything works
> like before
>
> (2.2) device offers VIRITO_F_IOMMU_PLATFORM, but the driver does not
> negotiate it --> device is allowed to reject functioning
>
> * device offers VIRITO_F_IOMMU_PLATFORM and the driver negotiates it:
> there are two possibilities:
> (2.3) the driver *disables* the IOMMU, and works like before
> (2.4) the driver *configures* the IOMMU and works accordingly
>
> The SeaBIOS patch negotiates VIRITO_F_IOMMU_PLATFORM, but it *neither*
> disables *nor* configures the IOMMU. It simply *ignores* the IOMMU. I
> don't see how that is any different *in effect* from simply not
> negotiating VIRITO_F_IOMMU_PLATFORM -- case (2.2) --, and I don't
> understand why QEMU tolerates "ignoring the IOMMU" differently from "not
> negotiating the flag".
>
>
> (3) *If* we indeed just wanted to follow the SeaBIOS patch, then
> VIRTIO_F_IOMMU_PLATFORM should be treated in OVMF *precisely* in
> parallel with VIRTIO_F_VERSION_1.
>
>
> (4) I'm confused by the intersection of the SEV and
> VIRTIO_F_IOMMU_PLATFORM use cases. The IoMmu DXE driver added in edk2
> commit f9d129e68a45 ("OvmfPkg: Add IoMmuDxe driver", 2017-07-06) is
> specific to SEV.
>
> In SEV-less guests, the IoMmu DXE driver will not install the IOMMU
> protocol, but the QEMU command line may still contain the
> "iommu_platform=true" property. For example, as far as I recall, DPDK
> guest payloads use an emulated "viommu" device. For this OVMF, would
> have to provide a separate IOMMU DXE driver, one that could actually
> interact with the "viommu" device. And there should be some coordination
> that exactly one instance of gEdkiiIoMmuProtocolGuid OR
> gIoMmuAbsentProtocolGuid be installed.
>
> I see that the SEV references are only in the blurb of the patch set; no
> actual commit message refers to SEV. That's OK; I just think the blurb
> is confusing.
>
I was trying to figure out why setting iommu_platform fails to detect the HDD
in OVMF. Since SEV IOMMU work was still fresh in my mind hence I thought we
simply need to update the Virtio drivers to consume IOMMU protocol directly.
But your explanation on how things work makes sense; thanks for explaining
it in great detail.
I will follow your recommendation, and look into extending VIRTIO_DEVICE_PROTOCOL
with necessary functions and delegate the work to EFI_PCI_IO_PROTOCOL (which will
use IOMMU driver if available).
Thanks
Brijesh
>
> (5) Now I'm coming to my main point. The virtio device drivers in
> OvmfPkg are UEFI_DRIVER modules that conform to the UEFI driver model.
> They consume our home-grown the VIRTIO_DEVICE_PROTOCOL, and produce
> whatever UEFI protocol is appropriate on top (for example,
> EFI_BLOCK_IO_PROTOCOL in VirtioBlkDxe).
>
> Such a driver has no business talking to the platform's IOMMU protocol
> directly (even if there is one). Instead:
>
>
> (5.1) The VIRTIO_DEVICE_PROTOCOL has to be extended with the necessary
>
> AllocateSharedPages, FreeSharedPages, MapSharedPages, UnmapSharedPages
>
> functions.
>
>
> (5.2) All top-level virtio driver code, including VirtioLib, has to be
> rebased to the new VIRTIO_DEVICE_PROTOCOL member functions. This covers
> the ring memory itself, the memory pointed-to by the descriptors placed
> on the ring(s) -- i.e., the device specific requests --, and all further
> memory that is referenced by those device specific requests.
>
> This will result in a larger memory footprint, as all current pool
> allocations will be turned into page allocations, but I guess that is
> tolerable.
>
>
> (5.3) All virtio driver code should treat VIRTIO_F_IOMMU_PLATFORM simply
> in parallel with VIRTIO_F_VERSION_1, and don't act upon
> VIRTIO_F_IOMMU_PLATFORM in any special shape or form. So basically this
> is just my point (3) from above.
>
>
> (5.4) There are three VIRTIO_DEVICE_PROTOCOL implementations in edk2:
>
> - "OvmfPkg/VirtioPciDeviceDxe" binds legacy-only and transitional
> virtio-pci devices, and offers virtio 0.9.5 semantics.
>
> - "ArmVirtPkg/VirtioFdtDxe" (via "OvmfPkg/Library/VirtioMmioDeviceLib")
> binds virtio-mmio devices, and offers virtio 0.9.5 semantics.
>
> - "OvmfPkg/Virtio10Dxe" binds modern-only virtio-pci devices, and offers
> virtio 1.0.0 semantics.
>
> The first two drivers should implement the AllocateSharedPages() and
> FreeSharedPages() member functions simply with the corresponding
> MemoryAllocationLib functions (using BootServicesData type memory), and
> implement the MapSharedPages() and UnmapSharedPages() member functions
> as no-ops (return the input addresses transparently).
>
> The third driver should implement all four new member functions by
> respectively delegating the job to:
> - EFI_PCI_IO_PROTOCOL.AllocateBuffer() -- with BootServicesData --
> - EFI_PCI_IO_PROTOCOL.FreeBuffer()
> - EFI_PCI_IO_PROTOCOL.Map() -- with BusMasterCommonBuffer64 --
> - EFI_PCI_IO_PROTOCOL.Unmap()
>
> The EFI_PCI_IO_PROTOCOL implementation will delegate these calls to the
> platform-specific PCI host bridge / root bridge driver, and *that*
> driver in turn is allowed to talk to an IOMMU protocol (if any).
>
> (This last step is already covered by the following edk2 commits:
> - generally, c15da8eb3587 ("MdeModulePkg/PciHostBridge: Add IOMMU
> support.", 2017-04-29),
> - specifically for SEV in OVMF, c6ab9aecb71b ("OvmfPkg: update
> PciHostBridgeDxe to use PlatformHasIoMmuLib", 2017-07-06).)
>
>
> (5.5) There's a delicate question that has to be considered with care;
> namely the ExitBootServices() callbacks in the virtio drivers.
>
> Currently these functions perform virtio resets. They cause the devices
> to forget all their configuration (including guest RAM references).
> Aborting in-flight DMA (e.g. in VirtioNetDxe) is a practice that is
> specifically recommended in the UEFI Driver Writers' Guide; plus at some
> point the guest kernel will reclaim and overwrite BootServicesData type
> memory. If at that point QEMU is still looking at some (originally
> firmware-allocated) areas as virtio rings, the results won't be amusing.
> (Speaking from experience.) Hence the resetting of virtio devices upon
> ExitBootServices().
>
> Now, remember that ExitBootServices() callbacks *must not* change the
> UEFI memory map, so *no* memory allocations *must* be released in the
> callbacks. The virtio resets performed in the callbacks are surgical for
> this reason; nothing else is being done. The memory will be reclaimed by
> the OS, later.
>
> With an IOMMU in the picture, further actions become necessary: *after*
> the virtio reset, any buffers previously in use (including rings, device
> specific requests pointed-to by descriptors, and any further memory
> referenced by those requests) must be *unmapped*, but *not freed*.
>
> (Speaking in SEV terms, this will result in those memory areas seeing
> their C bits restored, without changing the UEFI memmap.)
>
> This means that:
> - the new VIRTIO_DEVICE_PROTOCOL.UnmapSharedPages() function has to be
> called judiciously from these callbacks, after the virtio reset,
> - *and* that the *entire* call chain originating from
> UnmapSharedPages(), through PciIo, through PciRootBridgeIo, to
> EdkiiIoMmu, *must* not call gBS->FreePool() or gBS->FreePages() (or the
> equivalent MemoryAllocationLib functions).
>
> Note that if the last requirement is currently violated (outside of
> OvmfPkg), then that is a general problem for physical platforms as well
> -- IMO, a physical NIC driver too should be able to abort DMA in its
> exit-boot-services callback and then unmap any relevant IOMMU mappings
> (via PciIo->Unmap().)
>
> Thanks
> Laszlo
>
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
[not found] ` <904dae9f-e515-01ba-e16f-6561616c78af@redhat.com>
@ 2017-07-26 15:30 ` Laszlo Ersek
0 siblings, 0 replies; 28+ messages in thread
From: Laszlo Ersek @ 2017-07-26 15:30 UTC (permalink / raw)
To: Jason Wang, Brijesh Singh, edk2-devel
Cc: Tom Lendacky, Jordan Justen, Michael S . Tsirkin, Gerd Hoffmann,
Ard Biesheuvel
On 07/26/17 05:32, Jason Wang wrote:
>
>
> On 2017年07月26日 02:17, Laszlo Ersek wrote:
>> Adding Ard
>>
>> On 07/20/17 00:09, Brijesh Singh wrote:
>>> I have found that OVMF fails to detect the disk when iommu_platform
>>> is set from
>>> qemu cli. The failure occurs during the feature bit negotiation.
>>>
>>> Recently, EDKII introduced IOMMU protocol d1fddc4533bf. SEV patch
>>> series introduced
>>> a IoMmu protocol driver f9d129e68a45 to set a DMA access attribute
>>> and methods to
>>> allocate, free, map and unmap the DMA memory for the master bus devices
>>>
>>> In this patch series, I have tried to enable the IOMMU_PLATFORM
>>> feature for
>>> VirtioBlkDevice. I am sending this as RFC to seek feedback before I
>>> extend the support
>>> for other Virtio devices. The patch has been tested in SEV guest -
>>> mainly because
>>> IoMmuDxe driver installs the IOMMU protocol for SEV guest only. If
>>> needed, I can
>>> extend the IoMmuDxe driver to install IOMMU protocol for non SEV guests.
>>>
>>> qemu cli used for testing:
>>>
>>> # $QEMU \
>>> ...
>>> -drive file=${HDA_FILE},if=none,id=disk0,format=qcow2 \
>>> -device
>>> virtio-blk-pci,drive=disk0,disable-legacy=on,iommu_platform=true,disable-modern=off,scsi=off
>>>
>>> ...
>>>
>>> Cc: Jordan Justen<jordan.l.justen@intel.com>
>>> Cc: Laszlo Ersek<lersek@redhat.com>
>>> Cc: Jason Wang<jasowang@redhat.com>
>>> Cc: Michael S. Tsirkin<mst@redhat.com>
>>> Contributed-under: TianoCore Contribution Agreement 1.0
>>> Signed-off-by: Brijesh Singh<brijesh.singh@amd.com>
>>>
>>> Brijesh Singh (3):
>>> OvmfPkg/Include/Virtio10: Define VIRTIO_F_IOMMU_PLATFORM feature bit
>>> OvmfPkg/VirtioLib: Add IOMMU_PLATFORM support
>>> OvmfPkg/VirtioBlkDxe: Add VIRITO_F_IOMMU_PLATFORM support
>> In the course of processing my post-PTO email backlog, yesterday I
>> skimmed this topic very quickly, and thought about it for an hour or so
>> (while away for the computer). Here's my take on it.
>>
>>
>> (1) The closest to any formal language that I found for
>> VIRTIO_F_IOMMU_PLATFORM are:
>>
>> https://issues.oasis-open.org/browse/VIRTIO-154
>> https://lists.oasis-open.org/archives/virtio-dev/201610/msg00121.html
>>
>> Are these references up-to-date? The ticket also references SVN r587 as
>> the resolution, but that link is dead.
>>
>>
>> (2) A few weeks back I saw Jason's SeaBIOS patch (also pointed out to me
>> more recently by Brijesh, in private):
>>
>> [SeaBIOS] [PATCH] virtio: IOMMU support
>>
>> I don't understand this patch. (I also don't understand the
>> "iommu_platform" device property on the QEMU command line.) According to
>> the spec language quoted from the mailing list above, we have four cases:
>>
>> (2.1) device does not offer VIRITO_F_IOMMU_PLATFORM --> everything works
>> like before
>>
>> (2.2) device offers VIRITO_F_IOMMU_PLATFORM, but the driver does not
>> negotiate it --> device is allowed to reject functioning
>>
>> * device offers VIRITO_F_IOMMU_PLATFORM and the driver negotiates it:
>> there are two possibilities:
>> (2.3) the driver*disables* the IOMMU, and works like before
>> (2.4) the driver*configures* the IOMMU and works accordingly
>>
>> The SeaBIOS patch negotiates VIRITO_F_IOMMU_PLATFORM, but it*neither*
>> disables*nor* configures the IOMMU. It simply*ignores* the IOMMU. I
>> don't see how that is any different*in effect* from simply not
>> negotiating VIRITO_F_IOMMU_PLATFORM -- case (2.2) --, and I don't
>> understand why QEMU tolerates "ignoring the IOMMU" differently from "not
>> negotiating the flag".
>
> I think the difference is we don't want give the ability of bypassing
> IOMMU at driver level. That's why we forbid it in the case of 2.2.
>
> For 2.3 IOMMU was disabled by e.g guest os not driver.
Ah! That makes a lot of sense. I guess this is again motivated by the
DPDK use case -- the guest kernel would decide about IOMMU setup, but
the virtio driver (which is in guest userspace) is required to comply
with the IOMMU requirement, even if the guest kernel does not actually
program the IOMMU.
I hope my above interpretation is correct, because the recommendations
in my other (long) email match it 100%. Namely, in UEFI, IOMMU setup (or
non-setup) is separated to various platform drivers, and the virtio
device drivers should be abstracted from the actual IOMMU presence (this
is the gist of my long email). Hence simply confirming
VIRITO_F_IOMMU_PLATFORM is right for the device drivers.
Thanks
Laszlo
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-25 18:17 ` Laszlo Ersek
2017-07-25 23:42 ` Brijesh Singh
[not found] ` <904dae9f-e515-01ba-e16f-6561616c78af@redhat.com>
@ 2017-07-27 14:21 ` Brijesh Singh
2017-07-27 17:16 ` Laszlo Ersek
2 siblings, 1 reply; 28+ messages in thread
From: Brijesh Singh @ 2017-07-27 14:21 UTC (permalink / raw)
To: Laszlo Ersek, edk2-devel
Cc: brijesh.singh, Tom Lendacky, Jordan Justen, Jason Wang,
Michael S . Tsirkin, Gerd Hoffmann, Ard Biesheuvel
Hi Laszlo,
>
> (5.3) All virtio driver code should treat VIRTIO_F_IOMMU_PLATFORM simply
> in parallel with VIRTIO_F_VERSION_1, and don't act upon
> VIRTIO_F_IOMMU_PLATFORM in any special shape or form. So basically this
> is just my point (3) from above.
>
>
> (5.4) There are three VIRTIO_DEVICE_PROTOCOL implementations in edk2:
>
> - "OvmfPkg/VirtioPciDeviceDxe" binds legacy-only and transitional
> virtio-pci devices, and offers virtio 0.9.5 semantics.
>
> - "ArmVirtPkg/VirtioFdtDxe" (via "OvmfPkg/Library/VirtioMmioDeviceLib")
> binds virtio-mmio devices, and offers virtio 0.9.5 semantics.
>
> - "OvmfPkg/Virtio10Dxe" binds modern-only virtio-pci devices, and offers
> virtio 1.0.0 semantics.
>
> The first two drivers should implement the AllocateSharedPages() and
> FreeSharedPages() member functions simply with the corresponding
> MemoryAllocationLib functions (using BootServicesData type memory), and
> implement the MapSharedPages() and UnmapSharedPages() member functions
> as no-ops (return the input addresses transparently).
>
> The third driver should implement all four new member functions by
> respectively delegating the job to:
> - EFI_PCI_IO_PROTOCOL.AllocateBuffer() -- with BootServicesData --
> - EFI_PCI_IO_PROTOCOL.FreeBuffer()
> - EFI_PCI_IO_PROTOCOL.Map() -- with BusMasterCommonBuffer64 --
> - EFI_PCI_IO_PROTOCOL.Unmap()
>
I have working to implement patch per your recommendation. I assume you mean
map the buffers with EfiPciIoOperationBusMasterCommonBuffer [1]. If so, I see
one issue with SEV guest. When SEV is active, IOMMU driver uses a bounce buffer
to map host address to a device address. While creating bounce buffer we can map
it either for EfiPciIoOperationBusMasterRead or EfiPciIoOperationBusMasterWrite
Operation. If caller wants to map EfiPciIoOperationBusMasterCommonBuffer then it
must allocate the buffer using EFI_PCI_IO_PROTOCOL.AllocateBuffer() [2] otherwise
we will fail to map. I see that PciRootBridgeIo.c has similar check when using
a bounce buffer for < 4GB use cases [3].
Do you see any issue if we use EfiPciIoOperationBusMasterRead or EfiPciIoOperationBusMasterWrite
instead of EfiPciIoOperationBusMasterCommonBuffer ?
[1] https://github.com/tianocore/edk2/blob/master/EdkCompatibilityPkg/Foundation/Efi/Protocol/PciIo/PciIo.h#L169
[2] https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Bus/Pci/PciBusDxe/PciIo.c#L1082
[3] https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Bus/Pci/PciHostBridgeDxe/PciRootBridgeIo.c#L1109
> The EFI_PCI_IO_PROTOCOL implementation will delegate these calls to the
> platform-specific PCI host bridge / root bridge driver, and *that*
> driver in turn is allowed to talk to an IOMMU protocol (if any).
>
> (This last step is already covered by the following edk2 commits:
> - generally, c15da8eb3587 ("MdeModulePkg/PciHostBridge: Add IOMMU
> support.", 2017-04-29),
> - specifically for SEV in OVMF, c6ab9aecb71b ("OvmfPkg: update
> PciHostBridgeDxe to use PlatformHasIoMmuLib", 2017-07-06).)
>
>
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-27 14:21 ` Brijesh Singh
@ 2017-07-27 17:16 ` Laszlo Ersek
2017-07-27 17:56 ` Ard Biesheuvel
0 siblings, 1 reply; 28+ messages in thread
From: Laszlo Ersek @ 2017-07-27 17:16 UTC (permalink / raw)
To: Brijesh Singh
Cc: edk2-devel, Tom Lendacky, Jordan Justen, Jason Wang,
Michael S . Tsirkin, Gerd Hoffmann, Ard Biesheuvel
On 07/27/17 16:21, Brijesh Singh wrote:
> Hi Laszlo,
>
>
>>
>> (5.3) All virtio driver code should treat VIRTIO_F_IOMMU_PLATFORM
>> simply in parallel with VIRTIO_F_VERSION_1, and don't act upon
>> VIRTIO_F_IOMMU_PLATFORM in any special shape or form. So basically
>> this is just my point (3) from above.
>>
>>
>> (5.4) There are three VIRTIO_DEVICE_PROTOCOL implementations in edk2:
>>
>> - "OvmfPkg/VirtioPciDeviceDxe" binds legacy-only and transitional
>> virtio-pci devices, and offers virtio 0.9.5 semantics.
>>
>> - "ArmVirtPkg/VirtioFdtDxe" (via
>> "OvmfPkg/Library/VirtioMmioDeviceLib") binds virtio-mmio devices,
>> and offers virtio 0.9.5 semantics.
>>
>> - "OvmfPkg/Virtio10Dxe" binds modern-only virtio-pci devices, and
>> offers virtio 1.0.0 semantics.
>>
>> The first two drivers should implement the AllocateSharedPages() and
>> FreeSharedPages() member functions simply with the corresponding
>> MemoryAllocationLib functions (using BootServicesData type memory),
>> and implement the MapSharedPages() and UnmapSharedPages() member
>> functions as no-ops (return the input addresses transparently).
>>
>> The third driver should implement all four new member functions by
>> respectively delegating the job to:
>> - EFI_PCI_IO_PROTOCOL.AllocateBuffer() -- with BootServicesData --
>> - EFI_PCI_IO_PROTOCOL.FreeBuffer()
>> - EFI_PCI_IO_PROTOCOL.Map() -- with BusMasterCommonBuffer64 --
>> - EFI_PCI_IO_PROTOCOL.Unmap()
>>
>
> I have working to implement patch per your recommendation. I assume
> you mean map the buffers with EfiPciIoOperationBusMasterCommonBuffer
> [1].
Yes.
> If so, I see one issue with SEV guest. When SEV is active, IOMMU
> driver uses a bounce buffer to map host address to a device address.
> While creating bounce buffer we can map it either for
> EfiPciIoOperationBusMasterRead or EfiPciIoOperationBusMasterWrite
> Operation. If caller wants to map
> EfiPciIoOperationBusMasterCommonBuffer then it must allocate the
> buffer using EFI_PCI_IO_PROTOCOL.AllocateBuffer() [2] otherwise we
> will fail to map.
Correct.
> I see that PciRootBridgeIo.c has similar check when using a bounce
> buffer for < 4GB use cases [3].
Correct.
> Do you see any issue if we use EfiPciIoOperationBusMasterRead or
> EfiPciIoOperationBusMasterWrite instead of
> EfiPciIoOperationBusMasterCommonBuffer ?
Yes, I do. The BusMasterRead and BusMasterWrite operations are suitable
for one-shot, uni-directional transfers, where the bounce buffer
contents and the client code contents are exchanged on Map/Unmap.
However virtio transfers, generally speaking, are not like this. While
the individual memory areas pointed-to by separate virtio descriptors
can me associated with one specific transfer direction (see the
VRING_DESC_F_WRITE flag), the virtio ring area itself is long-lived, and
it is simultaneously read and written by both host and guest,
asynchronously and repeatedly. This calls for BusMasterCommonBuffer.
Once we implement BusMasterCommonBuffer, we can use it for everything
else.
Another reason why separate allocation and mapping (and conversely,
separate unmapping and deallocation) are required is the
ExitBootServices() callbacks. In that callback, unmapping must happen
*without* memory allocation or deallocation (because the IOMMU must be
de-programmed, but the UEFI memmap must not be changed), covering all
memory areas that are at that point shared between host and guest
(regardless of transfer direction).
Normally, Map allocates the bounce buffer internally, and Unmap releases
the bounce buffer internally (for BusMasterRead and BusMasterWrite),
which is not right here. If you use AllocateBuffer() separately, then
Map() -- with BusMasterCommonBuffer -- will not allocate internally, and
Unmap() will also not deallocate internally. So, in the
ExitBootServices() callback, you will be able to call Unmap() only, and
then *not* call FreeBuffer() at all.
This is why I suggest introducing all four functions (Allocate, Free,
Map, Unmap) to the VIRTIO_DEVICE_PROTOCOL, and why all virtio drivers
should use all four functions explicitly, not just Map and Unmap.
... Actually, I think there is a bug in
"OvmfPkg/IoMmuDxe/AmdSevIoMmu.c". You have the following distribution of
operations at the moment:
- IoMmuAllocateBuffer() allocates pages and clears the memory encryption
mask
- IoMmuFreeBuffer() re-sets the memory encryption mask, and deallocates
pages
- IoMmuMap() does nothing at all when BusMasterCommonBuffer is requested
(and IoMmuAllocateBuffer() was called previously). Otherwise,
IoMmuMap() allocates pages, allocates MAP_INFO, and clears the memory
encryption mask.
- IoMmuUnmap() does nothing when cleaning up a BusMasterCommonBuffer
operation (--> NO_MAPPING). Otherwise, IoMmuUnmap() clears the
encryption mask, and frees both the pages and MAP_INFO.
This distribution of operations seems wrong. The key point is that
AllocateBuffer() *need not* result in a buffer that is immediately
usable, and that client code is required to call Map()
*unconditionally*, even if BusMasterCommonBuffer is the desired
operation. Therefore, the right distribution of operations is:
- IoMmuAllocateBuffer() allocates pages and does not touch the
encryption mask..
- IoMmuFreeBuffer() deallocates pages and does not touch the encryption
mask.
- IoMmuMap() does not allocate pages when BusMasterCommonBuffer is
requested, and it allocates pages (bounce buffer) otherwise.
*Regardless* of BusMaster operation, the following actions are carried
out unconditionally:
. the memory encryption mask is cleared in this function (and in this
function only),
. An attempt is made to grab a MAP_INFO structure from an internal
free list (to be introduced!). The head of the list is a new static
variable. If the free list is empty, then a MAP_INFO structure is
allocated with AllocatePool(). The NO_MAPPING macro becomes unused
and can be deleted from the source code.
- IoMmuUnmap() clears the encryption mask unconditionally. (For this, it
has to consult the MAP_INFO structure that is being passed in from the
caller.) In addition:
. If MapInfo->Operation is BusMasterCommonBuffer, then we know the
allocation was done separately in AllocateBuffer, so we do not
release the pages. Otherwise, we do release the pages.
. MapInfo is linked back on the internal free list (see above). It is
*never* released with FreePool().
This approach guarantees that IoMmuUnmap() can de-program the IOMMU (=
re-set the memory encryption mask) without changing the UEFI memory
map. (I trust that MemEncryptSevSetPageEncMask() will not split page
tables internally when it *re*sets the encryption mask -- is that
correct?)
I'm sorry that I didn't catch this earlier in your commit f9d129e68a45
("OvmfPkg: Add IoMmuDxe driver", 2017-07-06), but as you see, I gave you
only an Acked-by on that, not a Reviewed-by. I've really had to think it
through from the virtio perspective; I didn't foresee this use case back
then (I only felt that I wasn't getting the full picture about the IOMMU
protocol details).
Thanks
Laszlo
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-27 17:16 ` Laszlo Ersek
@ 2017-07-27 17:56 ` Ard Biesheuvel
2017-07-27 19:00 ` Brijesh Singh
0 siblings, 1 reply; 28+ messages in thread
From: Ard Biesheuvel @ 2017-07-27 17:56 UTC (permalink / raw)
To: Laszlo Ersek
Cc: Brijesh Singh, edk2-devel@lists.01.org, Tom Lendacky,
Jordan Justen, Jason Wang, Michael S . Tsirkin, Gerd Hoffmann
On 27 July 2017 at 18:16, Laszlo Ersek <lersek@redhat.com> wrote:
> On 07/27/17 16:21, Brijesh Singh wrote:
>> Hi Laszlo,
>>
>>
>>>
>>> (5.3) All virtio driver code should treat VIRTIO_F_IOMMU_PLATFORM
>>> simply in parallel with VIRTIO_F_VERSION_1, and don't act upon
>>> VIRTIO_F_IOMMU_PLATFORM in any special shape or form. So basically
>>> this is just my point (3) from above.
>>>
>>>
>>> (5.4) There are three VIRTIO_DEVICE_PROTOCOL implementations in edk2:
>>>
>>> - "OvmfPkg/VirtioPciDeviceDxe" binds legacy-only and transitional
>>> virtio-pci devices, and offers virtio 0.9.5 semantics.
>>>
>>> - "ArmVirtPkg/VirtioFdtDxe" (via
>>> "OvmfPkg/Library/VirtioMmioDeviceLib") binds virtio-mmio devices,
>>> and offers virtio 0.9.5 semantics.
>>>
>>> - "OvmfPkg/Virtio10Dxe" binds modern-only virtio-pci devices, and
>>> offers virtio 1.0.0 semantics.
>>>
>>> The first two drivers should implement the AllocateSharedPages() and
>>> FreeSharedPages() member functions simply with the corresponding
>>> MemoryAllocationLib functions (using BootServicesData type memory),
>>> and implement the MapSharedPages() and UnmapSharedPages() member
>>> functions as no-ops (return the input addresses transparently).
>>>
>>> The third driver should implement all four new member functions by
>>> respectively delegating the job to:
>>> - EFI_PCI_IO_PROTOCOL.AllocateBuffer() -- with BootServicesData --
>>> - EFI_PCI_IO_PROTOCOL.FreeBuffer()
>>> - EFI_PCI_IO_PROTOCOL.Map() -- with BusMasterCommonBuffer64 --
>>> - EFI_PCI_IO_PROTOCOL.Unmap()
>>>
>>
>> I have working to implement patch per your recommendation. I assume
>> you mean map the buffers with EfiPciIoOperationBusMasterCommonBuffer
>> [1].
>
> Yes.
>
>> If so, I see one issue with SEV guest. When SEV is active, IOMMU
>> driver uses a bounce buffer to map host address to a device address.
>> While creating bounce buffer we can map it either for
>> EfiPciIoOperationBusMasterRead or EfiPciIoOperationBusMasterWrite
>> Operation. If caller wants to map
>> EfiPciIoOperationBusMasterCommonBuffer then it must allocate the
>> buffer using EFI_PCI_IO_PROTOCOL.AllocateBuffer() [2] otherwise we
>> will fail to map.
>
> Correct.
>
>> I see that PciRootBridgeIo.c has similar check when using a bounce
>> buffer for < 4GB use cases [3].
>
> Correct.
>
>> Do you see any issue if we use EfiPciIoOperationBusMasterRead or
>> EfiPciIoOperationBusMasterWrite instead of
>> EfiPciIoOperationBusMasterCommonBuffer ?
>
> Yes, I do. The BusMasterRead and BusMasterWrite operations are suitable
> for one-shot, uni-directional transfers, where the bounce buffer
> contents and the client code contents are exchanged on Map/Unmap.
>
> However virtio transfers, generally speaking, are not like this. While
> the individual memory areas pointed-to by separate virtio descriptors
> can me associated with one specific transfer direction (see the
> VRING_DESC_F_WRITE flag), the virtio ring area itself is long-lived, and
> it is simultaneously read and written by both host and guest,
> asynchronously and repeatedly. This calls for BusMasterCommonBuffer.
> Once we implement BusMasterCommonBuffer, we can use it for everything
> else.
>
>
> Another reason why separate allocation and mapping (and conversely,
> separate unmapping and deallocation) are required is the
> ExitBootServices() callbacks. In that callback, unmapping must happen
> *without* memory allocation or deallocation (because the IOMMU must be
> de-programmed, but the UEFI memmap must not be changed), covering all
> memory areas that are at that point shared between host and guest
> (regardless of transfer direction).
>
> Normally, Map allocates the bounce buffer internally, and Unmap releases
> the bounce buffer internally (for BusMasterRead and BusMasterWrite),
> which is not right here. If you use AllocateBuffer() separately, then
> Map() -- with BusMasterCommonBuffer -- will not allocate internally, and
> Unmap() will also not deallocate internally. So, in the
> ExitBootServices() callback, you will be able to call Unmap() only, and
> then *not* call FreeBuffer() at all.
>
> This is why I suggest introducing all four functions (Allocate, Free,
> Map, Unmap) to the VIRTIO_DEVICE_PROTOCOL, and why all virtio drivers
> should use all four functions explicitly, not just Map and Unmap.
>
> ... Actually, I think there is a bug in
> "OvmfPkg/IoMmuDxe/AmdSevIoMmu.c". You have the following distribution of
> operations at the moment:
>
> - IoMmuAllocateBuffer() allocates pages and clears the memory encryption
> mask
>
> - IoMmuFreeBuffer() re-sets the memory encryption mask, and deallocates
> pages
>
> - IoMmuMap() does nothing at all when BusMasterCommonBuffer is requested
> (and IoMmuAllocateBuffer() was called previously). Otherwise,
> IoMmuMap() allocates pages, allocates MAP_INFO, and clears the memory
> encryption mask.
>
> - IoMmuUnmap() does nothing when cleaning up a BusMasterCommonBuffer
> operation (--> NO_MAPPING). Otherwise, IoMmuUnmap() clears the
> encryption mask, and frees both the pages and MAP_INFO.
>
> This distribution of operations seems wrong. The key point is that
> AllocateBuffer() *need not* result in a buffer that is immediately
> usable, and that client code is required to call Map()
> *unconditionally*, even if BusMasterCommonBuffer is the desired
> operation. Therefore, the right distribution of operations is:
>
> - IoMmuAllocateBuffer() allocates pages and does not touch the
> encryption mask..
>
> - IoMmuFreeBuffer() deallocates pages and does not touch the encryption
> mask.
>
> - IoMmuMap() does not allocate pages when BusMasterCommonBuffer is
> requested, and it allocates pages (bounce buffer) otherwise.
>
> *Regardless* of BusMaster operation, the following actions are carried
> out unconditionally:
>
> . the memory encryption mask is cleared in this function (and in this
> function only),
>
> . An attempt is made to grab a MAP_INFO structure from an internal
> free list (to be introduced!). The head of the list is a new static
> variable. If the free list is empty, then a MAP_INFO structure is
> allocated with AllocatePool(). The NO_MAPPING macro becomes unused
> and can be deleted from the source code.
>
> - IoMmuUnmap() clears the encryption mask unconditionally. (For this, it
> has to consult the MAP_INFO structure that is being passed in from the
> caller.) In addition:
>
> . If MapInfo->Operation is BusMasterCommonBuffer, then we know the
> allocation was done separately in AllocateBuffer, so we do not
> release the pages. Otherwise, we do release the pages.
>
> . MapInfo is linked back on the internal free list (see above). It is
> *never* released with FreePool().
>
> This approach guarantees that IoMmuUnmap() can de-program the IOMMU (=
> re-set the memory encryption mask) without changing the UEFI memory
> map. (I trust that MemEncryptSevSetPageEncMask() will not split page
> tables internally when it *re*sets the encryption mask -- is that
> correct?)
>
> I'm sorry that I didn't catch this earlier in your commit f9d129e68a45
> ("OvmfPkg: Add IoMmuDxe driver", 2017-07-06), but as you see, I gave you
> only an Acked-by on that, not a Reviewed-by. I've really had to think it
> through from the virtio perspective; I didn't foresee this use case back
> then (I only felt that I wasn't getting the full picture about the IOMMU
> protocol details).
>
I have to concur with Laszlo here: the respective semantics of the
allocate/map/unmap/free operations should be identical to their
counterparts in the PCI I/O protocol, and in the DmaLib in
EmbeddedPkg.
Note that this is likely to cause problems with proprietary x86
drivers in option ROMs, which are used to getting away with using host
addresses for DMA, and fail to invoke Map() for common buffers (or
fail to invoke AllocateBuffer() altogether). The API is clear, and
drivers that abide by the rules should operate correctly even when
using encrypted memory or non-1:1 mapping between the host and PCI
address space (which is how I ran into these issues)
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-27 17:56 ` Ard Biesheuvel
@ 2017-07-27 19:00 ` Brijesh Singh
2017-07-27 20:55 ` Brijesh Singh
2017-07-28 13:38 ` Laszlo Ersek
0 siblings, 2 replies; 28+ messages in thread
From: Brijesh Singh @ 2017-07-27 19:00 UTC (permalink / raw)
To: Ard Biesheuvel, Laszlo Ersek
Cc: brijesh.singh, edk2-devel@lists.01.org, Tom Lendacky,
Jordan Justen, Jason Wang, Michael S . Tsirkin, Gerd Hoffmann
On 07/27/2017 12:56 PM, Ard Biesheuvel wrote:
> On 27 July 2017 at 18:16, Laszlo Ersek <lersek@redhat.com> wrote:
>> On 07/27/17 16:21, Brijesh Singh wrote:
>>> Hi Laszlo,
>>>
>>>
>>>>
>>>> (5.3) All virtio driver code should treat VIRTIO_F_IOMMU_PLATFORM
>>>> simply in parallel with VIRTIO_F_VERSION_1, and don't act upon
>>>> VIRTIO_F_IOMMU_PLATFORM in any special shape or form. So basically
>>>> this is just my point (3) from above.
>>>>
>>>>
>>>> (5.4) There are three VIRTIO_DEVICE_PROTOCOL implementations in edk2:
>>>>
>>>> - "OvmfPkg/VirtioPciDeviceDxe" binds legacy-only and transitional
>>>> virtio-pci devices, and offers virtio 0.9.5 semantics.
>>>>
>>>> - "ArmVirtPkg/VirtioFdtDxe" (via
>>>> "OvmfPkg/Library/VirtioMmioDeviceLib") binds virtio-mmio devices,
>>>> and offers virtio 0.9.5 semantics.
>>>>
>>>> - "OvmfPkg/Virtio10Dxe" binds modern-only virtio-pci devices, and
>>>> offers virtio 1.0.0 semantics.
>>>>
>>>> The first two drivers should implement the AllocateSharedPages() and
>>>> FreeSharedPages() member functions simply with the corresponding
>>>> MemoryAllocationLib functions (using BootServicesData type memory),
>>>> and implement the MapSharedPages() and UnmapSharedPages() member
>>>> functions as no-ops (return the input addresses transparently).
>>>>
>>>> The third driver should implement all four new member functions by
>>>> respectively delegating the job to:
>>>> - EFI_PCI_IO_PROTOCOL.AllocateBuffer() -- with BootServicesData --
>>>> - EFI_PCI_IO_PROTOCOL.FreeBuffer()
>>>> - EFI_PCI_IO_PROTOCOL.Map() -- with BusMasterCommonBuffer64 --
>>>> - EFI_PCI_IO_PROTOCOL.Unmap()
>>>>
>>>
>>> I have working to implement patch per your recommendation. I assume
>>> you mean map the buffers with EfiPciIoOperationBusMasterCommonBuffer
>>> [1].
>>
>> Yes.
>>
>>> If so, I see one issue with SEV guest. When SEV is active, IOMMU
>>> driver uses a bounce buffer to map host address to a device address.
>>> While creating bounce buffer we can map it either for
>>> EfiPciIoOperationBusMasterRead or EfiPciIoOperationBusMasterWrite
>>> Operation. If caller wants to map
>>> EfiPciIoOperationBusMasterCommonBuffer then it must allocate the
>>> buffer using EFI_PCI_IO_PROTOCOL.AllocateBuffer() [2] otherwise we
>>> will fail to map.
>>
>> Correct.
>>
>>> I see that PciRootBridgeIo.c has similar check when using a bounce
>>> buffer for < 4GB use cases [3].
>>
>> Correct.
>>
>>> Do you see any issue if we use EfiPciIoOperationBusMasterRead or
>>> EfiPciIoOperationBusMasterWrite instead of
>>> EfiPciIoOperationBusMasterCommonBuffer ?
>>
>> Yes, I do. The BusMasterRead and BusMasterWrite operations are suitable
>> for one-shot, uni-directional transfers, where the bounce buffer
>> contents and the client code contents are exchanged on Map/Unmap.
>>
>> However virtio transfers, generally speaking, are not like this. While
>> the individual memory areas pointed-to by separate virtio descriptors
>> can me associated with one specific transfer direction (see the
>> VRING_DESC_F_WRITE flag), the virtio ring area itself is long-lived, and
>> it is simultaneously read and written by both host and guest,
>> asynchronously and repeatedly. This calls for BusMasterCommonBuffer.
>> Once we implement BusMasterCommonBuffer, we can use it for everything
>> else.
>>
>>
>> Another reason why separate allocation and mapping (and conversely,
>> separate unmapping and deallocation) are required is the
>> ExitBootServices() callbacks. In that callback, unmapping must happen
>> *without* memory allocation or deallocation (because the IOMMU must be
>> de-programmed, but the UEFI memmap must not be changed), covering all
>> memory areas that are at that point shared between host and guest
>> (regardless of transfer direction).
>>
>> Normally, Map allocates the bounce buffer internally, and Unmap releases
>> the bounce buffer internally (for BusMasterRead and BusMasterWrite),
>> which is not right here. If you use AllocateBuffer() separately, then
>> Map() -- with BusMasterCommonBuffer -- will not allocate internally, and
>> Unmap() will also not deallocate internally. So, in the
>> ExitBootServices() callback, you will be able to call Unmap() only, and
>> then *not* call FreeBuffer() at all.
>>
>> This is why I suggest introducing all four functions (Allocate, Free,
>> Map, Unmap) to the VIRTIO_DEVICE_PROTOCOL, and why all virtio drivers
>> should use all four functions explicitly, not just Map and Unmap.
>>
>> ... Actually, I think there is a bug in
>> "OvmfPkg/IoMmuDxe/AmdSevIoMmu.c". You have the following distribution of
>> operations at the moment:
>>
>> - IoMmuAllocateBuffer() allocates pages and clears the memory encryption
>> mask
>>
>> - IoMmuFreeBuffer() re-sets the memory encryption mask, and deallocates
>> pages
>>
>> - IoMmuMap() does nothing at all when BusMasterCommonBuffer is requested
>> (and IoMmuAllocateBuffer() was called previously). Otherwise,
>> IoMmuMap() allocates pages, allocates MAP_INFO, and clears the memory
>> encryption mask.
>>
>> - IoMmuUnmap() does nothing when cleaning up a BusMasterCommonBuffer
>> operation (--> NO_MAPPING). Otherwise, IoMmuUnmap() clears the
>> encryption mask, and frees both the pages and MAP_INFO.
>>
I agree with you, there is a bug in AmdSevIoMmu.c. I will fix it. And introduce
list to track if the buffer was allocated by us. If buffer was allocated by
us then we can safely say that it does not require a bounce buffer. While
implementing the initial code I was not able to see BusMasterCommonBuffer
usage. But with virtio things are getting a bit more clear in my mind.
>> This distribution of operations seems wrong. The key point is that
>> AllocateBuffer() *need not* result in a buffer that is immediately
>> usable, and that client code is required to call Map()
>> *unconditionally*, even if BusMasterCommonBuffer is the desired
>> operation. Therefore, the right distribution of operations is:
>>
>> - IoMmuAllocateBuffer() allocates pages and does not touch the
>> encryption mask..
>>
>> - IoMmuFreeBuffer() deallocates pages and does not touch the encryption
>> mask.
>>
Actually one of main reason why we cleared and restored the memory encryption mask
during allocate/free is because we also consume the IOMMU protocol in QemuFwCfgLib
as a method to allocate and free a DMA buffer. I am certainly open to suggestions.
[1] https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L159
[2] https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L197
>> - IoMmuMap() does not allocate pages when BusMasterCommonBuffer is
>> requested, and it allocates pages (bounce buffer) otherwise.
>>
I am trying to wrap my head around how we can support BusMasterCommonBuffer
when buffer was not allocated by us. Changing the memory encryption mask in
a page table will not update the contents. Also since the memory encryption
mask works on PAGE_SIZE hence changing the encryption mask on not our allocated
buffer could mess things up (e.g if NumberOfBytes is not PAGE_SIZE aligned).
>> *Regardless* of BusMaster operation, the following actions are carried
>> out unconditionally:
>>
>> . the memory encryption mask is cleared in this function (and in this
>> function only),
>>
>> . An attempt is made to grab a MAP_INFO structure from an internal
>> free list (to be introduced!). The head of the list is a new static
>> variable. If the free list is empty, then a MAP_INFO structure is
>> allocated with AllocatePool(). The NO_MAPPING macro becomes unused
>> and can be deleted from the source code.
>>
>> - IoMmuUnmap() clears the encryption mask unconditionally. (For this, it
>> has to consult the MAP_INFO structure that is being passed in from the
>> caller.) In addition:
>>
>> . If MapInfo->Operation is BusMasterCommonBuffer, then we know the
>> allocation was done separately in AllocateBuffer, so we do not
>> release the pages. Otherwise, we do release the pages.
>>
>> . MapInfo is linked back on the internal free list (see above). It is
>> *never* released with FreePool().
>>
>> This approach guarantees that IoMmuUnmap() can de-program the IOMMU (=
>> re-set the memory encryption mask) without changing the UEFI memory
>> map. (I trust that MemEncryptSevSetPageEncMask() will not split page
>> tables internally when it *re*sets the encryption mask -- is that
>> correct?)
Yes, MemEncryptSevSetPageEncMask() will not split pages when it restores mask.
>>>> I'm sorry that I didn't catch this earlier in your commit f9d129e68a45
>> ("OvmfPkg: Add IoMmuDxe driver", 2017-07-06), but as you see, I gave you
>> only an Acked-by on that, not a Reviewed-by. I've really had to think it
>> through from the virtio perspective; I didn't foresee this use case back
>> then (I only felt that I wasn't getting the full picture about the IOMMU
>> protocol details).
>>
>
> I have to concur with Laszlo here: the respective semantics of the
> allocate/map/unmap/free operations should be identical to their
> counterparts in the PCI I/O protocol, and in the DmaLib in
> EmbeddedPkg.
>
> Note that this is likely to cause problems with proprietary x86
> drivers in option ROMs, which are used to getting away with using host
> addresses for DMA, and fail to invoke Map() for common buffers (or
> fail to invoke AllocateBuffer() altogether). The API is clear, and
> drivers that abide by the rules should operate correctly even when
> using encrypted memory or non-1:1 mapping between the host and PCI
> address space (which is how I ran into these issues)
>
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-27 19:00 ` Brijesh Singh
@ 2017-07-27 20:55 ` Brijesh Singh
2017-07-27 21:31 ` Ard Biesheuvel
2017-07-28 13:38 ` Laszlo Ersek
1 sibling, 1 reply; 28+ messages in thread
From: Brijesh Singh @ 2017-07-27 20:55 UTC (permalink / raw)
To: Ard Biesheuvel, Laszlo Ersek
Cc: brijesh.singh, edk2-devel@lists.01.org, Tom Lendacky,
Jordan Justen, Jason Wang, Michael S . Tsirkin, Gerd Hoffmann
On 07/27/2017 02:00 PM, Brijesh Singh wrote:
>>> This distribution of operations seems wrong. The key point is that
>>> AllocateBuffer() *need not* result in a buffer that is immediately
>>> usable, and that client code is required to call Map()
>>> *unconditionally*, even if BusMasterCommonBuffer is the desired
>>> operation. Therefore, the right distribution of operations is:
>>>
>>> - IoMmuAllocateBuffer() allocates pages and does not touch the
>>> encryption mask..
>>>
>>> - IoMmuFreeBuffer() deallocates pages and does not touch the encryption
>>> mask.
>>>
>
> Actually one of main reason why we cleared and restored the memory encryption mask
> during allocate/free is because we also consume the IOMMU protocol in QemuFwCfgLib
> as a method to allocate and free a DMA buffer. I am certainly open to suggestions.
>
> [1] https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L159
> [2] https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L197
>
>>> - IoMmuMap() does not allocate pages when BusMasterCommonBuffer is
>>> requested, and it allocates pages (bounce buffer) otherwise.
>>>
>
> I am trying to wrap my head around how we can support BusMasterCommonBuffer
> when buffer was not allocated by us. Changing the memory encryption mask in
> a page table will not update the contents. Also since the memory encryption
> mask works on PAGE_SIZE hence changing the encryption mask on not our allocated
> buffer could mess things up (e.g if NumberOfBytes is not PAGE_SIZE aligned).
>
I may be missing something in my understanding. Here is a flow I have in my
mind, please correct me.
OvmfPkg/VirtIoBlk.c:
VirtioBlkInit()
....
....
VirtioRingInit
Virtio->AllocateSharedPages(RingSize, &Ring->Base)
PciIo->AllocatePages(RingSize, &RingAddress)
Virtio->MapSharedPages(...,BusMasterCommonBuffer, Ring->Base, RingSize, &RingDeviceAddress)
.....
.....
This case is straight forward and we can easily maps. No need for bounce buffering.
VirtioBlkReadBlocks(..., BufferSize, Buffer,)
......
......
SynchronousRequest(..., BufferSize, Buffer)
....
Virtio->MapSharedPages(..., BusMasterCommonBuffer, Buffer, BufferSize, &DeviceAddress)
VirtioAppendDesc(DeviceAddress, BufferSize, ...)
VirtioFlush (...)
In the above case, "Buffer" was not allocated by us hence we will not able to change the
memory encryption attributes. Am I missing something in the flow ?
>>> *Regardless* of BusMaster operation, the following actions are carried
>>> out unconditionally:
>>>
>>> . the memory encryption mask is cleared in this function (and in this
>>> function only),
>>>
>>> . An attempt is made to grab a MAP_INFO structure from an internal
>>> free list (to be introduced!). The head of the list is a new static
>>> variable. If the free list is empty, then a MAP_INFO structure is
>>> allocated with AllocatePool(). The NO_MAPPING macro becomes unused
>>> and can be deleted from the source code.
>>>
>>> - IoMmuUnmap() clears the encryption mask unconditionally. (For this, it
>>> has to consult the MAP_INFO structure that is being passed in from the
>>> caller.) In addition:
>>>
>>> . If MapInfo->Operation is BusMasterCommonBuffer, then we know the
>>> allocation was done separately in AllocateBuffer, so we do not
>>> release the pages. Otherwise, we do release the pages.
>>>
>>> . MapInfo is linked back on the internal free list (see above). It is
>>> *never* released with FreePool().
>>>
>>> This approach guarantees that IoMmuUnmap() can de-program the IOMMU (=
>>> re-set the memory encryption mask) without changing the UEFI memory
>>> map. (I trust that MemEncryptSevSetPageEncMask() will not split page
>>> tables internally when it *re*sets the encryption mask -- is that
>>> correct?)
>
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-27 20:55 ` Brijesh Singh
@ 2017-07-27 21:31 ` Ard Biesheuvel
2017-07-27 21:38 ` Andrew Fish
2017-07-27 22:10 ` Brijesh Singh
0 siblings, 2 replies; 28+ messages in thread
From: Ard Biesheuvel @ 2017-07-27 21:31 UTC (permalink / raw)
To: Brijesh Singh
Cc: Laszlo Ersek, edk2-devel@lists.01.org, Tom Lendacky,
Jordan Justen, Jason Wang, Michael S . Tsirkin, Gerd Hoffmann
> On 27 Jul 2017, at 21:55, Brijesh Singh <brijesh.singh@amd.com> wrote:
>
>
>
> On 07/27/2017 02:00 PM, Brijesh Singh wrote:
>
>>>> This distribution of operations seems wrong. The key point is that
>>>> AllocateBuffer() *need not* result in a buffer that is immediately
>>>> usable, and that client code is required to call Map()
>>>> *unconditionally*, even if BusMasterCommonBuffer is the desired
>>>> operation. Therefore, the right distribution of operations is:
>>>>
>>>> - IoMmuAllocateBuffer() allocates pages and does not touch the
>>>> encryption mask..
>>>>
>>>> - IoMmuFreeBuffer() deallocates pages and does not touch the encryption
>>>> mask.
>>>>
>> Actually one of main reason why we cleared and restored the memory encryption mask
>> during allocate/free is because we also consume the IOMMU protocol in QemuFwCfgLib
>> as a method to allocate and free a DMA buffer. I am certainly open to suggestions.
>> [1] https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L159
>> [2] https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L197
>>>> - IoMmuMap() does not allocate pages when BusMasterCommonBuffer is
>>>> requested, and it allocates pages (bounce buffer) otherwise.
>>>>
>> I am trying to wrap my head around how we can support BusMasterCommonBuffer
>> when buffer was not allocated by us. Changing the memory encryption mask in
>> a page table will not update the contents. Also since the memory encryption
>> mask works on PAGE_SIZE hence changing the encryption mask on not our allocated
>> buffer could mess things up (e.g if NumberOfBytes is not PAGE_SIZE aligned).
>
> I may be missing something in my understanding. Here is a flow I have in my
> mind, please correct me.
>
> OvmfPkg/VirtIoBlk.c:
>
> VirtioBlkInit()
> ....
> ....
> VirtioRingInit
> Virtio->AllocateSharedPages(RingSize, &Ring->Base)
> PciIo->AllocatePages(RingSize, &RingAddress)
> Virtio->MapSharedPages(...,BusMasterCommonBuffer, Ring->Base, RingSize, &RingDeviceAddress)
> .....
> .....
>
> This case is straight forward and we can easily maps. No need for bounce buffering.
>
> VirtioBlkReadBlocks(..., BufferSize, Buffer,)
> ......
> ......
> SynchronousRequest(..., BufferSize, Buffer)
> ....
> Virtio->MapSharedPages(..., BusMasterCommonBuffer, Buffer, BufferSize, &DeviceAddress)
> VirtioAppendDesc(DeviceAddress, BufferSize, ...)
> VirtioFlush (...)
>
> In the above case, "Buffer" was not allocated by us hence we will not able to change the
> memory encryption attributes. Am I missing something in the flow ?
>
Common buffer mappings may only be created from buffers that were allocated by AllocateBuffer(). In fact, that is its main purpose
>
>>>> *Regardless* of BusMaster operation, the following actions are carried
>>>> out unconditionally:
>>>>
>>>> . the memory encryption mask is cleared in this function (and in this
>>>> function only),
>>>>
>>>> . An attempt is made to grab a MAP_INFO structure from an internal
>>>> free list (to be introduced!). The head of the list is a new static
>>>> variable. If the free list is empty, then a MAP_INFO structure is
>>>> allocated with AllocatePool(). The NO_MAPPING macro becomes unused
>>>> and can be deleted from the source code.
>>>>
>>>> - IoMmuUnmap() clears the encryption mask unconditionally. (For this, it
>>>> has to consult the MAP_INFO structure that is being passed in from the
>>>> caller.) In addition:
>>>>
>>>> . If MapInfo->Operation is BusMasterCommonBuffer, then we know the
>>>> allocation was done separately in AllocateBuffer, so we do not
>>>> release the pages. Otherwise, we do release the pages.
>>>>
>>>> . MapInfo is linked back on the internal free list (see above). It is
>>>> *never* released with FreePool().
>>>>
>>>> This approach guarantees that IoMmuUnmap() can de-program the IOMMU (=
>>>> re-set the memory encryption mask) without changing the UEFI memory
>>>> map. (I trust that MemEncryptSevSetPageEncMask() will not split page
>>>> tables internally when it *re*sets the encryption mask -- is that
>>>> correct?)
>
>
>
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-27 21:31 ` Ard Biesheuvel
@ 2017-07-27 21:38 ` Andrew Fish
2017-07-27 22:13 ` Brijesh Singh
2017-07-27 22:10 ` Brijesh Singh
1 sibling, 1 reply; 28+ messages in thread
From: Andrew Fish @ 2017-07-27 21:38 UTC (permalink / raw)
To: Brijesh Singh
Cc: Tom Lendacky, Michael S . Tsirkin, Jordan Justen,
edk2-devel@lists.01.org, Laszlo Ersek, Jason Wang, Ard Biesheuvel
> On Jul 27, 2017, at 2:31 PM, Ard Biesheuvel <ard.biesheuvel@linaro.org> wrote:
>
>>
>> On 27 Jul 2017, at 21:55, Brijesh Singh <brijesh.singh@amd.com> wrote:
>>
>>
>>
>> On 07/27/2017 02:00 PM, Brijesh Singh wrote:
>>
>>>>> This distribution of operations seems wrong. The key point is that
>>>>> AllocateBuffer() *need not* result in a buffer that is immediately
>>>>> usable, and that client code is required to call Map()
>>>>> *unconditionally*, even if BusMasterCommonBuffer is the desired
>>>>> operation. Therefore, the right distribution of operations is:
>>>>>
>>>>> - IoMmuAllocateBuffer() allocates pages and does not touch the
>>>>> encryption mask..
>>>>>
>>>>> - IoMmuFreeBuffer() deallocates pages and does not touch the encryption
>>>>> mask.
>>>>>
>>> Actually one of main reason why we cleared and restored the memory encryption mask
>>> during allocate/free is because we also consume the IOMMU protocol in QemuFwCfgLib
>>> as a method to allocate and free a DMA buffer. I am certainly open to suggestions.
>>> [1] https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L159
>>> [2] https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L197
>>>>> - IoMmuMap() does not allocate pages when BusMasterCommonBuffer is
>>>>> requested, and it allocates pages (bounce buffer) otherwise.
>>>>>
>>> I am trying to wrap my head around how we can support BusMasterCommonBuffer
>>> when buffer was not allocated by us. Changing the memory encryption mask in
>>> a page table will not update the contents. Also since the memory encryption
>>> mask works on PAGE_SIZE hence changing the encryption mask on not our allocated
>>> buffer could mess things up (e.g if NumberOfBytes is not PAGE_SIZE aligned).
>>
>> I may be missing something in my understanding. Here is a flow I have in my
>> mind, please correct me.
>>
>> OvmfPkg/VirtIoBlk.c:
>>
>> VirtioBlkInit()
>> ....
>> ....
>> VirtioRingInit
>> Virtio->AllocateSharedPages(RingSize, &Ring->Base)
>> PciIo->AllocatePages(RingSize, &RingAddress)
>> Virtio->MapSharedPages(...,BusMasterCommonBuffer, Ring->Base, RingSize, &RingDeviceAddress)
>> .....
>> .....
>>
>> This case is straight forward and we can easily maps. No need for bounce buffering.
>>
>> VirtioBlkReadBlocks(..., BufferSize, Buffer,)
>> ......
>> ......
>> SynchronousRequest(..., BufferSize, Buffer)
>> ....
>> Virtio->MapSharedPages(..., BusMasterCommonBuffer, Buffer, BufferSize, &DeviceAddress)
>> VirtioAppendDesc(DeviceAddress, BufferSize, ...)
>> VirtioFlush (...)
>>
>> In the above case, "Buffer" was not allocated by us hence we will not able to change the
>> memory encryption attributes. Am I missing something in the flow ?
>>
>
>
> Common buffer mappings may only be created from buffers that were allocated by AllocateBuffer(). In fact, that is its main purpose
Brijesh,
If you look in the UEFI Spec 13.4 EFI PCI I/O Protocol there is a good write on DMA.
DMA Bus Master Read Operation
=========================
Call Map() for EfiPciIoOperationBusMasterRead.
Program the DMA Bus Master with the DeviceAddress returned by Map(). Start the DMA Bus Master.
Wait for DMA Bus Master to complete the read operation.
Call Unmap().
DMA Bus Master Write Operation
==========================
Call Map() for EfiPciOperationBusMasterWrite.
Program the DMA Bus Master with the DeviceAddress returned by Map().
Start the DMA Bus Master.
Wait for DMA Bus Master to complete the write operation.
Perform a PCI controller specific read transaction to flush all PCI write buffers (See PCI Specification Section 3.2.5.2) .
Call Flush().
Call Unmap().
DMA Bus Master Common Buffer Operation
==================================
Call AllocateBuffer() to allocate a common buffer.
Call Map() for EfiPciIoOperationBusMasterCommonBuffer.
Program the DMA Bus Master with the DeviceAddress returned by Map().
The common buffer can now be accessed equally by the processor and the DMA bus master. Call Unmap().
Call FreeBuffer().
Thanks,
Andrew Fish
>>
>>>>> *Regardless* of BusMaster operation, the following actions are carried
>>>>> out unconditionally:
>>>>>
>>>>> . the memory encryption mask is cleared in this function (and in this
>>>>> function only),
>>>>>
>>>>> . An attempt is made to grab a MAP_INFO structure from an internal
>>>>> free list (to be introduced!). The head of the list is a new static
>>>>> variable. If the free list is empty, then a MAP_INFO structure is
>>>>> allocated with AllocatePool(). The NO_MAPPING macro becomes unused
>>>>> and can be deleted from the source code.
>>>>>
>>>>> - IoMmuUnmap() clears the encryption mask unconditionally. (For this, it
>>>>> has to consult the MAP_INFO structure that is being passed in from the
>>>>> caller.) In addition:
>>>>>
>>>>> . If MapInfo->Operation is BusMasterCommonBuffer, then we know the
>>>>> allocation was done separately in AllocateBuffer, so we do not
>>>>> release the pages. Otherwise, we do release the pages.
>>>>>
>>>>> . MapInfo is linked back on the internal free list (see above). It is
>>>>> *never* released with FreePool().
>>>>>
>>>>> This approach guarantees that IoMmuUnmap() can de-program the IOMMU (=
>>>>> re-set the memory encryption mask) without changing the UEFI memory
>>>>> map. (I trust that MemEncryptSevSetPageEncMask() will not split page
>>>>> tables internally when it *re*sets the encryption mask -- is that
>>>>> correct?)
>>
>>
>>
> _______________________________________________
> edk2-devel mailing list
> edk2-devel@lists.01.org
> https://lists.01.org/mailman/listinfo/edk2-devel
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-27 21:31 ` Ard Biesheuvel
2017-07-27 21:38 ` Andrew Fish
@ 2017-07-27 22:10 ` Brijesh Singh
2017-07-28 8:39 ` Ard Biesheuvel
1 sibling, 1 reply; 28+ messages in thread
From: Brijesh Singh @ 2017-07-27 22:10 UTC (permalink / raw)
To: Ard Biesheuvel
Cc: brijesh.singh, Laszlo Ersek, edk2-devel@lists.01.org,
Tom Lendacky, Jordan Justen, Jason Wang, Michael S . Tsirkin,
Gerd Hoffmann
On 07/27/2017 04:31 PM, Ard Biesheuvel wrote:
>
>> On 27 Jul 2017, at 21:55, Brijesh Singh <brijesh.singh@amd.com> wrote:
>>
>>
>>
>> On 07/27/2017 02:00 PM, Brijesh Singh wrote:
>>
>>>>> This distribution of operations seems wrong. The key point is that
>>>>> AllocateBuffer() *need not* result in a buffer that is immediately
>>>>> usable, and that client code is required to call Map()
>>>>> *unconditionally*, even if BusMasterCommonBuffer is the desired
>>>>> operation. Therefore, the right distribution of operations is:
>>>>>
>>>>> - IoMmuAllocateBuffer() allocates pages and does not touch the
>>>>> encryption mask..
>>>>>
>>>>> - IoMmuFreeBuffer() deallocates pages and does not touch the encryption
>>>>> mask.
>>>>>
>>> Actually one of main reason why we cleared and restored the memory encryption mask
>>> during allocate/free is because we also consume the IOMMU protocol in QemuFwCfgLib
>>> as a method to allocate and free a DMA buffer. I am certainly open to suggestions.
>>> [1] https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L159
>>> [2] https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L197
>>>>> - IoMmuMap() does not allocate pages when BusMasterCommonBuffer is
>>>>> requested, and it allocates pages (bounce buffer) otherwise.
>>>>>
>>> I am trying to wrap my head around how we can support BusMasterCommonBuffer
>>> when buffer was not allocated by us. Changing the memory encryption mask in
>>> a page table will not update the contents. Also since the memory encryption
>>> mask works on PAGE_SIZE hence changing the encryption mask on not our allocated
>>> buffer could mess things up (e.g if NumberOfBytes is not PAGE_SIZE aligned).
>>
>> I may be missing something in my understanding. Here is a flow I have in my
>> mind, please correct me.
>>
>> OvmfPkg/VirtIoBlk.c:
>>
>> VirtioBlkInit()
>> ....
>> ....
>> VirtioRingInit
>> Virtio->AllocateSharedPages(RingSize, &Ring->Base)
>> PciIo->AllocatePages(RingSize, &RingAddress)
>> Virtio->MapSharedPages(...,BusMasterCommonBuffer, Ring->Base, RingSize, &RingDeviceAddress)
>> .....
>> .....
>>
>> This case is straight forward and we can easily maps. No need for bounce buffering.
>>
>> VirtioBlkReadBlocks(..., BufferSize, Buffer,)
>> ......
>> ......
>> SynchronousRequest(..., BufferSize, Buffer)
>> ....
>> Virtio->MapSharedPages(..., BusMasterCommonBuffer, Buffer, BufferSize, &DeviceAddress)
>> VirtioAppendDesc(DeviceAddress, BufferSize, ...)
>> VirtioFlush (...)
>>
>> In the above case, "Buffer" was not allocated by us hence we will not able to change the
>> memory encryption attributes. Am I missing something in the flow ?
>>
>
>
> Common buffer mappings may only be created from buffers that were allocated by AllocateBuffer(). In fact, that is its main purpose
Yes, that part is well understood. If the buffer was allocated by us (e.g vring, request/status
structure etc) then those should be mapped as "BusMasterCommonBuffer".
But I am trying to figure out, how to map a data buffers before issuing a virtio request. e.g when
VirtioBlkReadBlocks() is called, "Buffer" pointer is not a DMA address hence we need to map it.
I think it should be mapped using "BusMasterWrite" not "BusMasterCommonBuffer" before adding into vring.
>>
>>>>> *Regardless* of BusMaster operation, the following actions are carried
>>>>> out unconditionally:
>>>>>
>>>>> . the memory encryption mask is cleared in this function (and in this
>>>>> function only),
>>>>>
>>>>> . An attempt is made to grab a MAP_INFO structure from an internal
>>>>> free list (to be introduced!). The head of the list is a new static
>>>>> variable. If the free list is empty, then a MAP_INFO structure is
>>>>> allocated with AllocatePool(). The NO_MAPPING macro becomes unused
>>>>> and can be deleted from the source code.
>>>>>
>>>>> - IoMmuUnmap() clears the encryption mask unconditionally. (For this, it
>>>>> has to consult the MAP_INFO structure that is being passed in from the
>>>>> caller.) In addition:
>>>>>
>>>>> . If MapInfo->Operation is BusMasterCommonBuffer, then we know the
>>>>> allocation was done separately in AllocateBuffer, so we do not
>>>>> release the pages. Otherwise, we do release the pages.
>>>>>
>>>>> . MapInfo is linked back on the internal free list (see above). It is
>>>>> *never* released with FreePool().
>>>>>
>>>>> This approach guarantees that IoMmuUnmap() can de-program the IOMMU (=
>>>>> re-set the memory encryption mask) without changing the UEFI memory
>>>>> map. (I trust that MemEncryptSevSetPageEncMask() will not split page
>>>>> tables internally when it *re*sets the encryption mask -- is that
>>>>> correct?)
>>
>>
>>
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-27 21:38 ` Andrew Fish
@ 2017-07-27 22:13 ` Brijesh Singh
0 siblings, 0 replies; 28+ messages in thread
From: Brijesh Singh @ 2017-07-27 22:13 UTC (permalink / raw)
To: Andrew Fish
Cc: brijesh.singh, Tom Lendacky, Michael S . Tsirkin, Jordan Justen,
edk2-devel@lists.01.org, Laszlo Ersek, Jason Wang, Ard Biesheuvel
Hi Andrew,
On 07/27/2017 04:38 PM, Andrew Fish wrote:
>
> Brijesh,
>
> If you look in the UEFI Spec 13.4 EFI PCI I/O Protocol there is a good write on DMA.
>
> DMA Bus Master Read Operation
> =========================
> Call Map() for EfiPciIoOperationBusMasterRead.
> Program the DMA Bus Master with the DeviceAddress returned by Map(). Start the DMA Bus Master.
> Wait for DMA Bus Master to complete the read operation.
> Call Unmap().
>
>
> DMA Bus Master Write Operation
> ==========================
> Call Map() for EfiPciOperationBusMasterWrite.
> Program the DMA Bus Master with the DeviceAddress returned by Map().
> Start the DMA Bus Master.
> Wait for DMA Bus Master to complete the write operation.
> Perform a PCI controller specific read transaction to flush all PCI write buffers (See PCI Specification Section 3.2.5.2) .
> Call Flush().
> Call Unmap().
>
> DMA Bus Master Common Buffer Operation
> ==================================
> Call AllocateBuffer() to allocate a common buffer.
> Call Map() for EfiPciIoOperationBusMasterCommonBuffer.
> Program the DMA Bus Master with the DeviceAddress returned by Map().
> The common buffer can now be accessed equally by the processor and the DMA bus master. Call Unmap().
> Call FreeBuffer().
>
Thanks for the link.
-Brijesh
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-27 22:10 ` Brijesh Singh
@ 2017-07-28 8:39 ` Ard Biesheuvel
2017-07-28 15:27 ` Laszlo Ersek
0 siblings, 1 reply; 28+ messages in thread
From: Ard Biesheuvel @ 2017-07-28 8:39 UTC (permalink / raw)
To: Brijesh Singh
Cc: Laszlo Ersek, edk2-devel@lists.01.org, Tom Lendacky,
Jordan Justen, Jason Wang, Michael S . Tsirkin, Gerd Hoffmann
On 27 July 2017 at 23:10, Brijesh Singh <brijesh.singh@amd.com> wrote:
>
>
> On 07/27/2017 04:31 PM, Ard Biesheuvel wrote:
>>
>>
>>> On 27 Jul 2017, at 21:55, Brijesh Singh <brijesh.singh@amd.com> wrote:
>>>
>>>
>>>
>>> On 07/27/2017 02:00 PM, Brijesh Singh wrote:
>>>
>>>>>> This distribution of operations seems wrong. The key point is that
>>>>>> AllocateBuffer() *need not* result in a buffer that is immediately
>>>>>> usable, and that client code is required to call Map()
>>>>>> *unconditionally*, even if BusMasterCommonBuffer is the desired
>>>>>> operation. Therefore, the right distribution of operations is:
>>>>>>
>>>>>> - IoMmuAllocateBuffer() allocates pages and does not touch the
>>>>>> encryption mask..
>>>>>>
>>>>>> - IoMmuFreeBuffer() deallocates pages and does not touch the
>>>>>> encryption
>>>>>> mask.
>>>>>>
>>>> Actually one of main reason why we cleared and restored the memory
>>>> encryption mask
>>>> during allocate/free is because we also consume the IOMMU protocol in
>>>> QemuFwCfgLib
>>>> as a method to allocate and free a DMA buffer. I am certainly open to
>>>> suggestions.
>>>> [1]
>>>> https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L159
>>>> [2]
>>>> https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L197
>>>>>>
>>>>>> - IoMmuMap() does not allocate pages when BusMasterCommonBuffer is
>>>>>> requested, and it allocates pages (bounce buffer) otherwise.
>>>>>>
>>>> I am trying to wrap my head around how we can support
>>>> BusMasterCommonBuffer
>>>> when buffer was not allocated by us. Changing the memory encryption mask
>>>> in
>>>> a page table will not update the contents. Also since the memory
>>>> encryption
>>>> mask works on PAGE_SIZE hence changing the encryption mask on not our
>>>> allocated
>>>> buffer could mess things up (e.g if NumberOfBytes is not PAGE_SIZE
>>>> aligned).
>>>
>>>
>>> I may be missing something in my understanding. Here is a flow I have in
>>> my
>>> mind, please correct me.
>>>
>>> OvmfPkg/VirtIoBlk.c:
>>>
>>> VirtioBlkInit()
>>> ....
>>> ....
>>> VirtioRingInit
>>> Virtio->AllocateSharedPages(RingSize, &Ring->Base)
>>> PciIo->AllocatePages(RingSize, &RingAddress)
>>> Virtio->MapSharedPages(...,BusMasterCommonBuffer, Ring->Base,
>>> RingSize, &RingDeviceAddress)
>>> .....
>>> .....
>>>
>>> This case is straight forward and we can easily maps. No need for bounce
>>> buffering.
>>>
>>> VirtioBlkReadBlocks(..., BufferSize, Buffer,)
>>> ......
>>> ......
>>> SynchronousRequest(..., BufferSize, Buffer)
>>> ....
>>> Virtio->MapSharedPages(..., BusMasterCommonBuffer, Buffer,
>>> BufferSize, &DeviceAddress)
>>> VirtioAppendDesc(DeviceAddress, BufferSize, ...)
>>> VirtioFlush (...)
>>> In the above case, "Buffer" was not allocated by us hence we will not
>>> able to change the
>>> memory encryption attributes. Am I missing something in the flow ?
>>>
>>
>>
>> Common buffer mappings may only be created from buffers that were
>> allocated by AllocateBuffer(). In fact, that is its main purpose
>
>
> Yes, that part is well understood. If the buffer was allocated by us (e.g
> vring, request/status
> structure etc) then those should be mapped as "BusMasterCommonBuffer".
>
> But I am trying to figure out, how to map a data buffers before issuing a
> virtio request. e.g when
> VirtioBlkReadBlocks() is called, "Buffer" pointer is not a DMA address hence
> we need to map it.
> I think it should be mapped using "BusMasterWrite" not
> "BusMasterCommonBuffer" before adding into vring.
>
If the transfer is strictly unidirectional, then that should work. If
the transfer goes both ways, you may need to map/unmap for read and
then map/unmap for write
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-27 19:00 ` Brijesh Singh
2017-07-27 20:55 ` Brijesh Singh
@ 2017-07-28 13:38 ` Laszlo Ersek
2017-07-28 16:00 ` Brijesh Singh
1 sibling, 1 reply; 28+ messages in thread
From: Laszlo Ersek @ 2017-07-28 13:38 UTC (permalink / raw)
To: Brijesh Singh, Ard Biesheuvel
Cc: edk2-devel@lists.01.org, Tom Lendacky, Jordan Justen, Jason Wang,
Michael S . Tsirkin, Gerd Hoffmann
On 07/27/17 21:00, Brijesh Singh wrote:
> On 07/27/2017 12:56 PM, Ard Biesheuvel wrote:
>> On 27 July 2017 at 18:16, Laszlo Ersek <lersek@redhat.com> wrote:
>>> Normally, Map allocates the bounce buffer internally, and Unmap
>>> releases the bounce buffer internally (for BusMasterRead and
>>> BusMasterWrite), which is not right here. If you use
>>> AllocateBuffer() separately, then Map() -- with
>>> BusMasterCommonBuffer -- will not allocate internally, and Unmap()
>>> will also not deallocate internally. So, in the ExitBootServices()
>>> callback, you will be able to call Unmap() only, and then *not* call
>>> FreeBuffer() at all.
>>>
>>> This is why I suggest introducing all four functions (Allocate,
>>> Free, Map, Unmap) to the VIRTIO_DEVICE_PROTOCOL, and why all virtio
>>> drivers should use all four functions explicitly, not just Map and
>>> Unmap.
>>>
>>> ... Actually, I think there is a bug in
>>> "OvmfPkg/IoMmuDxe/AmdSevIoMmu.c". You have the following
>>> distribution of operations at the moment:
>>>
>>> - IoMmuAllocateBuffer() allocates pages and clears the memory
>>> encryption mask
>>>
>>> - IoMmuFreeBuffer() re-sets the memory encryption mask, and
>>> deallocates pages
>>>
>>> - IoMmuMap() does nothing at all when BusMasterCommonBuffer is
>>> requested (and IoMmuAllocateBuffer() was called previously).
>>> Otherwise, IoMmuMap() allocates pages, allocates MAP_INFO, and
>>> clears the memory encryption mask.
>>>
>>> - IoMmuUnmap() does nothing when cleaning up a BusMasterCommonBuffer
>>> operation (--> NO_MAPPING). Otherwise, IoMmuUnmap() clears the
>>> encryption mask, and frees both the pages and MAP_INFO.
>>>
>
> I agree with you, there is a bug in AmdSevIoMmu.c. I will fix it. And
> introduce list to track if the buffer was allocated by us. If buffer
> was allocated by us then we can safely say that it does not require a
> bounce buffer. While implementing the initial code I was not able to
> see BusMasterCommonBuffer usage. But with virtio things are getting a
> bit more clear in my mind.
The purpose of the internal list is a little bit different -- it is a
"free list", not a tracker list.
Under the proposed scheme, a MAP_INFO structure will have to be
allocated for *all* mappings: both for CommonBuffer (where the actual
pages come from the caller, who retrieved them earlier with an
AllocateBuffer() call), and for Read/Write (where the actual pages will
be allocated internally to Map). Allocating the MAP_INFO structure in
Map() is necessary in *both* cases because the Unmap() function can
*only* consult this MAP_INFO structure to learn the address and the size
at which it has to re-set the memory encryption mask. This is because
the Unmap() function gets no other input parameter.
Then, regardless of the original operation (CommonBuffer vs.
Read/Write), the MAP_INFO structure has to be recycled in Unmap(). (For
CommonBuffer, the actual pages are owned by the caller, so we don't free
them here; for Read/Write, the actual pages are owned by Map/Unmap, so
we free them in Unmap() *in addition* to recycling MAP_INFO.) The main
point is that MAP_INFO cannot be released with FreePool() because that
could change the UEFI memmap during ExitBootServices() processing. So
MAP_INFO has to be "released" to an internal *free* list.
And since we have an internal free list, the original MAP_INFO
allocation in Map() should consult the free list first, and only if it
is empty should it fall back to AllocatePool().
Whether the actual pages tracked by MAP_INFO were allocated internally
by Map(), or externally by the caller (via AllocateBuffer()) is an
independent question. (And it can be seen from the MAP_INFO.Operation
field.)
Does this make it clearer?
>
>>> This distribution of operations seems wrong. The key point is that
>>> AllocateBuffer() *need not* result in a buffer that is immediately
>>> usable, and that client code is required to call Map()
>>> *unconditionally*, even if BusMasterCommonBuffer is the desired
>>> operation. Therefore, the right distribution of operations is:
>>>
>>> - IoMmuAllocateBuffer() allocates pages and does not touch the
>>> encryption mask..
>>>
>>> - IoMmuFreeBuffer() deallocates pages and does not touch the
>>> encryption mask.
>>>
>
> Actually one of main reason why we cleared and restored the memory
> encryption mask during allocate/free is because we also consume the
> IOMMU protocol in QemuFwCfgLib as a method to allocate and free a DMA
> buffer. I am certainly open to suggestions.
Argh. That's again my fault then, I should have noticed it. :( I
apologize, the Map/Unmap/AllocateBuffer/FreeBuffer APIs are a "first"
for me as well.
As discussed earlier (and confirmed by Ard), calling *just*
AllocateBuffer() is never enough, Map()/Unmap() are always necessary.
So here's what I suggest (to keep the changes localized):
- Extend the prototype of InternalQemuFwCfgSevDmaAllocateBuffer()
function to output a "VOID *Mapping" parameter as well, in addition to
the address.
- Modify the prototype of the InternalQemuFwCfgSevDmaFreeBuffer()
function to take a "VOID *Mapping" parameter in addition to the buffer
address.
- In the DXE implementation of InternalQemuFwCfgSevDmaAllocateBuffer(),
keep the current IOMMU AllocateBuffer() call, but also call IOMMU
Map(), with CommonBuffer. Furthermore, propagate the Mapping output of
Map() outwards. (Note that Map() may have to be called in a loop,
dependent on "NumberOfBytes".)
- In the DXE implementation of InternalQemuFwCfgSevDmaFreeBuffer(), call
the IOMMU Unmap() function first (using the new Mapping parameter).
>
> [1]
> https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L159
>
> [2]
> https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L197
>
>
>>> - IoMmuMap() does not allocate pages when BusMasterCommonBuffer is
>>> requested, and it allocates pages (bounce buffer) otherwise.
>>>
>
> I am trying to wrap my head around how we can support
> BusMasterCommonBuffer when buffer was not allocated by us. Changing
> the memory encryption mask in a page table will not update the
> contents.
Thank you for the clarification. I've now tried to review the current
code for a better understanding. Are the below statements correct?
- For the guest, guest memory is always readable transparently.
- For the host, guest memory is always readable *as it was written
last*.
- If the guest memory was last written with the C bit clear, then the
host can read it as plaintext (regardless of the current state of the
C bit).
- If the guest memory was last written with the C bit set, then the host
can only read garbage (regardless of the current state of the C bit).
*If* this is the case, then:
(a) We already have a sort of guest->host information leak. Namely, in
IoMmuFreeBuffer() [OvmfPkg/IoMmuDxe/AmdSevIoMmu.c], the C bit is
restored, and the pages are released with gBS->FreePages(). However, the
pages being released are not rewritten in place (they are not actually
re-encrypted, only marked for encryption whenever they are next written
to). This means that pages released like this remain readable to the
hypervisor for an unpredictable time.
This is not necessarily a critical problem (after all the contents of
those pages were visible to the hypervisor at some point anyway!); but
in the longer term, such pages could accumulate, and if the hypervisor
is compromised later, it could potentially read an unbounded amount of
"past guest data" as plain-text.
(b) Plus, approaching the question from the Map() direction, we need to
consider two scenarios:
- Client code calls AllocateBuffer(), then Map(), and it writes to the
buffer only then. This should be safe.
- client code calls AllocateBuffer(), writes to it, and then calls
Map(). This will result in memory contents that look like garbage to
the hypervisor. Bad.
I can imagine the following to handle these cases: in the Map() and
Unmap() functions, we have to decrypt and encrypt the memory contents
in-place, after changing the C bit (without allocating additional
memory). Introduce a static UINT8 array with EFI_PAGE_SIZE bytes (this
will always remain in encrypted memory). Update the C bit with a single
function call for the entire range (like now) -- this will not affect
the guest-readability of the pages --, then bounce each page within the
range to the static buffer and back to its original place. In effect
this will in-place encrypt or decrypt the memory, and will be faster
than a byte-wise rewrite.
* BusMasterRead (host will want to read guest memory):
- Client calls Map() without calling AllocateBuffer() first. Map()
allocates an internal bounce buffer, clears the C bit, and does the
copying.
- Client fires off the PCI transaction.
- Client calls Unmap(), without calling FreeBuffer() later. Unmap()
restores the C-bit, *zeros* the pages, and releases the pages.
* BusMasterWrite (host will want to write to guest memory):
- Client calls Map() without calling AllocateBuffer() first. Map()
allocates an internal bounce buffer and clears the C bit.
- Client fires off the PCI transaction.
- Client calls Unmap(), without calling FreeBuffer() later. Unmap()
copies the bounce buffer to crpyted (client-owned) memory, restores
the C-bit, *zeros* the pages, and releases the pages.
* BusMasterCommonBuffer:
- Client calls AllocateBuffer(), and places some data in the returned
memory.
- Client calls Map(). Map() clears the C bit in one fell swoop, and
then decrypts the buffer in-place (by bouncing it page-wise to the
static array and back).
- Client communicates with the device.
- Client calls Unmap(). Unmap() restores the C bit in one fell swoop,
and encrypts the buffer in-place (by bouncing it page-wise to the
static array and back).
- Client reads some residual data from the buffer.
- Client calls FreeBuffer(). FreeBuffer() relases the pages.
This is suitable for the discussed ExitBootServices() callback update
too, where the callback will reset the virtio device (like now) and then
call Unmap() for all shared areas, without calling FreeBuffer() on them.
The above logic will re-encrypt the contents without affecting the UEFI
memmap.
> Also since the memory encryption mask works on PAGE_SIZE hence
> changing the encryption mask on not our allocated buffer could mess
> things up (e.g if NumberOfBytes is not PAGE_SIZE aligned).
This is not a problem for BusMasterRead and BusMasterWrite because for
those operations, Map() allocates the internal bounce buffer. Also not a
problem for BusMasterCommonBuffer, because then the client first calls
AllocateBuffer (whole number of pages), and so Map() can round up the
number of bytes and de-crypt full pages in-place (see above).
>
>>> *Regardless* of BusMaster operation, the following actions are
>>> carried out unconditionally:
>>>
>>> . the memory encryption mask is cleared in this function (and in
>>> this function only),
>>>
>>> . An attempt is made to grab a MAP_INFO structure from an
>>> internal free list (to be introduced!). The head of the list is
>>> a new static variable. If the free list is empty, then a
>>> MAP_INFO structure is allocated with AllocatePool(). The
>>> NO_MAPPING macro becomes unused and can be deleted from the
>>> source code.
>>>
>>> - IoMmuUnmap() clears the encryption mask unconditionally. (For
>>> this, it has to consult the MAP_INFO structure that is being
>>> passed in from the caller.) In addition:
>>>
>>> . If MapInfo->Operation is BusMasterCommonBuffer, then we know
>>> the allocation was done separately in AllocateBuffer, so we do
>>> not release the pages. Otherwise, we do release the pages.
>>>
>>> . MapInfo is linked back on the internal free list (see above).
>>> It is *never* released with FreePool().
>>>
>>> This approach guarantees that IoMmuUnmap() can de-program the
>>> IOMMU (= re-set the memory encryption mask) without changing the
>>> UEFI memory map. (I trust that MemEncryptSevSetPageEncMask() will
>>> not split page tables internally when it *re*sets the encryption
>>> mask -- is that correct?)
>
> Yes, MemEncryptSevSetPageEncMask() will not split pages when it
> restores mask.
Great, thanks.
Laszlo
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-28 8:39 ` Ard Biesheuvel
@ 2017-07-28 15:27 ` Laszlo Ersek
0 siblings, 0 replies; 28+ messages in thread
From: Laszlo Ersek @ 2017-07-28 15:27 UTC (permalink / raw)
To: Ard Biesheuvel, Brijesh Singh
Cc: edk2-devel@lists.01.org, Tom Lendacky, Jordan Justen, Jason Wang,
Michael S . Tsirkin, Gerd Hoffmann
On 07/28/17 10:39, Ard Biesheuvel wrote:
> On 27 July 2017 at 23:10, Brijesh Singh <brijesh.singh@amd.com> wrote:
>>
>>
>> On 07/27/2017 04:31 PM, Ard Biesheuvel wrote:
>>>
>>>
>>>> On 27 Jul 2017, at 21:55, Brijesh Singh <brijesh.singh@amd.com> wrote:
>>>>
>>>>
>>>>
>>>> On 07/27/2017 02:00 PM, Brijesh Singh wrote:
>>>>
>>>>>>> This distribution of operations seems wrong. The key point is that
>>>>>>> AllocateBuffer() *need not* result in a buffer that is immediately
>>>>>>> usable, and that client code is required to call Map()
>>>>>>> *unconditionally*, even if BusMasterCommonBuffer is the desired
>>>>>>> operation. Therefore, the right distribution of operations is:
>>>>>>>
>>>>>>> - IoMmuAllocateBuffer() allocates pages and does not touch the
>>>>>>> encryption mask..
>>>>>>>
>>>>>>> - IoMmuFreeBuffer() deallocates pages and does not touch the
>>>>>>> encryption
>>>>>>> mask.
>>>>>>>
>>>>> Actually one of main reason why we cleared and restored the memory
>>>>> encryption mask
>>>>> during allocate/free is because we also consume the IOMMU protocol in
>>>>> QemuFwCfgLib
>>>>> as a method to allocate and free a DMA buffer. I am certainly open to
>>>>> suggestions.
>>>>> [1]
>>>>> https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L159
>>>>> [2]
>>>>> https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L197
>>>>>>>
>>>>>>> - IoMmuMap() does not allocate pages when BusMasterCommonBuffer is
>>>>>>> requested, and it allocates pages (bounce buffer) otherwise.
>>>>>>>
>>>>> I am trying to wrap my head around how we can support
>>>>> BusMasterCommonBuffer
>>>>> when buffer was not allocated by us. Changing the memory encryption mask
>>>>> in
>>>>> a page table will not update the contents. Also since the memory
>>>>> encryption
>>>>> mask works on PAGE_SIZE hence changing the encryption mask on not our
>>>>> allocated
>>>>> buffer could mess things up (e.g if NumberOfBytes is not PAGE_SIZE
>>>>> aligned).
>>>>
>>>>
>>>> I may be missing something in my understanding. Here is a flow I have in
>>>> my
>>>> mind, please correct me.
>>>>
>>>> OvmfPkg/VirtIoBlk.c:
>>>>
>>>> VirtioBlkInit()
>>>> ....
>>>> ....
>>>> VirtioRingInit
>>>> Virtio->AllocateSharedPages(RingSize, &Ring->Base)
>>>> PciIo->AllocatePages(RingSize, &RingAddress)
>>>> Virtio->MapSharedPages(...,BusMasterCommonBuffer, Ring->Base,
>>>> RingSize, &RingDeviceAddress)
>>>> .....
>>>> .....
>>>>
>>>> This case is straight forward and we can easily maps. No need for bounce
>>>> buffering.
>>>>
>>>> VirtioBlkReadBlocks(..., BufferSize, Buffer,)
>>>> ......
>>>> ......
>>>> SynchronousRequest(..., BufferSize, Buffer)
>>>> ....
>>>> Virtio->MapSharedPages(..., BusMasterCommonBuffer, Buffer,
>>>> BufferSize, &DeviceAddress)
>>>> VirtioAppendDesc(DeviceAddress, BufferSize, ...)
>>>> VirtioFlush (...)
>>>> In the above case, "Buffer" was not allocated by us hence we will not
>>>> able to change the
>>>> memory encryption attributes. Am I missing something in the flow ?
>>>>
>>>
>>>
>>> Common buffer mappings may only be created from buffers that were
>>> allocated by AllocateBuffer(). In fact, that is its main purpose
>>
>>
>> Yes, that part is well understood. If the buffer was allocated by us (e.g
>> vring, request/status
>> structure etc) then those should be mapped as "BusMasterCommonBuffer".
Brijesh, thanks for the clarification. Previously I replied (at length)
to your paragraph that said "trying to wrap my head around...", and it
wasn't clear what you meant by "allocated by us". In my previous
response, I assumed that you meant a distinction between "allocated in
Map()" vs. "allocated in AllocateBuffer()". I stand by my earlier answer
for that (assumed) distinction, but now I see that you meant something else.
>>
>> But I am trying to figure out, how to map a data buffers before issuing a
>> virtio request. e.g when
>> VirtioBlkReadBlocks() is called, "Buffer" pointer is not a DMA address hence
>> we need to map it.
>> I think it should be mapped using "BusMasterWrite" not
>> "BusMasterCommonBuffer" before adding into vring.
>>
>
> If the transfer is strictly unidirectional, then that should work. If
> the transfer goes both ways, you may need to map/unmap for read and
> then map/unmap for write
>
You (Brijesh and Ard) are both right. This question depends on the
outermost interface that the specific virtio driver provides. In this
case, VirtioBlkReadBlocks() implements
EFI_BLOCK_IO_PROTOCOL.ReadBlocks(). The buffer is owned by an
independent agent, and it is guaranteed by the ReadBlocks() interface
that the transfer is unidirectional. So a standalone Map() call with
BusMasterWrite is appropriate, followed by a standalone Unmap() call
*before* VirtioBlkReadBlocks() returns. In this sense, my earlier
request to "*always* use AllocateBuffer + Map" was too strict.
However, in the *general* case, the recommendation remains the same. For
the virtio-net driver for example, the interfaces are not synchronous.
E.g., while EFI_BLOCK_IO_PROTOCOL.WriteBlocks() is synchronous,
EFI_SIMPLE_NETWORK_PROTOCOL.Transmit() is not. So, although in
VirtioNetTransmit() we might be tempted to use BusMasterRead, that's not
right, because ExitBootServices() could be called before the SNP client
collects the buffer with VirtioNetGetStatus().
The ExitBootServices() callback will have to Unmap() the area without
freeing it, and that's only possible if BusMasterCommonBuffer is used in
VirtioNetTransmit() to begin with. This means that we'll have to save
the client's data -- after updating it according to HeaderSize -- with
AllocateBuffer() in VirtioNetTransmit(), Map() *that* as
BusMasterCommonBuffer, and undo both steps in VirtioNetGetStatus(). And,
in the exit-boot-services callback, it has to be Unmap()ped only, but
not freed.
Basically it depends upon whether you can complete the entire operation
synchronously, before the outermost protocol interface returns.
I recommend that we work on the IoMmuDxe and QemuFwCfgLib updates first.
(And, my apologies again for not catching these issues immediately; as I
said, this is my first time doing non-1:1 DMA.)
Thanks,
Laszlo
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-28 13:38 ` Laszlo Ersek
@ 2017-07-28 16:00 ` Brijesh Singh
2017-07-28 16:16 ` Laszlo Ersek
` (2 more replies)
0 siblings, 3 replies; 28+ messages in thread
From: Brijesh Singh @ 2017-07-28 16:00 UTC (permalink / raw)
To: Laszlo Ersek, Ard Biesheuvel
Cc: brijesh.singh, edk2-devel@lists.01.org, Tom Lendacky,
Jordan Justen, Jason Wang, Michael S . Tsirkin, Gerd Hoffmann
On 07/28/2017 08:38 AM, Laszlo Ersek wrote:
> On 07/27/17 21:00, Brijesh Singh wrote:
>> On 07/27/2017 12:56 PM, Ard Biesheuvel wrote:
>>> On 27 July 2017 at 18:16, Laszlo Ersek <lersek@redhat.com> wrote:
>
>>>> Normally, Map allocates the bounce buffer internally, and Unmap
>>>> releases the bounce buffer internally (for BusMasterRead and
>>>> BusMasterWrite), which is not right here. If you use
>>>> AllocateBuffer() separately, then Map() -- with
>>>> BusMasterCommonBuffer -- will not allocate internally, and Unmap()
>>>> will also not deallocate internally. So, in the ExitBootServices()
>>>> callback, you will be able to call Unmap() only, and then *not* call
>>>> FreeBuffer() at all.
>>>>
>>>> This is why I suggest introducing all four functions (Allocate,
>>>> Free, Map, Unmap) to the VIRTIO_DEVICE_PROTOCOL, and why all virtio
>>>> drivers should use all four functions explicitly, not just Map and
>>>> Unmap.
>>>>
>>>> ... Actually, I think there is a bug in
>>>> "OvmfPkg/IoMmuDxe/AmdSevIoMmu.c". You have the following
>>>> distribution of operations at the moment:
>>>>
>>>> - IoMmuAllocateBuffer() allocates pages and clears the memory
>>>> encryption mask
>>>>
>>>> - IoMmuFreeBuffer() re-sets the memory encryption mask, and
>>>> deallocates pages
>>>>
>>>> - IoMmuMap() does nothing at all when BusMasterCommonBuffer is
>>>> requested (and IoMmuAllocateBuffer() was called previously).
>>>> Otherwise, IoMmuMap() allocates pages, allocates MAP_INFO, and
>>>> clears the memory encryption mask.
>>>>
>>>> - IoMmuUnmap() does nothing when cleaning up a BusMasterCommonBuffer
>>>> operation (--> NO_MAPPING). Otherwise, IoMmuUnmap() clears the
>>>> encryption mask, and frees both the pages and MAP_INFO.
>>>>
>>
>> I agree with you, there is a bug in AmdSevIoMmu.c. I will fix it. And
>> introduce list to track if the buffer was allocated by us. If buffer
>> was allocated by us then we can safely say that it does not require a
>> bounce buffer. While implementing the initial code I was not able to
>> see BusMasterCommonBuffer usage. But with virtio things are getting a
>> bit more clear in my mind.
>
> The purpose of the internal list is a little bit different -- it is a
> "free list", not a tracker list.
>
> Under the proposed scheme, a MAP_INFO structure will have to be
> allocated for *all* mappings: both for CommonBuffer (where the actual
> pages come from the caller, who retrieved them earlier with an
> AllocateBuffer() call), and for Read/Write (where the actual pages will
> be allocated internally to Map). Allocating the MAP_INFO structure in
> Map() is necessary in *both* cases because the Unmap() function can
> *only* consult this MAP_INFO structure to learn the address and the size
> at which it has to re-set the memory encryption mask. This is because
> the Unmap() function gets no other input parameter.
>
> Then, regardless of the original operation (CommonBuffer vs.
> Read/Write), the MAP_INFO structure has to be recycled in Unmap(). (For
> CommonBuffer, the actual pages are owned by the caller, so we don't free
> them here; for Read/Write, the actual pages are owned by Map/Unmap, so
> we free them in Unmap() *in addition* to recycling MAP_INFO.) The main
> point is that MAP_INFO cannot be released with FreePool() because that
> could change the UEFI memmap during ExitBootServices() processing. So
> MAP_INFO has to be "released" to an internal *free* list.
>
> And since we have an internal free list, the original MAP_INFO
> allocation in Map() should consult the free list first, and only if it
> is empty should it fall back to AllocatePool().
>
> Whether the actual pages tracked by MAP_INFO were allocated internally
> by Map(), or externally by the caller (via AllocateBuffer()) is an
> independent question. (And it can be seen from the MAP_INFO.Operation
> field.)
>
> Does this make it clearer?
>
It makes sense. thanks
One question, do we need to return EFI_NOT_SUPPORTED error when we get
request to map a buffer with CommonBuffer but the buffer was not
allocated using "EFI_PCI_IO_PROTOCOL->AllocateBuffer (...)"?
At least as per spec, it seems if caller wants to map a buffer with
CommonBuffer then it must have called "EFI_PCI_IO_PROTOCOL->AllocateBuffer (...)"
>>
>>>> This distribution of operations seems wrong. The key point is that
>>>> AllocateBuffer() *need not* result in a buffer that is immediately
>>>> usable, and that client code is required to call Map()
>>>> *unconditionally*, even if BusMasterCommonBuffer is the desired
>>>> operation. Therefore, the right distribution of operations is:
>>>>
>>>> - IoMmuAllocateBuffer() allocates pages and does not touch the
>>>> encryption mask..
>>>>
>>>> - IoMmuFreeBuffer() deallocates pages and does not touch the
>>>> encryption mask.
>>>>
>>
>> Actually one of main reason why we cleared and restored the memory
>> encryption mask during allocate/free is because we also consume the
>> IOMMU protocol in QemuFwCfgLib as a method to allocate and free a DMA
>> buffer. I am certainly open to suggestions.
>
> Argh. That's again my fault then, I should have noticed it. :( I
> apologize, the Map/Unmap/AllocateBuffer/FreeBuffer APIs are a "first"
> for me as well.
>
> As discussed earlier (and confirmed by Ard), calling *just*
> AllocateBuffer() is never enough, Map()/Unmap() are always necessary.
>
> So here's what I suggest (to keep the changes localized):
>
> - Extend the prototype of InternalQemuFwCfgSevDmaAllocateBuffer()
> function to output a "VOID *Mapping" parameter as well, in addition to
> the address.
>
> - Modify the prototype of the InternalQemuFwCfgSevDmaFreeBuffer()
> function to take a "VOID *Mapping" parameter in addition to the buffer
> address.
>
> - In the DXE implementation of InternalQemuFwCfgSevDmaAllocateBuffer(),
> keep the current IOMMU AllocateBuffer() call, but also call IOMMU
> Map(), with CommonBuffer. Furthermore, propagate the Mapping output of
> Map() outwards. (Note that Map() may have to be called in a loop,
> dependent on "NumberOfBytes".)
>
> - In the DXE implementation of InternalQemuFwCfgSevDmaFreeBuffer(), call
> the IOMMU Unmap() function first (using the new Mapping parameter).
>
I will update the code and send patch for review. thanks
>>
>> [1]
>> https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L159
>>
>> [2]
>> https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgDxe.c#L197
>>
>>
>>>> - IoMmuMap() does not allocate pages when BusMasterCommonBuffer is
>>>> requested, and it allocates pages (bounce buffer) otherwise.
>>>>
>>
>> I am trying to wrap my head around how we can support
>> BusMasterCommonBuffer when buffer was not allocated by us. Changing
>> the memory encryption mask in a page table will not update the
>> contents.
>
> Thank you for the clarification. I've now tried to review the current
> code for a better understanding. Are the below statements correct?
>
> - For the guest, guest memory is always readable transparently.
> - For the host, guest memory is always readable *as it was written
> last*.
> - If the guest memory was last written with the C bit clear, then the
> host can read it as plaintext (regardless of the current state of the
> C bit).
> - If the guest memory was last written with the C bit set, then the host
> can only read garbage (regardless of the current state of the C bit).
>
Your understanding is correct.
> *If* this is the case, then:
>
> (a) We already have a sort of guest->host information leak. Namely, in
> IoMmuFreeBuffer() [OvmfPkg/IoMmuDxe/AmdSevIoMmu.c], the C bit is
> restored, and the pages are released with gBS->FreePages(). However, the
> pages being released are not rewritten in place (they are not actually
> re-encrypted, only marked for encryption whenever they are next written
> to). This means that pages released like this remain readable to the
> hypervisor for an unpredictable time.
>
> This is not necessarily a critical problem (after all the contents of
> those pages were visible to the hypervisor at some point anyway!); but
> in the longer term, such pages could accumulate, and if the hypervisor
> is compromised later, it could potentially read an unbounded amount of
> "past guest data" as plain-text.
>
Very good catch, I can *zero* the pages. IIRC, I did not consider doing
so because it may introduce unnecessary perform hit (mainly when dealing
with larger pages). I think when we load kernel or initrd using QemuFwCfg
DMA interface we allocate large buffers.
I will still go ahead and experiment *zeroing* page and measure the performance
impact before we integrate the solution.
> (b) Plus, approaching the question from the Map() direction, we need to
> consider two scenarios:
>
> - Client code calls AllocateBuffer(), then Map(), and it writes to the
> buffer only then. This should be safe.
> - client code calls AllocateBuffer(), writes to it, and then calls
> Map(). This will result in memory contents that look like garbage to
> the hypervisor. Bad.
>
> I can imagine the following to handle these cases: in the Map() and
> Unmap() functions, we have to decrypt and encrypt the memory contents
> in-place, after changing the C bit (without allocating additional
> memory). Introduce a static UINT8 array with EFI_PAGE_SIZE bytes (this
> will always remain in encrypted memory). Update the C bit with a single
> function call for the entire range (like now) -- this will not affect
> the guest-readability of the pages --, then bounce each page within the
> range to the static buffer and back to its original place. In effect
> this will in-place encrypt or decrypt the memory, and will be faster
> than a byte-wise rewrite.
>
> * BusMasterRead (host will want to read guest memory):
> - Client calls Map() without calling AllocateBuffer() first. Map()
> allocates an internal bounce buffer, clears the C bit, and does the
> copying.
> - Client fires off the PCI transaction.
> - Client calls Unmap(), without calling FreeBuffer() later. Unmap()
> restores the C-bit, *zeros* the pages, and releases the pages.
>
Yes this will work just fine (see my previous comments on *zeroing* pages)
> * BusMasterWrite (host will want to write to guest memory):
> - Client calls Map() without calling AllocateBuffer() first. Map()
> allocates an internal bounce buffer and clears the C bit.
> - Client fires off the PCI transaction.
> - Client calls Unmap(), without calling FreeBuffer() later. Unmap()
> copies the bounce buffer to crpyted (client-owned) memory, restores
> the C-bit, *zeros* the pages, and releases the pages.
>
Yes, this will work just fine (see my previous comments on *zeroing* pages)
> * BusMasterCommonBuffer:
> - Client calls AllocateBuffer(), and places some data in the returned
> memory.
> - Client calls Map(). Map() clears the C bit in one fell swoop, and
> then decrypts the buffer in-place (by bouncing it page-wise to the
> static array and back).
> - Client communicates with the device.
> - Client calls Unmap(). Unmap() restores the C bit in one fell swoop,
> and encrypts the buffer in-place (by bouncing it page-wise to the
> static array and back).
> - Client reads some residual data from the buffer.
> - Client calls FreeBuffer(). FreeBuffer() relases the pages.
>
Yes this works fine as long as the client uses EFI_PCI_IO_PROTOCOL.AllocateBuffer()
to allocate the buffer.
> This is suitable for the discussed ExitBootServices() callback update
> too, where the callback will reset the virtio device (like now) and then
> call Unmap() for all shared areas, without calling FreeBuffer() on them.
> The above logic will re-encrypt the contents without affecting the UEFI
> memmap.
>
>> Also since the memory encryption mask works on PAGE_SIZE hence
>> changing the encryption mask on not our allocated buffer could mess
>> things up (e.g if NumberOfBytes is not PAGE_SIZE aligned).
>
> This is not a problem for BusMasterRead and BusMasterWrite because for
> those operations, Map() allocates the internal bounce buffer. Also not a
> problem for BusMasterCommonBuffer, because then the client first calls
> AllocateBuffer (whole number of pages), and so Map() can round up the
> number of bytes and de-crypt full pages in-place (see above).
>
>>
>>>> *Regardless* of BusMaster operation, the following actions are
>>>> carried out unconditionally:
>>>>
>>>> . the memory encryption mask is cleared in this function (and in
>>>> this function only),
>>>>
>>>> . An attempt is made to grab a MAP_INFO structure from an
>>>> internal free list (to be introduced!). The head of the list is
>>>> a new static variable. If the free list is empty, then a
>>>> MAP_INFO structure is allocated with AllocatePool(). The
>>>> NO_MAPPING macro becomes unused and can be deleted from the
>>>> source code.
>>>>
>>>> - IoMmuUnmap() clears the encryption mask unconditionally. (For
>>>> this, it has to consult the MAP_INFO structure that is being
>>>> passed in from the caller.) In addition:
>>>>
>>>> . If MapInfo->Operation is BusMasterCommonBuffer, then we know
>>>> the allocation was done separately in AllocateBuffer, so we do
>>>> not release the pages. Otherwise, we do release the pages.
>>>>
>>>> . MapInfo is linked back on the internal free list (see above).
>>>> It is *never* released with FreePool().
>>>>
>>>> This approach guarantees that IoMmuUnmap() can de-program the
>>>> IOMMU (= re-set the memory encryption mask) without changing the
>>>> UEFI memory map. (I trust that MemEncryptSevSetPageEncMask() will
>>>> not split page tables internally when it *re*sets the encryption
>>>> mask -- is that correct?)
>>
>> Yes, MemEncryptSevSetPageEncMask() will not split pages when it
>> restores mask.
>
> Great, thanks.
> Laszlo
>
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-28 16:00 ` Brijesh Singh
@ 2017-07-28 16:16 ` Laszlo Ersek
2017-07-28 19:21 ` Laszlo Ersek
2017-07-28 19:59 ` Laszlo Ersek
2 siblings, 0 replies; 28+ messages in thread
From: Laszlo Ersek @ 2017-07-28 16:16 UTC (permalink / raw)
To: Brijesh Singh, Ard Biesheuvel
Cc: edk2-devel@lists.01.org, Tom Lendacky, Jordan Justen, Jason Wang,
Michael S . Tsirkin, Gerd Hoffmann
On 07/28/17 18:00, Brijesh Singh wrote:
>
>
> On 07/28/2017 08:38 AM, Laszlo Ersek wrote:
>> On 07/27/17 21:00, Brijesh Singh wrote:
>>> I agree with you, there is a bug in AmdSevIoMmu.c. I will fix it. And
>>> introduce list to track if the buffer was allocated by us. If buffer
>>> was allocated by us then we can safely say that it does not require a
>>> bounce buffer. While implementing the initial code I was not able to
>>> see BusMasterCommonBuffer usage. But with virtio things are getting a
>>> bit more clear in my mind.
>>
>> The purpose of the internal list is a little bit different -- it is a
>> "free list", not a tracker list.
>>
>> Under the proposed scheme, a MAP_INFO structure will have to be
>> allocated for *all* mappings: both for CommonBuffer (where the actual
>> pages come from the caller, who retrieved them earlier with an
>> AllocateBuffer() call), and for Read/Write (where the actual pages will
>> be allocated internally to Map). Allocating the MAP_INFO structure in
>> Map() is necessary in *both* cases because the Unmap() function can
>> *only* consult this MAP_INFO structure to learn the address and the size
>> at which it has to re-set the memory encryption mask. This is because
>> the Unmap() function gets no other input parameter.
>>
>> Then, regardless of the original operation (CommonBuffer vs.
>> Read/Write), the MAP_INFO structure has to be recycled in Unmap(). (For
>> CommonBuffer, the actual pages are owned by the caller, so we don't free
>> them here; for Read/Write, the actual pages are owned by Map/Unmap, so
>> we free them in Unmap() *in addition* to recycling MAP_INFO.) The main
>> point is that MAP_INFO cannot be released with FreePool() because that
>> could change the UEFI memmap during ExitBootServices() processing. So
>> MAP_INFO has to be "released" to an internal *free* list.
>>
>> And since we have an internal free list, the original MAP_INFO
>> allocation in Map() should consult the free list first, and only if it
>> is empty should it fall back to AllocatePool().
>>
>> Whether the actual pages tracked by MAP_INFO were allocated internally
>> by Map(), or externally by the caller (via AllocateBuffer()) is an
>> independent question. (And it can be seen from the MAP_INFO.Operation
>> field.)
>>
>> Does this make it clearer?
>>
>
> It makes sense. thanks
>
> One question, do we need to return EFI_NOT_SUPPORTED error when we get
> request to map a buffer with CommonBuffer but the buffer was not
> allocated using "EFI_PCI_IO_PROTOCOL->AllocateBuffer (...)"?
>
> At least as per spec, it seems if caller wants to map a buffer with
> CommonBuffer then it must have called
> "EFI_PCI_IO_PROTOCOL->AllocateBuffer (...)"
Yes, that is it, exactly: if the caller violates a requirement in the
UEFI spec, covering the use of the APIs, then the firmware *may* make an
attempt to detect that (and to reject it), but the firmware is not
*required* to do so.
A much simpler example: on a double-free programming error, the second
gBS->FreePool() call is not *required* to detect the problem. ("The
Buffer that is freed must have been allocated by AllocatePool().")
So, I don't think we need to implement measures for checking that a
CommonBuffer Map() actually refers to a previously returned, active,
AllocateBuffer() address.
(Snipping the rest, I've read it all, thanks for the answers. Nothing to
add this time :) )
Cheers!
Laszlo
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-28 16:00 ` Brijesh Singh
2017-07-28 16:16 ` Laszlo Ersek
@ 2017-07-28 19:21 ` Laszlo Ersek
2017-07-28 19:59 ` Laszlo Ersek
2 siblings, 0 replies; 28+ messages in thread
From: Laszlo Ersek @ 2017-07-28 19:21 UTC (permalink / raw)
To: Brijesh Singh, Ard Biesheuvel
Cc: edk2-devel@lists.01.org, Tom Lendacky, Jordan Justen, Jason Wang,
Michael S . Tsirkin, Gerd Hoffmann
On 07/28/17 18:00, Brijesh Singh wrote:
>
>
> On 07/28/2017 08:38 AM, Laszlo Ersek wrote:
>> On 07/27/17 21:00, Brijesh Singh wrote:
>>> Actually one of main reason why we cleared and restored the memory
>>> encryption mask during allocate/free is because we also consume the
>>> IOMMU protocol in QemuFwCfgLib as a method to allocate and free a
>>> DMA buffer. I am certainly open to suggestions.
>>
>> Argh. That's again my fault then, I should have noticed it. :( I
>> apologize, the Map/Unmap/AllocateBuffer/FreeBuffer APIs are a "first"
>> for me as well.
>>
>> As discussed earlier (and confirmed by Ard), calling *just*
>> AllocateBuffer() is never enough, Map()/Unmap() are always necessary.
>>
>> So here's what I suggest (to keep the changes localized):
>>
>> - Extend the prototype of InternalQemuFwCfgSevDmaAllocateBuffer()
>> function to output a "VOID *Mapping" parameter as well, in addition
>> to the address.
>>
>> - Modify the prototype of the InternalQemuFwCfgSevDmaFreeBuffer()
>> function to take a "VOID *Mapping" parameter in addition to the
>> buffer address.
>>
>> - In the DXE implementation of
>> InternalQemuFwCfgSevDmaAllocateBuffer(), keep the current IOMMU
>> AllocateBuffer() call, but also call IOMMU Map(), with
>> CommonBuffer. Furthermore, propagate the Mapping output of Map()
>> outwards. (Note that Map() may have to be called in a loop,
>> dependent on "NumberOfBytes".)
>>
>> - In the DXE implementation of InternalQemuFwCfgSevDmaFreeBuffer(),
>> call the IOMMU Unmap() function first (using the new Mapping
>> parameter).
>>
>
> I will update the code and send patch for review. thanks
Here's an alternative, given that you mentioned being
performance-conscious. I'm not "suggesting" this as preferred, just
offering it for your consideration. Pick whichever you like more.
In this approach, I'm going to go back on my original request, namely to
keep the InternalQemuFwCfgDmaBytes() implementation centralized, in the
"QemuFwCfgLib.c" file. I now realize that the differences are large
enough that this may not have been a good idea. So here goes:
* Internal APIs ("QemuFwCfgLibInternal.h"):
- Remove the InternalQemuFwCfgSev*() function prototypes.
- Introduce the InternalQemuFwCfgDmaBytes() function prototype.
* SEC instance ("QemuFwCfgSec.c"):
- Remove the InternalQemuFwCfgSev*() function definitions.
- Add the InternalQemuFwCfgDmaBytes() function definition with
ASSERT(FALSE) + CpuDeadLoop().
* PEI instance ("QemuFwCfgPei.c):
- Remove the InternalQemuFwCfgSev*() function definitions.
- Copy the InternalQemuFwCfgDmaBytes() function definition from the
currently shared code ("QemuFwCfgLib.c"), and remove all branches
that are related to the SEV enabled case. IOW, specialize the
implementation to the plaintext case.
- In QemuFwCfgInitialize(), replace the
InternalQemuFwCfgSevIsEnabled() function call with a direct call to
MemEncryptSevIsEnabled().
* DXE instance ("QemuFwCfgDxe.c"):
- Remove the InternalQemuFwCfgSev*() function definitions.
- Copy the InternalQemuFwCfgDmaBytes() function definition from the
currently shared code ("QemuFwCfgLib.c"), as a starting point.
- Replace the InternalQemuFwCfgSevIsEnabled() call with a direct call
to MemEncryptSevIsEnabled().
- When SEV is enabled, split the control area ("FW_CFG_DMA_ACCESS") to
a separate buffer. This control area should be allocated with IOMMU
AllocateBuffer(), and Map()ped separately, as
BusMasterCommonBuffer64.
- For the data transfer buffer, use *only* Map() and Unmap(), without
AllocateBuffer() and FreeBuffer(). Use BusMasterRead64 and
BusMasterWrite64, dependent on FW_CFG_DMA_CTL_WRITE /
FW_CFG_DMA_CTL_READ. The point is that the potentially large data
area will be bounced only once, because Map()/Unmap() will own the
bounce buffer, and the in-place (de)crypting can be avoided. (The
fw_cfg DMA transfer is completed in one synchronous operation, as
witnessed by the library client.) The explicit CopyMem() calls can
be removed.
- Remove the InternalQemuFwCfgSevDmaAllocateBuffer() and
InternalQemuFwCfgSevDmaFreeBuffer() calls.
* shared code ("QemuFwCfgLib.c"):
- remove the InternalQemuFwCfgDmaBytes() function definition.
Thanks,
Laszlo
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-28 16:00 ` Brijesh Singh
2017-07-28 16:16 ` Laszlo Ersek
2017-07-28 19:21 ` Laszlo Ersek
@ 2017-07-28 19:59 ` Laszlo Ersek
2017-07-29 0:52 ` Brijesh Singh
2 siblings, 1 reply; 28+ messages in thread
From: Laszlo Ersek @ 2017-07-28 19:59 UTC (permalink / raw)
To: Brijesh Singh, Ard Biesheuvel
Cc: edk2-devel@lists.01.org, Tom Lendacky, Jordan Justen, Jason Wang,
Michael S . Tsirkin, Gerd Hoffmann
On 07/28/17 18:00, Brijesh Singh wrote:
> On 07/28/2017 08:38 AM, Laszlo Ersek wrote:
snip
>> (b) Plus, approaching the question from the Map() direction, we need
>> to consider two scenarios:
>>
>> - Client code calls AllocateBuffer(), then Map(), and it writes to
>> the buffer only then. This should be safe.
>> - client code calls AllocateBuffer(), writes to it, and then calls
>> Map(). This will result in memory contents that look like garbage
>> to the hypervisor. Bad.
>>
>> I can imagine the following to handle these cases: in the Map() and
>> Unmap() functions, we have to decrypt and encrypt the memory contents
>> in-place, after changing the C bit (without allocating additional
>> memory). Introduce a static UINT8 array with EFI_PAGE_SIZE bytes
>> (this will always remain in encrypted memory). Update the C bit with
>> a single function call for the entire range (like now) -- this will
>> not affect the guest-readability of the pages --, then bounce each
>> page within the range to the static buffer and back to its original
>> place. In effect this will in-place encrypt or decrypt the memory,
>> and will be faster than a byte-wise rewrite.
snip
>> * BusMasterCommonBuffer:
>> - Client calls AllocateBuffer(), and places some data in the
>> returned memory.
>> - Client calls Map(). Map() clears the C bit in one fell swoop,
>> and then decrypts the buffer in-place (by bouncing it page-wise
>> to the static array and back).
>> - Client communicates with the device.
>> - Client calls Unmap(). Unmap() restores the C bit in one fell
>> swoop, and encrypts the buffer in-place (by bouncing it
>> page-wise to the static array and back).
>> - Client reads some residual data from the buffer.
>> - Client calls FreeBuffer(). FreeBuffer() relases the pages.
>>
>
> Yes this works fine as long as the client uses
> EFI_PCI_IO_PROTOCOL.AllocateBuffer() to allocate the buffer.
Again, a performance-oriented thought:
Above I suggested using a statically allocated page-sized buffer, for
the in-place encryption/decryption. Ultimately this means *two*
CopyMem()s for the entire buffer (just executed page-wise), in *each* of
Map() and Unmap().
Maybe we can do better: what if you perform the CopyMem() from the
buffer right back to the same buffer? CopyMem() is *required* to work
with overlapping source and target areas (similarly to memmove() in
standard C).
This would result in *one* CopyMem (for in-place de-/encryption) in each
of Map() and Unmap(), and thereby it would have identical performance
impact to the BusMasterRead and BusMasterWrite Map() operations (where
copying / crypting takes place between distinct memory areas).
The OVMF DSC files resolve "BaseMemoryLib" -- which provides CopyMem()
-- to "MdePkg/Library/BaseMemoryLibRepStr/BaseMemoryLibRepStr.inf";
regardless of module type. The actual implementation appears to reside
in "MdePkg/Library/BaseMemoryLibRepStr/X64/CopyMem.nasm":
> global ASM_PFX(InternalMemCopyMem)
> ASM_PFX(InternalMemCopyMem):
> push rsi
> push rdi
> mov rsi, rdx ; rsi <- Source
> mov rdi, rcx ; rdi <- Destination
> lea r9, [rsi + r8 - 1] ; r9 <- End of Source
> cmp rsi, rdi
> mov rax, rdi ; rax <- Destination as return value
> jae .0
> cmp r9, rdi
> jae @CopyBackward ; Copy backward if overlapped
> .0:
> mov rcx, r8
> and r8, 7
> shr rcx, 3
> rep movsq ; Copy as many Qwords as possible
> jmp @CopyBytes
> @CopyBackward:
> mov rsi, r9 ; rsi <- End of Source
> lea rdi, [rdi + r8 - 1] ; esi <- End of Destination
> std ; set direction flag
> @CopyBytes:
> mov rcx, r8
> rep movsb ; Copy bytes backward
> cld
> pop rdi
> pop rsi
> ret
>
However, I'm afraid even if this works on SEV (which I certainly
expect!), this code won't be reached, due to the following CopyMem()
wrapper implementation in
"MdePkg/Library/BaseMemoryLibRepStr/CopyMemWrapper.c":
> VOID *
> EFIAPI
> CopyMem (
> OUT VOID *DestinationBuffer,
> IN CONST VOID *SourceBuffer,
> IN UINTN Length
> )
> {
> if (Length == 0) {
> return DestinationBuffer;
> }
> ASSERT ((Length - 1) <= (MAX_ADDRESS - (UINTN)DestinationBuffer));
> ASSERT ((Length - 1) <= (MAX_ADDRESS - (UINTN)SourceBuffer));
>
> if (DestinationBuffer == SourceBuffer) {
> return DestinationBuffer;
> }
> return InternalMemCopyMem (DestinationBuffer, SourceBuffer, Length);
> }
As you see, (DestinationBuffer == SourceBuffer) is handled as a no-op
(quite justifiedly, except in the case of SEV).
Personally I think it would be OK to copy the wrapper function and the
assembly code to OvmfPkg/IoMmuDxe/X64, under the names SevCopyMem() and
InternalSevCopyMem(), and call SevCopyMem() in the CommonBuffer cases of
Map() and Unmap(), for the in-place flipping.
For the 32-bit case (OvmfPkgIa32.dsc), my understanding is that guests
cannot control the C bit at all (there is no C bit in the PTEs), and
memory is always encrypted. Is that correct? If so, then we only need to
ensure that SevCopyMem() compile, as it will never be called -- in the
entry point function of OvmfPkg/IoMmuDxe, MemEncryptSevIsEnabled() will
return FALSE, and so the IOMMU protocol will not be installed. Therefore
the 32-bit version (under OvmfPkg/IoMmuDxe/Ia32) of SevCopyMem() can be
stubbed out as an ASSERT(FALSE)+CpuDeadLoop().
If you can think of a better location for SevCopyMem(), that's fine as
well. For example, you could add it to
"OvmfPkg/Library/BaseMemEncryptSevLib" as well.
... I don't think this functionality should be added under MdePkg,
because it is *very* special to the IOMMU implementation, and
practically no other module should use a "busy" in-place CopyMem().
Thanks
Laszlo
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-28 19:59 ` Laszlo Ersek
@ 2017-07-29 0:52 ` Brijesh Singh
2017-07-29 1:37 ` Brijesh Singh
0 siblings, 1 reply; 28+ messages in thread
From: Brijesh Singh @ 2017-07-29 0:52 UTC (permalink / raw)
To: Laszlo Ersek, Ard Biesheuvel
Cc: brijesh.singh, edk2-devel@lists.01.org, Tom Lendacky,
Jordan Justen, Jason Wang, Michael S . Tsirkin, Gerd Hoffmann
On 7/28/17 2:59 PM, Laszlo Ersek wrote:
> On 07/28/17 18:00, Brijesh Singh wrote:
>> On 07/28/2017 08:38 AM, Laszlo Ersek wrote:
> snip
>
>>> (b) Plus, approaching the question from the Map() direction, we need
>>> to consider two scenarios:
>>>
>>> - Client code calls AllocateBuffer(), then Map(), and it writes to
>>> the buffer only then. This should be safe.
>>> - client code calls AllocateBuffer(), writes to it, and then calls
>>> Map(). This will result in memory contents that look like garbage
>>> to the hypervisor. Bad.
>>>
>>> I can imagine the following to handle these cases: in the Map() and
>>> Unmap() functions, we have to decrypt and encrypt the memory contents
>>> in-place, after changing the C bit (without allocating additional
>>> memory). Introduce a static UINT8 array with EFI_PAGE_SIZE bytes
>>> (this will always remain in encrypted memory). Update the C bit with
>>> a single function call for the entire range (like now) -- this will
>>> not affect the guest-readability of the pages --, then bounce each
>>> page within the range to the static buffer and back to its original
>>> place. In effect this will in-place encrypt or decrypt the memory,
>>> and will be faster than a byte-wise rewrite.
> snip
>
>>> * BusMasterCommonBuffer:
>>> - Client calls AllocateBuffer(), and places some data in the
>>> returned memory.
>>> - Client calls Map(). Map() clears the C bit in one fell swoop,
>>> and then decrypts the buffer in-place (by bouncing it page-wise
>>> to the static array and back).
>>> - Client communicates with the device.
>>> - Client calls Unmap(). Unmap() restores the C bit in one fell
>>> swoop, and encrypts the buffer in-place (by bouncing it
>>> page-wise to the static array and back).
>>> - Client reads some residual data from the buffer.
>>> - Client calls FreeBuffer(). FreeBuffer() relases the pages.
>>>
>> Yes this works fine as long as the client uses
>> EFI_PCI_IO_PROTOCOL.AllocateBuffer() to allocate the buffer.
> Again, a performance-oriented thought:
>
> Above I suggested using a statically allocated page-sized buffer, for
> the in-place encryption/decryption. Ultimately this means *two*
> CopyMem()s for the entire buffer (just executed page-wise), in *each* of
> Map() and Unmap().
>
> Maybe we can do better: what if you perform the CopyMem() from the
> buffer right back to the same buffer? CopyMem() is *required* to work
> with overlapping source and target areas (similarly to memmove() in
> standard C).
>
> This would result in *one* CopyMem (for in-place de-/encryption) in each
> of Map() and Unmap(), and thereby it would have identical performance
> impact to the BusMasterRead and BusMasterWrite Map() operations (where
> copying / crypting takes place between distinct memory areas).
>
> The OVMF DSC files resolve "BaseMemoryLib" -- which provides CopyMem()
> -- to "MdePkg/Library/BaseMemoryLibRepStr/BaseMemoryLibRepStr.inf";
> regardless of module type. The actual implementation appears to reside
> in "MdePkg/Library/BaseMemoryLibRepStr/X64/CopyMem.nasm":
>
AMD APM document a procedure which must be used to perform in-place
encryption/decryption. We must follow those steps to ensure that data is
flush into memory using the correct C-bit. Not doing so may result in
unpredictable results.
http://support.amd.com/TechDocs/24593.pdf (Section 7.10.8)
>> global ASM_PFX(InternalMemCopyMem)
>> ASM_PFX(InternalMemCopyMem):
>> push rsi
>> push rdi
>> mov rsi, rdx ; rsi <- Source
>> mov rdi, rcx ; rdi <- Destination
>> lea r9, [rsi + r8 - 1] ; r9 <- End of Source
>> cmp rsi, rdi
>> mov rax, rdi ; rax <- Destination as return value
>> jae .0
>> cmp r9, rdi
>> jae @CopyBackward ; Copy backward if overlapped
>> .0:
>> mov rcx, r8
>> and r8, 7
>> shr rcx, 3
>> rep movsq ; Copy as many Qwords as possible
>> jmp @CopyBytes
>> @CopyBackward:
>> mov rsi, r9 ; rsi <- End of Source
>> lea rdi, [rdi + r8 - 1] ; esi <- End of Destination
>> std ; set direction flag
>> @CopyBytes:
>> mov rcx, r8
>> rep movsb ; Copy bytes backward
>> cld
>> pop rdi
>> pop rsi
>> ret
>>
> However, I'm afraid even if this works on SEV (which I certainly
> expect!), this code won't be reached, due to the following CopyMem()
> wrapper implementation in
> "MdePkg/Library/BaseMemoryLibRepStr/CopyMemWrapper.c":
>
>> VOID *
>> EFIAPI
>> CopyMem (
>> OUT VOID *DestinationBuffer,
>> IN CONST VOID *SourceBuffer,
>> IN UINTN Length
>> )
>> {
>> if (Length == 0) {
>> return DestinationBuffer;
>> }
>> ASSERT ((Length - 1) <= (MAX_ADDRESS - (UINTN)DestinationBuffer));
>> ASSERT ((Length - 1) <= (MAX_ADDRESS - (UINTN)SourceBuffer));
>>
>> if (DestinationBuffer == SourceBuffer) {
>> return DestinationBuffer;
>> }
>> return InternalMemCopyMem (DestinationBuffer, SourceBuffer, Length);
>> }
> As you see, (DestinationBuffer == SourceBuffer) is handled as a no-op
> (quite justifiedly, except in the case of SEV).
>
> Personally I think it would be OK to copy the wrapper function and the
> assembly code to OvmfPkg/IoMmuDxe/X64, under the names SevCopyMem() and
> InternalSevCopyMem(), and call SevCopyMem() in the CommonBuffer cases of
> Map() and Unmap(), for the in-place flipping.
>
> For the 32-bit case (OvmfPkgIa32.dsc), my understanding is that guests
> cannot control the C bit at all (there is no C bit in the PTEs), and
> memory is always encrypted. Is that correct? If so, then we only need to
> ensure that SevCopyMem() compile, as it will never be called -- in the
> entry point function of OvmfPkg/IoMmuDxe, MemEncryptSevIsEnabled() will
> return FALSE, and so the IOMMU protocol will not be installed. Therefore
> the 32-bit version (under OvmfPkg/IoMmuDxe/Ia32) of SevCopyMem() can be
> stubbed out as an ASSERT(FALSE)+CpuDeadLoop().
>
> If you can think of a better location for SevCopyMem(), that's fine as
> well. For example, you could add it to
> "OvmfPkg/Library/BaseMemEncryptSevLib" as well.
>
> ... I don't think this functionality should be added under MdePkg,
> because it is *very* special to the IOMMU implementation, and
> practically no other module should use a "busy" in-place CopyMem().
>
> Thanks
> Laszlo
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-29 0:52 ` Brijesh Singh
@ 2017-07-29 1:37 ` Brijesh Singh
2017-07-31 18:20 ` Laszlo Ersek
0 siblings, 1 reply; 28+ messages in thread
From: Brijesh Singh @ 2017-07-29 1:37 UTC (permalink / raw)
To: Laszlo Ersek, Ard Biesheuvel
Cc: brijesh.singh, edk2-devel@lists.01.org, Tom Lendacky,
Jordan Justen, Jason Wang, Michael S . Tsirkin, Gerd Hoffmann
On 7/28/17 7:52 PM, Brijesh Singh wrote:
[snip]
> AMD APM document a procedure which must be used to perform in-place
> encryption/decryption. We must follow those steps to ensure that data is
> flush into memory using the correct C-bit. Not doing so may result in
> unpredictable results.
>
> http://support.amd.com/TechDocs/24593.pdf (Section 7.10.8)
I am wondering if UEFI provides APIs to get two linear mapping of the
same physical address. The steps says we create two mapping of same
physical address with different C-bits. I will look into UEFI docs to
see if something like this exist otherwise we have to consider two
memcpy. Since its just for CommandBuffers (which usually are smaller
hence we may be okay from performance point of view. Also its just a
boot time thing, does not get used when guest OS is takes over. I will
investigate and see what works best without adding extra complexity in
the code :)
-Brijesh
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support
2017-07-29 1:37 ` Brijesh Singh
@ 2017-07-31 18:20 ` Laszlo Ersek
0 siblings, 0 replies; 28+ messages in thread
From: Laszlo Ersek @ 2017-07-31 18:20 UTC (permalink / raw)
To: Brijesh Singh, Ard Biesheuvel
Cc: edk2-devel@lists.01.org, Tom Lendacky, Jordan Justen, Jason Wang,
Michael S . Tsirkin, Gerd Hoffmann
On 07/29/17 03:37, Brijesh Singh wrote:
> On 7/28/17 7:52 PM, Brijesh Singh wrote:
> [snip]
>> AMD APM document a procedure which must be used to perform in-place
>> encryption/decryption. We must follow those steps to ensure that data is
>> flush into memory using the correct C-bit. Not doing so may result in
>> unpredictable results.
>>
>> http://support.amd.com/TechDocs/24593.pdf (Section 7.10.8)
> I am wondering if UEFI provides APIs to get two linear mapping of the
> same physical address. The steps says we create two mapping of same
> physical address with different C-bits. I will look into UEFI docs to
> see if something like this exist otherwise we have to consider two
> memcpy. Since its just for CommandBuffers (which usually are smaller
> hence we may be okay from performance point of view.
Right, the separate static bounce buffer (4KB in size) and the two
matching CopyMem()s look easier to understand and safer. Hopefully there
won't be a performance issue in practice.
> Also its just a
> boot time thing, does not get used when guest OS is takes over. I will
> investigate and see what works best without adding extra complexity in
> the code :)
Thanks! (Sorry about the late reply.)
Laszlo
^ permalink raw reply [flat|nested] 28+ messages in thread
end of thread, other threads:[~2017-07-31 18:18 UTC | newest]
Thread overview: 28+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2017-07-19 22:09 [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support Brijesh Singh
2017-07-19 22:09 ` [RFC v1 1/3] OvmfPkg/Include/Virtio10: Define VIRTIO_F_IOMMU_PLATFORM feature bit Brijesh Singh
2017-07-19 22:09 ` [RFC v1 2/3] OvmfPkg/VirtioLib: Add IOMMU_PLATFORM support Brijesh Singh
2017-07-19 22:09 ` [RFC v1 3/3] OvmfPkg/VirtioBlkDxe: Add VIRITO_F_IOMMU_PLATFORM support Brijesh Singh
[not found] ` <62320c1a-0cec-947c-8c63-5eb0416e4e33@redhat.com>
2017-07-21 11:17 ` [RFC v1 0/3] Add VIRTIO_F_IOMMU_PLATFORM support Brijesh Singh
[not found] ` <20170722024318-mutt-send-email-mst@kernel.org>
2017-07-24 8:25 ` Gerd Hoffmann
2017-07-25 18:17 ` Laszlo Ersek
2017-07-25 23:42 ` Brijesh Singh
[not found] ` <904dae9f-e515-01ba-e16f-6561616c78af@redhat.com>
2017-07-26 15:30 ` Laszlo Ersek
2017-07-27 14:21 ` Brijesh Singh
2017-07-27 17:16 ` Laszlo Ersek
2017-07-27 17:56 ` Ard Biesheuvel
2017-07-27 19:00 ` Brijesh Singh
2017-07-27 20:55 ` Brijesh Singh
2017-07-27 21:31 ` Ard Biesheuvel
2017-07-27 21:38 ` Andrew Fish
2017-07-27 22:13 ` Brijesh Singh
2017-07-27 22:10 ` Brijesh Singh
2017-07-28 8:39 ` Ard Biesheuvel
2017-07-28 15:27 ` Laszlo Ersek
2017-07-28 13:38 ` Laszlo Ersek
2017-07-28 16:00 ` Brijesh Singh
2017-07-28 16:16 ` Laszlo Ersek
2017-07-28 19:21 ` Laszlo Ersek
2017-07-28 19:59 ` Laszlo Ersek
2017-07-29 0:52 ` Brijesh Singh
2017-07-29 1:37 ` Brijesh Singh
2017-07-31 18:20 ` Laszlo Ersek
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox