From c1e793128dc53cfed7faec60edf0c5998f527097 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 26 Jul 2023 17:41:31 +0200 Subject: [qemu] java: Make pci passthrough generic, not just for nvidia --- .../java/org/openslx/runvirt/plugin/qemu/App.java | 6 +- .../runvirt/plugin/qemu/cmdln/CommandLineArgs.java | 30 ++- ...sformationSpecificQemuGpuPassthroughNvidia.java | 277 -------------------- .../TransformationSpecificQemuPciPassthrough.java | 284 +++++++++++++++++++++ ...mationSpecificQemuGpuPassthroughNvidiaTest.java | 10 +- 5 files changed, 318 insertions(+), 289 deletions(-) delete mode 100644 core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuGpuPassthroughNvidia.java create mode 100644 core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuPciPassthrough.java diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/App.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/App.java index 6a0dc9cb..0744c9b5 100644 --- a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/App.java +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/App.java @@ -25,14 +25,14 @@ import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericInterf import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericMemory; import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericName; import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericParallelDevices; -import org.openslx.runvirt.plugin.qemu.configuration.TransformationSpecificQemuSerialDevices; import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericUuid; import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericWrapperScript; import org.openslx.runvirt.plugin.qemu.configuration.TransformationSpecificQemuArchitecture; import org.openslx.runvirt.plugin.qemu.configuration.TransformationSpecificQemuFirmware; -import org.openslx.runvirt.plugin.qemu.configuration.TransformationSpecificQemuGpuPassthroughNvidia; import org.openslx.runvirt.plugin.qemu.configuration.TransformationSpecificQemuGraphics; import org.openslx.runvirt.plugin.qemu.configuration.TransformationSpecificQemuMdevPassthroughIntel; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationSpecificQemuPciPassthrough; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationSpecificQemuSerialDevices; import org.openslx.runvirt.plugin.qemu.virtualization.LibvirtHypervisorQemu; import org.openslx.runvirt.plugin.qemu.virtualization.LibvirtHypervisorQemu.QemuSessionType; import org.openslx.runvirt.viewer.Viewer; @@ -160,7 +160,7 @@ public class App transformationManager.register( new TransformationSpecificQemuGraphics( hypervisorQemu ), true ); transformationManager.register( new TransformationSpecificQemuSerialDevices( hypervisorQemu ), true ); transformationManager.register( new TransformationSpecificQemuMdevPassthroughIntel( hypervisorQemu ), false ); - transformationManager.register( new TransformationSpecificQemuGpuPassthroughNvidia( hypervisorQemu ), false ); + transformationManager.register( new TransformationSpecificQemuPciPassthrough( hypervisorQemu ), false ); } // Needs to be last one since TransformationSpecificQemuArchitecture sets this too diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/cmdln/CommandLineArgs.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/cmdln/CommandLineArgs.java index 21b11968..396c0d8c 100644 --- a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/cmdln/CommandLineArgs.java +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/cmdln/CommandLineArgs.java @@ -13,6 +13,8 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.openslx.libvirt.domain.device.HostdevPciDeviceDescription; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationSpecificQemuPciPassthrough; import org.openslx.util.Util; /** @@ -453,13 +455,33 @@ public class CommandLineArgs } /** - * Returns the state whether a passthrough of a NVIDIA GPU is required. - * - * @return state whether a passthrough of a NVIDIA GPU is required. + * Returns the state whether a passthrough of a NVIDIA GPU is requested. + * Do this by checking the vendor ID of each PCI device that's being passed + * through. If one of them is nvidia, assume we're running passthrough for + * an nvidia GPU. */ public boolean isNvidiaGpuPassthroughEnabled() { - return this.getVmNvGpuIds0().size() > 0; + List pciIds = this.getVmNvGpuIds0(); + // parse PCI device description and PCI device address + for ( int i = 0; i < pciIds.size() - 1; i += 2 ) { + // parse vendor and device ID + HostdevPciDeviceDescription deviceDescription = null; + try { + deviceDescription = HostdevPciDeviceDescription.valueOf( pciIds.get( i ) ); + } catch ( IllegalArgumentException e ) { + continue; + } + + // validate vendor ID + final int vendorId = deviceDescription.getVendorId(); + if ( TransformationSpecificQemuPciPassthrough.NVIDIA_PCI_VENDOR_ID != vendorId ) + continue; + + // Have at least one device by nvidia, just assume it's a GPU for now + return true; + } + return false; } /** 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 deleted file mode 100644 index 23e5fe18..00000000 --- a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuGpuPassthroughNvidia.java +++ /dev/null @@ -1,277 +0,0 @@ -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.GraphicsSpice; -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.libvirt.domain.device.Device; -import org.openslx.libvirt.domain.device.Graphics.ListenType; -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; - -/** - * Specific Nvidia GPU passthrough transformation for Libvirt/QEMU virtualization configurations. - * - * @author Manuel Bentele - * @version 1.0 - */ -public class TransformationSpecificQemuGpuPassthroughNvidia - extends TransformationSpecific -{ - /** - * Name of the configuration transformation. - */ - 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; - - /** - * Switch to turn patch for Nvidia GPU-Passthrough (enables Hyper-V enlightening) on or off to - * avoid driver error code 43 in guest system. - */ - public static final boolean NVIDIA_PATCH = true; - - /** - * 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. - * - * @param hypervisor Libvirt/QEMU hypervisor. - */ - public TransformationSpecificQemuGpuPassthroughNvidia( LibvirtHypervisorQemu hypervisor ) - { - 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. - * - * @param config virtualization configuration for the validation. - * @param args input arguments for the validation. - * @throws TransformationException validation has failed. - */ - private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException - { - 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 - public void transform( Domain config, CommandLineArgs args ) throws TransformationException - { - // validate configuration and input arguments - this.validateInputs( config, args ); - - // 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 ); - } - // Check config for PCI addresses already in use - boolean inUse[] = new boolean[ 64 ]; - inUse[0] = true; - inUse[1] = true; - for ( Device dev : config.getDevices() ) { - HostdevPciDeviceAddress target = dev.getPciTarget(); - if ( target == null ) - continue; - if ( target.getPciDomain() != 0 || target.getPciBus() != 0 ) - continue; // Ignore non-primary bus - if ( target.getPciDevice() >= inUse.length ) - continue; - inUse[target.getPciDevice()] = true; - } - // Use first free one. Usually 00:02:00 is primary VGA - int devAddr; - for ( devAddr = 0; devAddr < inUse.length; ++devAddr ) { - if ( !inUse[devAddr] ) - break; - } - - // passthrough PCI devices of the GPU - for ( final HostdevPciDeviceAddress pciDeviceAddress : pciDeviceAddresses ) { - final HostdevPci pciDevice = config.addHostdevPciDevice(); - pciDevice.setManaged( true ); - pciDevice.setSource( pciDeviceAddress ); - if ( pciDeviceAddress.getPciFunction() == 0 && pciDeviceAddresses.size() > 1 ) { - pciDevice.setMultifunction( true ); - } - pciDevice.setPciTarget( new HostdevPciDeviceAddress( 0, devAddr, pciDeviceAddress.getPciFunction() ) ); - } - - // 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 - if ( TransformationSpecificQemuGpuPassthroughNvidia.NVIDIA_PATCH ) { - config.setFeatureHypervVendorIdValue( TransformationSpecificQemuGpuPassthroughNvidia.HYPERV_VENDOR_ID ); - config.setFeatureHypervVendorIdState( true ); - config.setFeatureKvmHiddenState( true ); - } - - // disable all software video devices if device passthrough debug mode is not enabled - if ( !args.isDebugDevicePassthroughEnabled() ) { - for ( Video videoDevice : config.getVideoDevices() ) { - videoDevice.disable(); - } - } - - // force SPICE graphics to listen on local address for looking-glass-client - for ( int i = 0; i < config.getGraphicSpiceDevices().size(); i++ ) { - final GraphicsSpice graphicsSpiceDevice = config.getGraphicSpiceDevices().get( i ); - graphicsSpiceDevice.setListenType( ListenType.ADDRESS ); - graphicsSpiceDevice.setListenAddress( GraphicsSpice.DEFAULT_ADDRESS ); - graphicsSpiceDevice.setListenPort( GraphicsSpice.DEFAULT_PORT + i ); - } - } - } -} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuPciPassthrough.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuPciPassthrough.java new file mode 100644 index 00000000..1a20448f --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuPciPassthrough.java @@ -0,0 +1,284 @@ +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.Device; +import org.openslx.libvirt.domain.device.Graphics.ListenType; +import org.openslx.libvirt.domain.device.GraphicsSpice; +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; + +/** + * Specific Nvidia GPU passthrough transformation for Libvirt/QEMU virtualization configurations. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class TransformationSpecificQemuPciPassthrough + extends TransformationSpecific +{ + /** + * Name of the configuration transformation. + */ + private static final String NAME = "QEMU GPU passthrough [Nvidia]"; + + /** + * Vendor identifier of PCI devices from Nvidia. + */ + public static final int NVIDIA_PCI_VENDOR_ID = 0x10de; + + /** + * Switch to turn patch for Nvidia GPU-Passthrough (enables Hyper-V enlightening) on or off to + * avoid driver error code 43 in guest system. + */ + public static final boolean NVIDIA_PATCH = true; + + /** + * 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. + * + * @param hypervisor Libvirt/QEMU hypervisor. + */ + public TransformationSpecificQemuPciPassthrough( LibvirtHypervisorQemu hypervisor ) + { + super( TransformationSpecificQemuPciPassthrough.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!" ); + } + + // 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. + * + * @param config virtualization configuration for the validation. + * @param args input arguments for the validation. + * @throws TransformationException validation has failed. + */ + private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException + { + if ( config == null || args == null ) { + throw new TransformationException( "Virtualization configuration or input arguments are missing!" ); + } + + TransformationSpecificQemuPciPassthrough.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 + public void transform( Domain config, CommandLineArgs args ) throws TransformationException + { + // validate configuration and input arguments + this.validateInputs( config, args ); + + // validate submitted PCI IDs + final List pciDeviceAddresses = TransformationSpecificQemuPciPassthrough + .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 ); + } + // Check config for PCI addresses already in use + int inUse[] = new int[ 64 ]; + inUse[0] = Integer.MAX_VALUE; + inUse[1] = Integer.MAX_VALUE; + for ( Device dev : config.getDevices() ) { + HostdevPciDeviceAddress target = dev.getPciTarget(); + if ( target == null ) + continue; + if ( target.getPciDomain() != 0 || target.getPciBus() != 0 ) + continue; // Ignore non-primary bus + if ( target.getPciDevice() >= inUse.length ) + continue; + inUse[target.getPciDevice()] = Integer.MAX_VALUE; + } + + // passthrough PCI devices of the GPU + for ( final HostdevPciDeviceAddress pciDeviceAddress : pciDeviceAddresses ) { + final HostdevPci pciDevice = config.addHostdevPciDevice(); + pciDevice.setManaged( true ); + pciDevice.setSource( pciDeviceAddress ); + if ( pciDeviceAddress.getPciFunction() == 0 && pciDeviceAddresses.size() > 1 ) { + pciDevice.setMultifunction( true ); + } + int devAddr = getFreeAddr( inUse, pciDeviceAddress ); + pciDevice.setPciTarget( new HostdevPciDeviceAddress( 0, devAddr, pciDeviceAddress.getPciFunction() ) ); + } + + // check if passthrough of Nvidia GPU takes place + if ( args.isNvidiaGpuPassthroughEnabled() ) { + // add shared memory device for Looking Glass + final Shmem shmemDevice = config.addShmemDevice(); + shmemDevice.setName( "looking-glass" ); + shmemDevice.setModel( Shmem.Model.IVSHMEM_PLAIN ); + shmemDevice.setSize( TransformationSpecificQemuPciPassthrough.calculateFramebufferSize() ); + + // enable hypervisor shadowing to avoid error code 43 of Nvidia drivers in virtual machines + if ( TransformationSpecificQemuPciPassthrough.NVIDIA_PATCH ) { + config.setFeatureHypervVendorIdValue( TransformationSpecificQemuPciPassthrough.HYPERV_VENDOR_ID ); + config.setFeatureHypervVendorIdState( true ); + config.setFeatureKvmHiddenState( true ); + } + + // disable all software video devices if device passthrough debug mode is not enabled + if ( !args.isDebugDevicePassthroughEnabled() ) { + for ( Video videoDevice : config.getVideoDevices() ) { + videoDevice.disable(); + } + } + + // force SPICE graphics to listen on local address for looking-glass-client + for ( int i = 0; i < config.getGraphicSpiceDevices().size(); i++ ) { + final GraphicsSpice graphicsSpiceDevice = config.getGraphicSpiceDevices().get( i ); + graphicsSpiceDevice.setListenType( ListenType.ADDRESS ); + graphicsSpiceDevice.setListenAddress( GraphicsSpice.DEFAULT_ADDRESS ); + graphicsSpiceDevice.setListenPort( GraphicsSpice.DEFAULT_PORT + i ); + } + } + } + + private int getFreeAddr( int[] inUse, HostdevPciDeviceAddress pciDeviceAddress ) + { + // Use first free one. Usually 00:02:00 is primary VGA + int devAddr; + int firstFree = -1; + int lookup = (pciDeviceAddress.getPciDomain() << 16) + | (pciDeviceAddress.getPciBus() << 8) + | (pciDeviceAddress.getPciDevice()); + for ( devAddr = 0; devAddr < inUse.length; ++devAddr ) { + if ( firstFree == -1 && inUse[devAddr] == 0 ) { + firstFree = devAddr; + } else if ( inUse[devAddr] == lookup ) { + return devAddr; + } + } + inUse[firstFree] = lookup; + return firstFree; + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/test/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuGpuPassthroughNvidiaTest.java b/core/modules/qemu/runvirt-plugin-qemu/src/test/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuGpuPassthroughNvidiaTest.java index 4c021363..ae9f531b 100644 --- a/core/modules/qemu/runvirt-plugin-qemu/src/test/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuGpuPassthroughNvidiaTest.java +++ b/core/modules/qemu/runvirt-plugin-qemu/src/test/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuGpuPassthroughNvidiaTest.java @@ -29,7 +29,7 @@ import org.openslx.libvirt.xml.LibvirtXmlValidationException; import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs; import org.openslx.virtualization.configuration.transformation.TransformationException; -class TransformationSpecificQemuGpuPassthroughNvidiaStub extends TransformationSpecificQemuGpuPassthroughNvidia +class TransformationSpecificQemuGpuPassthroughNvidiaStub extends TransformationSpecificQemuPciPassthrough { final String capabilityFileName; @@ -87,13 +87,13 @@ public class TransformationSpecificQemuGpuPassthroughNvidiaTest assertEquals( Shmem.Model.IVSHMEM_PLAIN, shmemDevice.getModel() ); assertEquals( BigInteger.valueOf( 67108864 ).toString(), shmemDevice.getSize().toString() ); - if ( TransformationSpecificQemuGpuPassthroughNvidia.NVIDIA_PATCH ) { - assertEquals( TransformationSpecificQemuGpuPassthroughNvidia.HYPERV_VENDOR_ID, + if ( TransformationSpecificQemuPciPassthrough.NVIDIA_PATCH ) { + assertEquals( TransformationSpecificQemuPciPassthrough.HYPERV_VENDOR_ID, config.getFeatureHypervVendorIdValue() ); assertTrue( config.isFeatureHypervVendorIdStateOn() ); assertTrue( config.isFeatureKvmHiddenStateOn() ); } else { - assertNotEquals( TransformationSpecificQemuGpuPassthroughNvidia.HYPERV_VENDOR_ID, + assertNotEquals( TransformationSpecificQemuPciPassthrough.HYPERV_VENDOR_ID, config.getFeatureHypervVendorIdValue() ); assertFalse( config.isFeatureHypervVendorIdStateOn() ); assertFalse( config.isFeatureKvmHiddenStateOn() ); @@ -136,7 +136,7 @@ public class TransformationSpecificQemuGpuPassthroughNvidiaTest assertNotNull( shmemDevices ); assertEquals( 0, shmemDevices.size() ); - assertNotEquals( TransformationSpecificQemuGpuPassthroughNvidia.HYPERV_VENDOR_ID, + assertNotEquals( TransformationSpecificQemuPciPassthrough.HYPERV_VENDOR_ID, config.getFeatureHypervVendorIdValue() ); assertFalse( config.isFeatureHypervVendorIdStateOn() ); assertFalse( config.isFeatureKvmHiddenStateOn() ); -- cgit v1.2.3-55-g7522