From f7d38bc10b11abadbcd9b12b1784d7108f0a5d7e Mon Sep 17 00:00:00 2001 From: Manuel Bentele Date: Thu, 10 Jun 2021 10:47:03 +0200 Subject: [qemu] Implement passthrough of NVIDIA GPUs The implementation adds specified PCI devics of a NVIDIA GPU on the host system to the final Libvirt domain XML configuration for a NVIDIA GPU passthrough. In addition to that, the implementation adds support for the Looking Glass Client to display the framebuffer of the NVIDIA GPU on the host system through a shared memory device. --- ...sformationSpecificQemuGpuPassthroughNvidia.java | 179 ++++++++++++++++++++- 1 file changed, 176 insertions(+), 3 deletions(-) (limited to 'core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuGpuPassthroughNvidia.java') diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuGpuPassthroughNvidia.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuGpuPassthroughNvidia.java index c41f989c..a22bf027 100644 --- a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuGpuPassthroughNvidia.java +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuGpuPassthroughNvidia.java @@ -1,8 +1,19 @@ package org.openslx.runvirt.plugin.qemu.configuration; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import org.openslx.libvirt.capabilities.Capabilities; import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.device.HostdevPci; +import org.openslx.libvirt.domain.device.HostdevPciDeviceAddress; +import org.openslx.libvirt.domain.device.HostdevPciDeviceDescription; +import org.openslx.libvirt.domain.device.Shmem; +import org.openslx.libvirt.domain.device.Video; import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs; import org.openslx.runvirt.plugin.qemu.virtualization.LibvirtHypervisorQemu; +import org.openslx.runvirt.virtualization.LibvirtHypervisorException; import org.openslx.virtualization.configuration.transformation.TransformationException; import org.openslx.virtualization.configuration.transformation.TransformationSpecific; @@ -20,6 +31,31 @@ public class TransformationSpecificQemuGpuPassthroughNvidia */ private static final String NAME = "QEMU GPU passthrough [Nvidia]"; + /** + * Vendor identifier of PCI devices from Nvidia. + */ + private static final int NVIDIA_PCI_VENDOR_ID = 0x10de; + + /** + * Vendor identifier of the Hyper-V enlightenment for hypervisor shadowing. + */ + public static final String HYPERV_VENDOR_ID = "62776c706277"; + + /** + * Maximum width in pixel of the GPU passthrough rendered display. + */ + private static final long MAX_DISPLAY_WIDTH = 2560; + + /** + * Maximum height in pixel of the GPU passthrough rendered display. + */ + private static final long MAX_DISPLAY_HEIGHT = 1440; + + /** + * Reserved memory for framebuffer meta data of the Looking Glass shared memory device in MiB. + */ + private static final long RESERVED_MEMORY_FRAMEBUFFER = 10; + /** * Creates a new Nvidia GPU passthrough transformation for Libvirt/QEMU virtualization * configurations. @@ -31,6 +67,63 @@ public class TransformationSpecificQemuGpuPassthroughNvidia super( TransformationSpecificQemuGpuPassthroughNvidia.NAME, hypervisor ); } + /** + * Validates a PCI device description and address of a PCI device from a Nvidia GPU and parses + * the validated PCI device addresses. + * + * @param pciIds textual PCI device description and address to be validated. + * + * @return list of validated and parsed PCI device addresses for a NVIDIA GPU passthrough. + * + * @throws TransformationException validation of PCI device description and address failed. + */ + private static List validateParseNvidiaPciIds( List pciIds ) + throws TransformationException + { + final List parsedPciAddresses = new ArrayList(); + + if ( pciIds != null && pciIds.size() > 0 ) { + // abort if arguments do not follow the pattern: + // + // [0]: : + // [1]: ::. + // [2]: : + // [3]: ::. + // ... + // + if ( pciIds.size() % 2 != 0 ) { + throw new TransformationException( + "Arguments of PCI IDs are not follow the pattern for a GPU passthrough!" ); + } + + // parse PCI device description and PCI device address + for ( int i = 0; i < pciIds.size(); i += 2 ) { + // parse vendor and device ID + HostdevPciDeviceDescription deviceDescription = null; + try { + deviceDescription = HostdevPciDeviceDescription.valueOf( pciIds.get( i ) ); + } catch ( IllegalArgumentException e ) { + throw new TransformationException( "Invalid vendor or device ID of the PCI device description!" ); + } + + // validate vendor ID + final int vendorId = deviceDescription.getVendorId(); + if ( TransformationSpecificQemuGpuPassthroughNvidia.NVIDIA_PCI_VENDOR_ID != vendorId ) { + final String errorMsg = "Vendor ID '" + vendorId + "' of the PCI device is not from Nvidia!"; + throw new TransformationException( errorMsg ); + } + + // parse PCI domain, PCI bus, PCI device and PCI function + final HostdevPciDeviceAddress parsedPciAddress = HostdevPciDeviceAddress.valueOf( pciIds.get( i + 1 ) ); + if ( parsedPciAddress != null ) { + parsedPciAddresses.add( parsedPciAddress ); + } + } + } + + return parsedPciAddresses; + } + /** * Validates a virtualization configuration and input arguments for this transformation. * @@ -43,6 +136,55 @@ public class TransformationSpecificQemuGpuPassthroughNvidia if ( config == null || args == null ) { throw new TransformationException( "Virtualization configuration or input arguments are missing!" ); } + + TransformationSpecificQemuGpuPassthroughNvidia.validateParseNvidiaPciIds( args.getVmNvGpuIds0() ); + } + + /** + * Queries and returns the capabilities of the Libvirt/QEMU hypervisor. + * + * @return capabilities of the Libvirt/QEMU hypervisor. + * @throws TransformationException failed to query and return the capabilities of the + * Libvirt/QEMU hypervisor. + */ + protected Capabilities getCapabilities() throws TransformationException + { + Capabilities capabilities = null; + + try { + capabilities = this.getVirtualizer().getCapabilites(); + } catch ( LibvirtHypervisorException e ) { + final String errorMsg = new String( + "Failed to retrieve host capabilities from QEMU virtualizer: " + e.getLocalizedMessage() ); + throw new TransformationException( errorMsg ); + } + + return capabilities; + } + + private static BigInteger roundToNearestPowerOf2( BigInteger value ) + { + BigInteger k = BigInteger.valueOf( 1 ); + + while ( k.compareTo( value ) == -1 ) { + k = k.multiply( BigInteger.valueOf( 2 ) ); + } + + return k; + } + + /** + * Calculates the framebuffer memory size for the Looking Glass shared memory device. + * + * @return framebuffer memory size in bytes for the Looking Glass shared memory device. + */ + private static BigInteger calculateFramebufferSize() + { + final long totalBytesFramebuffer = MAX_DISPLAY_WIDTH * MAX_DISPLAY_HEIGHT * 4 * 2; + final long totalBytesReserved = RESERVED_MEMORY_FRAMEBUFFER * 1048576; + + // round sum of total memory in bytes to nearest power of two + return roundToNearestPowerOf2( BigInteger.valueOf( totalBytesFramebuffer + totalBytesReserved ) ); } @Override @@ -51,9 +193,40 @@ public class TransformationSpecificQemuGpuPassthroughNvidia // validate configuration and input arguments this.validateInputs( config, args ); - // check if IOMMU support is available on the host + // check if passthrough of Nvidia GPU takes place + if ( args.isNvidiaGpuPassthroughEnabled() ) { + // validate submitted PCI IDs + final List pciDeviceAddresses = TransformationSpecificQemuGpuPassthroughNvidia + .validateParseNvidiaPciIds( args.getVmNvGpuIds0() ); + + // check if IOMMU support is available on the host + if ( !this.getCapabilities().hasHostIommuSupport() ) { + final String errorMsg = "IOMMU support is not available on the hypervisor but required for GPU passthrough!"; + throw new TransformationException( errorMsg ); + } + + // passthrough PCI devices of the GPU + for ( final HostdevPciDeviceAddress pciDeviceAddress : pciDeviceAddresses ) { + final HostdevPci pciDevice = config.addHostdevPciDevice(); + pciDevice.setManaged( true ); + pciDevice.setSource( pciDeviceAddress ); + } - // TODO: implement Nvidia hypervisor shadowing - // call this filter at the end, since -> override of software graphics to 'none' necessary + // add shared memory device for Looking Glass + final Shmem shmemDevice = config.addShmemDevice(); + shmemDevice.setName( "looking-glass" ); + shmemDevice.setModel( Shmem.Model.IVSHMEM_PLAIN ); + shmemDevice.setSize( TransformationSpecificQemuGpuPassthroughNvidia.calculateFramebufferSize() ); + + // enable hypervisor shadowing to avoid error code 43 of Nvidia drivers in virtual machines + config.setFeatureHypervVendorIdValue( TransformationSpecificQemuGpuPassthroughNvidia.HYPERV_VENDOR_ID ); + config.setFeatureHypervVendorIdState( true ); + config.setFeatureKvmHiddenState( true ); + + // disable all software video devices by disable them + for ( Video videoDevice : config.getVideoDevices() ) { + videoDevice.disable(); + } + } } } -- cgit v1.2.3-55-g7522