]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
rework kvm_user_version cache mechanism
[qemu-server.git] / PVE / QemuServer.pm
index 4a570883b943dec233741e918a893526ff7a0fd2..220af7391426c66af62b28833594380d5d373eb1 100644 (file)
@@ -2,6 +2,7 @@ package PVE::QemuServer;
 
 use strict;
 use warnings;
+
 use POSIX;
 use IO::Handle;
 use IO::Select;
@@ -20,6 +21,7 @@ use JSON;
 use Fcntl;
 use PVE::SafeSyslog;
 use Storable qw(dclone);
+use MIME::Base64;
 use PVE::Exception qw(raise raise_param_exc);
 use PVE::Storage;
 use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline dir_glob_foreach $IPV6RE);
@@ -30,20 +32,30 @@ use PVE::ProcFSTools;
 use PVE::QemuConfig;
 use PVE::QMPClient;
 use PVE::RPCEnvironment;
+use PVE::GuestHelpers;
 use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr);
 use PVE::QemuServer::Memory;
 use PVE::QemuServer::USB qw(parse_usb_device);
 use PVE::QemuServer::Cloudinit;
+use PVE::SysFSTools;
 use PVE::Systemd;
 use Time::HiRes qw(gettimeofday);
 use File::Copy qw(copy);
 use URI::Escape;
 
 my $EDK2_FW_BASE = '/usr/share/pve-edk2-firmware/';
-my $OVMF_CODE = "$EDK2_FW_BASE/OVMF_CODE.fd";
-my $OVMF_VARS = "$EDK2_FW_BASE/OVMF_VARS.fd";
+my $OVMF = {
+    x86_64 => [
+       "$EDK2_FW_BASE/OVMF_CODE.fd",
+       "$EDK2_FW_BASE/OVMF_VARS.fd"
+    ],
+    aarch64 => [
+       "$EDK2_FW_BASE/AAVMF_CODE.fd",
+       "$EDK2_FW_BASE/AAVMF_VARS.fd"
+    ],
+};
 
-my $qemu_snap_storage = {rbd => 1, sheepdog => 1};
+my $qemu_snap_storage = { rbd => 1 };
 
 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
 
@@ -67,12 +79,6 @@ PVE::JSONSchema::register_standard_option('pve-qm-stateuri', {
     optional => 1,
 });
 
-PVE::JSONSchema::register_standard_option('pve-snapshot-name', {
-    description => "The name of the snapshot.",
-    type => 'string', format => 'pve-configid',
-    maxLength => 40,
-});
-
 PVE::JSONSchema::register_standard_option('pve-qm-image-format', {
     type => 'string',
     enum => [qw(raw cow qcow qed qcow2 vmdk cloop)],
@@ -110,8 +116,6 @@ mkdir $var_run_tmpdir;
 my $lock_dir = "/var/lock/qemu-server";
 mkdir $lock_dir;
 
-my $pcisysfs = "/sys/bus/pci";
-
 my $cpu_vendor_list = {
     # Intel CPUs
     486 => 'GenuineIntel',
@@ -163,7 +167,20 @@ my $cpu_vendor_list = {
     max => 'default',
 };
 
-my $cpu_flag = qr/[+-](pcid|spec-ctrl|ibpb|ssbd|virt-ssbd|amd-ssbd|amd-no-ssb|pdpe1gb)/;
+my @supported_cpu_flags = (
+    'pcid',
+    'spec-ctrl',
+    'ibpb',
+    'ssbd',
+    'virt-ssbd',
+    'amd-ssbd',
+    'amd-no-ssb',
+    'pdpe1gb',
+    'md-clear',
+    'hv-tlbflush',
+    'hv-evmcs'
+);
+my $cpu_flag = qr/[+-](@{[join('|', @supported_cpu_flags)]})/;
 
 my $cpu_fmt = {
     cputype => {
@@ -179,10 +196,17 @@ my $cpu_fmt = {
        optional => 1,
        default => 0
     },
+    'hv-vendor-id' => {
+       type => 'string',
+       pattern => qr/[a-zA-Z0-9]{1,12}/,
+       format_description => 'vendor-id',
+       description => 'The Hyper-V vendor ID. Some drivers or programs inside Windows guests need a specific ID.',
+       optional => 1,
+    },
     flags => {
        description => "List of additional CPU flags separated by ';'."
                     . " Use '+FLAG' to enable, '-FLAG' to disable a flag."
-                    . " Currently supported flags: 'pcid', 'spec-ctrl', 'ibpb', 'ssbd', 'virt-ssbd', 'amd-ssbd', 'amd-no-ssb', 'pdpe1gb'.",
+                    . " Currently supported flags: @{[join(', ', @supported_cpu_flags)]}.",
        format_description => '+FLAG[;-FLAG...]',
        type => 'string',
        pattern => qr/$cpu_flag(;$cpu_flag)*/,
@@ -230,7 +254,7 @@ my $vga_fmt = {
        default => 'std',
        optional => 1,
        default_key => 1,
-       enum => [qw(cirrus qxl qxl2 qxl3 qxl4 serial0 serial1 serial2 serial3 std virtio vmware)],
+       enum => [qw(cirrus qxl qxl2 qxl3 qxl4 none serial0 serial1 serial2 serial3 std virtio vmware)],
     },
     memory => {
        description => "Sets the VGA memory (in MiB). Has no effect with serial display.",
@@ -241,6 +265,36 @@ my $vga_fmt = {
     },
 };
 
+my $ivshmem_fmt = {
+    size => {
+       type => 'integer',
+       minimum => 1,
+       description => "The size of the file in MB.",
+    },
+    name => {
+       type => 'string',
+       pattern => '[a-zA-Z0-9\-]+',
+       optional => 1,
+       format_description => 'string',
+       description => "The name of the file. Will be prefixed with 'pve-shm-'. Default is the VMID. Will be deleted when the VM is stopped.",
+    },
+};
+
+my $audio_fmt = {
+    device => {
+       type => 'string',
+       enum => [qw(ich9-intel-hda intel-hda AC97)],
+       description =>  "Configure an audio device."
+    },
+    driver =>  {
+       type => 'string',
+       enum => ['spice'],
+       default => 'spice',
+       optional => 1,
+       description => "Driver backend for the audio device."
+    },
+};
+
 my $confdesc = {
     onboot => {
        optional => 1,
@@ -270,7 +324,7 @@ my $confdesc = {
        optional => 1,
        type => 'string',
        description => "Lock/unlock the VM.",
-       enum => [qw(migrate backup snapshot rollback)],
+       enum => [qw(backup clone create migrate rollback snapshot snapshot-delete suspending suspended)],
     },
     cpulimit => {
        optional => 1,
@@ -566,7 +620,7 @@ EODESCR
     smbios1 => {
        description => "Specify SMBIOS type 1 fields.",
        type => 'string', format => 'pve-qm-smbios1',
-       maxLength => 256,
+       maxLength => 512,
        optional => 1,
     },
     protection => {
@@ -600,7 +654,50 @@ EODESCR
        default => "1 (autogenerated)",
        optional => 1,
     },
+    hookscript => {
+       type => 'string',
+       format => 'pve-volume-id',
+       optional => 1,
+       description => "Script that will be executed during various steps in the vms lifetime.",
+    },
+    ivshmem => {
+       type => 'string',
+       format => $ivshmem_fmt,
+       description => "Inter-VM shared memory. Useful for direct communication between VMs, or to the host.",
+       optional => 1,
+    },
+    audio0 => {
+       type => 'string',
+       format => $audio_fmt,
+       description => "Configure a audio device, useful in combination with QXL/Spice.",
+       optional => 1
+    },
+};
+
+my $cicustom_fmt = {
+    meta => {
+       type => 'string',
+       optional => 1,
+       description => 'Specify a custom file containing all meta data passed to the VM via cloud-init. This is provider specific meaning configdrive2 and nocloud differ.',
+       format => 'pve-volume-id',
+       format_description => 'volume',
+    },
+    network => {
+       type => 'string',
+       optional => 1,
+       description => 'Specify a custom file containing all network data passed to the VM via cloud-init.',
+       format => 'pve-volume-id',
+       format_description => 'volume',
+    },
+    user => {
+       type => 'string',
+       optional => 1,
+       description => 'Specify a custom file containing all user data passed to the VM via cloud-init.',
+       format => 'pve-volume-id',
+       format_description => 'volume',
+    },
 };
+PVE::JSONSchema::register_format('pve-qm-cicustom', $cicustom_fmt);
 
 my $confdesc_cloudinit = {
     citype => {
@@ -619,6 +716,12 @@ my $confdesc_cloudinit = {
        type => 'string',
        description => 'cloud-init: Password to assign the user. Using this is generally not recommended. Use ssh keys instead. Also note that older cloud-init versions do not support hashed passwords.',
     },
+    cicustom => {
+       optional => 1,
+       type => 'string',
+       description => 'cloud-init: Specify custom files to replace the automatically generated ones at start.',
+       format => 'pve-qm-cicustom',
+    },
     searchdomain => {
        optional => 1,
        type => 'string',
@@ -729,13 +832,9 @@ The DHCP server assign addresses to the guest starting from 10.0.2.15.
 __EOD__
 
 my $net_fmt = {
-    macaddr => {
-       type => 'string',
-       pattern => qr/[0-9a-f]{2}(?::[0-9a-f]{2}){5}/i,
+    macaddr  => get_standard_option('mac-addr', {
        description => "MAC address. That address must be unique withing your network. This is automatically generated if not specified.",
-       format_description => "XX:XX:XX:XX:XX:XX",
-       optional => 1,
-    },
+    }),
     model => {
        type => 'string',
        description => "Network Card Model. The 'virtio' model provides the best performance with very low CPU overhead. If your guest does not support this driver, it is usually best to use 'e1000'.",
@@ -1031,6 +1130,16 @@ my %ssd_fmt = (
     },
 );
 
+my %wwn_fmt = (
+    wwn => {
+       type => 'string',
+       pattern => qr/^(0x)[0-9a-fA-F]{16}/,
+       format_description => 'wwn',
+       description => "The drive's worldwide name, encoded as 16 bytes hex string, prefixed by '0x'.",
+       optional => 1,
+    },
+);
+
 my $add_throttle_desc = sub {
     my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
     my $d = {
@@ -1079,6 +1188,7 @@ my $ide_fmt = {
     %drivedesc_base,
     %model_fmt,
     %ssd_fmt,
+    %wwn_fmt,
 };
 PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
 
@@ -1095,6 +1205,7 @@ my $scsi_fmt = {
     %queues_fmt,
     %scsiblock_fmt,
     %ssd_fmt,
+    %wwn_fmt,
 };
 my $scsidesc = {
     optional => 1,
@@ -1106,6 +1217,7 @@ PVE::JSONSchema::register_standard_option("pve-qm-scsi", $scsidesc);
 my $sata_fmt = {
     %drivedesc_base,
     %ssd_fmt,
+    %wwn_fmt,
 };
 my $satadesc = {
     optional => 1,
@@ -1132,6 +1244,7 @@ my $alldrive_fmt = {
     %queues_fmt,
     %scsiblock_fmt,
     %ssd_fmt,
+    %wwn_fmt,
 };
 
 my $efidisk_fmt = {
@@ -1195,8 +1308,7 @@ my $usbdesc = {
 };
 PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
 
-# NOTE: the match-groups of this regex are used in parse_hostpci
-my $PCIRE = qr/([a-f0-9]{2}:[a-f0-9]{2})(?:\.([a-f0-9]))?/;
+my $PCIRE = qr/[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?/;
 my $hostpci_fmt = {
     host => {
        default_key => 1,
@@ -1204,7 +1316,7 @@ my $hostpci_fmt = {
        pattern => qr/$PCIRE(;$PCIRE)*/,
        format_description => 'HOSTPCIID[;HOSTPCIID2...]',
        description => <<EODESCR,
-Host PCI device pass through. The PCI ID of a host's PCI device or a list 
+Host PCI device pass through. The PCI ID of a host's PCI device or a list
 of PCI virtual functions of the host. HOSTPCIID syntax is:
 
 'bus:dev.func' (hexadecimal numbers)
@@ -1237,6 +1349,17 @@ EODESCR
        optional => 1,
        default => 0,
     },
+    'mdev' => {
+       type => 'string',
+        format_description => 'string',
+       pattern => '[^/\.:]+',
+       optional => 1,
+       description => <<EODESCR
+The type of mediated device to use.
+An instance of this type will be created on startup of the VM and
+will be cleaned up when the VM stops.
+EODESCR
+    }
 };
 PVE::JSONSchema::register_format('pve-qm-hostpci', $hostpci_fmt);
 
@@ -1247,7 +1370,7 @@ my $hostpcidesc = {
        verbose_description =>  <<EODESCR,
 Map host PCI devices into guest.
 
-NOTE: This option allows direct access to host hardware. So it is no longer 
+NOTE: This option allows direct access to host hardware. So it is no longer
 possible to migrate such machines - use with special care.
 
 CAUTION: Experimental! User reported problems with this option.
@@ -1348,29 +1471,39 @@ sub kvm_version {
     return $kvm_api_version;
 }
 
-my $kvm_user_version;
+my $kvm_user_version = {};
+my $kvm_mtime = {};
 
 sub kvm_user_version {
+    my ($binary) = @_;
 
-    return $kvm_user_version if $kvm_user_version;
+    $binary //= get_command_for_arch(get_host_arch()); # get the native arch by default
+    my $st = stat($binary);
 
-    $kvm_user_version = 'unknown';
+    my $cachedmtime = $kvm_mtime->{$binary} // -1;
+    return $kvm_user_version->{$binary} if $kvm_user_version->{$binary} &&
+       $cachedmtime == $st->mtime;
+
+    $kvm_user_version->{$binary} = 'unknown';
+    $kvm_mtime->{$binary} = $st->mtime;
 
     my $code = sub {
        my $line = shift;
        if ($line =~ m/^QEMU( PC)? emulator version (\d+\.\d+(\.\d+)?)(\.\d+)?[,\s]/) {
-           $kvm_user_version = $2;
+           $kvm_user_version->{$binary} = $2;
        }
     };
 
-    eval { run_command("kvm -version", outfunc => $code); };
+    eval { run_command([$binary, '--version'], outfunc => $code); };
     warn $@ if $@;
 
-    return $kvm_user_version;
+    return $kvm_user_version->{$binary};
 
 }
 
-my $kernel_has_vhost_net = -c '/dev/vhost-net';
+sub kernel_has_vhost_net {
+    return -c '/dev/vhost-net';
+}
 
 sub valid_drive_names {
     # order is important - used to autoselect boot disk
@@ -1682,24 +1815,37 @@ sub machine_type_is_q35 {
 }
 
 sub print_tabletdevice_full {
-    my ($conf) = @_;
+    my ($conf, $arch) = @_;
 
     my $q35 = machine_type_is_q35($conf);
 
     # we use uhci for old VMs because tablet driver was buggy in older qemu
-    my $usbbus = $q35 ? "ehci" : "uhci";
+    my $usbbus;
+    if (machine_type_is_q35($conf) || $arch eq 'aarch64') {
+       $usbbus = 'ehci';
+    } else {
+       $usbbus = 'uhci';
+    }
 
     return "usb-tablet,id=tablet,bus=$usbbus.0,port=1";
 }
 
+sub print_keyboarddevice_full {
+    my ($conf, $arch, $machine) = @_;
+
+    return undef if $arch ne 'aarch64';
+
+    return "usb-kbd,id=keyboard,bus=ehci.0,port=2";
+}
+
 sub print_drivedevice_full {
-    my ($storecfg, $conf, $vmid, $drive, $bridges) = @_;
+    my ($storecfg, $conf, $vmid, $drive, $bridges, $arch, $machine_type) = @_;
 
     my $device = '';
     my $maxdev = 0;
 
     if ($drive->{interface} eq 'virtio') {
-       my $pciaddr = print_pci_addr("$drive->{interface}$drive->{index}", $bridges);
+       my $pciaddr = print_pci_addr("$drive->{interface}$drive->{index}", $bridges, $arch, $machine_type);
        $device = "virtio-blk-pci,drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}$pciaddr";
        $device .= ",iothread=iothread-$drive->{interface}$drive->{index}" if $drive->{iothread};
     } elsif ($drive->{interface} eq 'scsi') {
@@ -1738,6 +1884,7 @@ sub print_drivedevice_full {
        if ($drive->{ssd} && ($devicetype eq 'block' || $devicetype eq 'hd')) {
            $device .= ",rotation_rate=1";
        }
+       $device .= ",wwn=$drive->{wwn}" if $drive->{wwn};
 
     } elsif ($drive->{interface} eq 'ide' || $drive->{interface} eq 'sata') {
        my $maxdev = ($drive->{interface} eq 'sata') ? $MAX_SATA_DISKS : 2;
@@ -1762,6 +1909,7 @@ sub print_drivedevice_full {
                $device .= ",rotation_rate=1";
            }
        }
+       $device .= ",wwn=$drive->{wwn}" if $drive->{wwn};
     } elsif ($drive->{interface} eq 'usb') {
        die "implement me";
        #  -device ide-drive,bus=ide.1,unit=0,drive=drive-ide0-1-0,id=ide0-1-0
@@ -1800,7 +1948,7 @@ sub print_drive_full {
     my $path;
     my $volid = $drive->{file};
     my $format;
-    
+
     if (drive_is_cdrom($drive)) {
        $path = get_iso_path($storecfg, $vmid, $volid);
     } else {
@@ -1888,7 +2036,7 @@ sub print_drive_full {
 }
 
 sub print_netdevice_full {
-    my ($vmid, $conf, $net, $netid, $bridges, $use_old_bios_files) = @_;
+    my ($vmid, $conf, $net, $netid, $bridges, $use_old_bios_files, $arch, $machine_type) = @_;
 
     my $bootorder = $conf->{boot} || $confdesc->{boot}->{default};
 
@@ -1897,7 +2045,7 @@ sub print_netdevice_full {
          $device = 'virtio-net-pci';
      };
 
-    my $pciaddr = print_pci_addr("$netid", $bridges);
+    my $pciaddr = print_pci_addr("$netid", $bridges, $arch, $machine_type);
     my $tmpstr = "$device,mac=$net->{macaddr},netdev=$netid$pciaddr,id=$netid";
     if ($net->{queues} && $net->{queues} > 1 && $net->{model} eq 'virtio'){
        #Consider we have N queues, the number of vectors needed is 2*N + 2 (plus one config interrupt and control vq)
@@ -1926,7 +2074,7 @@ sub print_netdevice_full {
 }
 
 sub print_netdev_full {
-    my ($vmid, $conf, $net, $netid, $hotplug) = @_;
+    my ($vmid, $conf, $arch, $net, $netid, $hotplug) = @_;
 
     my $i = '';
     if ($netid =~ m/^net(\d+)$/) {
@@ -1942,7 +2090,9 @@ sub print_netdev_full {
         if length($ifname) >= 16;
 
     my $vhostparam = '';
-    $vhostparam = ',vhost=on' if $kernel_has_vhost_net && $net->{model} eq 'virtio';
+    if (is_native($arch)) {
+       $vhostparam = ',vhost=on' if kernel_has_vhost_net() && $net->{model} eq 'virtio';
+    }
 
     my $vmname = $conf->{name} || "vm$vmid";
 
@@ -1988,9 +2138,12 @@ my $vga_map = {
 };
 
 sub print_vga_device {
-    my ($conf, $vga, $id, $qxlnum, $bridges) = @_;
+    my ($conf, $vga, $arch, $machine, $id, $qxlnum, $bridges) = @_;
 
     my $type = $vga_map->{$vga->{type}};
+    if ($arch eq 'aarch64' && defined($type) && $type eq 'virtio-vga') {
+       $type = 'virtio-gpu';
+    }
     my $vgamem_mb = $vga->{memory};
     if ($qxlnum) {
        $type = $id ? 'qxl' : 'qxl-vga';
@@ -2021,9 +2174,9 @@ sub print_vga_device {
 
     if ($q35 && $vgaid eq 'vga') {
        # the first display uses pcie.0 bus on q35 machines
-       $pciaddr = print_pcie_addr($vgaid, $bridges);
+       $pciaddr = print_pcie_addr($vgaid, $bridges, $arch, $machine);
     } else {
-       $pciaddr = print_pci_addr($vgaid, $bridges);
+       $pciaddr = print_pci_addr($vgaid, $bridges, $arch, $machine);
     }
 
     return "$type,id=${vgaid}${memory}${pciaddr}";
@@ -2076,16 +2229,12 @@ sub parse_hostpci {
     my @idlist = split(/;/, $res->{host});
     delete $res->{host};
     foreach my $id (@idlist) {
-       if ($id =~ /^$PCIRE$/) {
-           if (defined($2)) {
-               push @{$res->{pciid}}, { id => $1, function => $2 };
-           } else {
-               my $pcidevices = lspci($1);
-               $res->{pciid} = $pcidevices->{$1};
-           }
-       } else {
-           # should have been caught by parse_property_string already
-           die "failed to parse PCI id: $id\n";
+       if ($id =~ m/\./) { # full id 00:00.1
+           push @{$res->{pciid}}, {
+               id => $id,
+           };
+       } else { # partial id 00:00
+           $res->{pciid} = PVE::SysFSTools::lspci($id);
        }
     }
     return $res;
@@ -2252,7 +2401,7 @@ sub vmconfig_cleanup_pending {
     return $changes;
 }
 
-# smbios: [manufacturer=str][,product=str][,version=str][,serial=str][,uuid=uuid][,sku=str][,family=str]
+# smbios: [manufacturer=str][,product=str][,version=str][,serial=str][,uuid=uuid][,sku=str][,family=str][,base64=bool]
 my $smbios1_fmt = {
     uuid => {
        type => 'string',
@@ -2263,46 +2412,51 @@ my $smbios1_fmt = {
     },
     version => {
        type => 'string',
-       pattern => '\S+',
-       format_description => 'string',
+       pattern => '[A-Za-z0-9+\/]+={0,2}',
+       format_description => 'Base64 encoded string',
         description => "Set SMBIOS1 version.",
        optional => 1,
     },
     serial => {
        type => 'string',
-       pattern => '\S+',
-       format_description => 'string',
+       pattern => '[A-Za-z0-9+\/]+={0,2}',
+       format_description => 'Base64 encoded string',
         description => "Set SMBIOS1 serial number.",
        optional => 1,
     },
     manufacturer => {
        type => 'string',
-       pattern => '\S+',
-       format_description => 'string',
+       pattern => '[A-Za-z0-9+\/]+={0,2}',
+       format_description => 'Base64 encoded string',
         description => "Set SMBIOS1 manufacturer.",
        optional => 1,
     },
     product => {
        type => 'string',
-       pattern => '\S+',
-       format_description => 'string',
+       pattern => '[A-Za-z0-9+\/]+={0,2}',
+       format_description => 'Base64 encoded string',
         description => "Set SMBIOS1 product ID.",
        optional => 1,
     },
     sku => {
        type => 'string',
-       pattern => '\S+',
-       format_description => 'string',
+       pattern => '[A-Za-z0-9+\/]+={0,2}',
+       format_description => 'Base64 encoded string',
         description => "Set SMBIOS1 SKU string.",
        optional => 1,
     },
     family => {
        type => 'string',
-       pattern => '\S+',
-       format_description => 'string',
+       pattern => '[A-Za-z0-9+\/]+={0,2}',
+       format_description => 'Base64 encoded string',
         description => "Set SMBIOS1 family string.",
        optional => 1,
     },
+    base64 => {
+       type => 'boolean',
+       description => 'Flag to indicate that the SMBIOS values are base64 encoded',
+       optional => 1,
+    },
 };
 
 sub parse_smbios1 {
@@ -2429,15 +2583,6 @@ sub check_type {
     }
 }
 
-sub check_iommu_support{
-    #fixme : need to check IOMMU support
-    #http://www.linux-kvm.org/page/How_to_assign_devices_with_VT-d_in_KVM
-
-    my $iommu=1;
-    return $iommu;
-
-}
-
 sub touch_config {
     my ($vmid) = @_;
 
@@ -2749,21 +2894,23 @@ sub config_list {
 sub check_local_resources {
     my ($conf, $noerr) = @_;
 
-    my $loc_res = 0;
+    my @loc_res = ();
+
+    push @loc_res, "hostusb" if $conf->{hostusb}; # old syntax
+    push @loc_res, "hostpci" if $conf->{hostpci}; # old syntax
 
-    $loc_res = 1 if $conf->{hostusb}; # old syntax
-    $loc_res = 1 if $conf->{hostpci}; # old syntax
+    push @loc_res, "ivshmem" if $conf->{ivshmem};
 
     foreach my $k (keys %$conf) {
        next if $k =~ m/^usb/ && ($conf->{$k} eq 'spice');
        # sockets are safe: they will recreated be on the target side post-migrate
        next if $k =~ m/^serial/ && ($conf->{$k} eq 'socket');
-       $loc_res = 1 if $k =~ m/^(usb|hostpci|serial|parallel)\d+$/;
+       push @loc_res, $k if $k =~ m/^(usb|hostpci|serial|parallel)\d+$/;
     }
 
-    die "VM uses local resources\n" if $loc_res && !$noerr;
+    die "VM uses local resources\n" if scalar @loc_res && !$noerr;
 
-    return $loc_res;
+    return \@loc_res;
 }
 
 # check if used storages are available on all nodes (use by migrate)
@@ -2819,6 +2966,45 @@ sub shared_nodes {
     return $nodehash
 }
 
+sub check_local_storage_availability {
+    my ($conf, $storecfg) = @_;
+
+    my $nodelist = PVE::Cluster::get_nodelist();
+    my $nodehash = { map { $_ => {} } @$nodelist };
+
+    foreach_drive($conf, sub {
+       my ($ds, $drive) = @_;
+
+       my $volid = $drive->{file};
+       return if !$volid;
+
+       my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+       if ($storeid) {
+           my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+
+           if ($scfg->{disable}) {
+               foreach my $node (keys %$nodehash) {
+                   $nodehash->{$node}->{unavailable_storages}->{$storeid} = 1;
+               }
+           } elsif (my $avail = $scfg->{nodes}) {
+               foreach my $node (keys %$nodehash) {
+                   if (!$avail->{$node}) {
+                       $nodehash->{$node}->{unavailable_storages}->{$storeid} = 1;
+                   }
+               }
+           }
+       }
+    });
+
+    foreach my $node (values %$nodehash) {
+       if (my $unavail = $node->{unavailable_storages}) {
+           $node->{unavailable_storages} = [ sort keys %$unavail ];
+       }
+    }
+
+    return $nodehash
+}
+
 sub check_cmdline {
     my ($pidfile, $pid) = @_;
 
@@ -2830,7 +3016,7 @@ sub check_cmdline {
        my @param = split(/\0/, $line);
 
        my $cmd = $param[0];
-       return if !$cmd || ($cmd !~ m|kvm$| && $cmd !~ m|qemu-system-x86_64$|);
+       return if !$cmd || ($cmd !~ m|kvm$| && $cmd !~ m@(?:^|/)qemu-system-[^/]+$@);
 
        for (my $i = 0; $i < scalar (@param); $i++) {
            my $p = $param[$i];
@@ -2961,6 +3147,11 @@ our $vmstatus_return_properties = {
        type => 'number',
        optional => 1,
     },
+    lock => {
+       description => "The current config lock, if any.",
+       type => 'string',
+       optional => 1,
+    }
 };
 
 my $last_proc_pid_stat;
@@ -3031,6 +3222,7 @@ sub vmstatus {
         $d->{template} = PVE::QemuConfig->is_template($conf);
 
        $d->{serial} = 1 if conf_has_serial($conf);
+       $d->{lock} = $conf->{lock} if $conf->{lock};
 
        $res->{$vmid} = $d;
     }
@@ -3190,7 +3382,7 @@ sub foreach_volid {
     my $volhash = {};
 
     my $test_volid = sub {
-       my ($volid, $is_cdrom, $replicate, $shared, $snapname) = @_;
+       my ($volid, $is_cdrom, $replicate, $shared, $snapname, $size) = @_;
 
        return if !$volid;
 
@@ -3208,11 +3400,12 @@ sub foreach_volid {
 
        $volhash->{$volid}->{referenced_in_snapshot}->{$snapname} = 1
            if defined($snapname);
+       $volhash->{$volid}->{size} = $size if $size;
     };
 
     foreach_drive($conf, sub {
        my ($ds, $drive) = @_;
-       $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, undef);
+       $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, undef, $drive->{size});
     });
 
     foreach my $snapname (keys %{$conf->{snapshots}}) {
@@ -3241,6 +3434,24 @@ sub conf_has_serial {
     return 0;
 }
 
+sub conf_has_audio {
+    my ($conf, $id) = @_;
+
+    $id //= 0;
+    my $audio = $conf->{"audio$id"};
+    return undef if !defined($audio);
+
+    my $audioproperties = PVE::JSONSchema::parse_property_string($audio_fmt, $audio);
+    my $audiodriver = $audioproperties->{driver} // 'spice';
+
+    return {
+       dev => $audioproperties->{device},
+       dev_id => "audiodev$id",
+       backend => $audiodriver,
+       backend_id => "$audiodriver-backend${id}",
+    };
+}
+
 sub vga_conf_has_spice {
     my ($vga) = @_;
 
@@ -3275,6 +3486,84 @@ sub get_basic_machine_info {
     return ($arch, $machine);
 }
 
+sub get_ovmf_files($) {
+    my ($arch) = @_;
+
+    my $ovmf = $OVMF->{$arch}
+       or die "no OVMF images known for architecture '$arch'\n";
+
+    return @$ovmf;
+}
+
+my $Arch2Qemu = {
+    aarch64 => '/usr/bin/qemu-system-aarch64',
+    x86_64 => '/usr/bin/qemu-system-x86_64',
+};
+sub get_command_for_arch($) {
+    my ($arch) = @_;
+    return '/usr/bin/kvm' if is_native($arch);
+
+    my $cmd = $Arch2Qemu->{$arch}
+       or die "don't know how to emulate architecture '$arch'\n";
+    return $cmd;
+}
+
+sub get_cpu_options {
+    my ($conf, $arch, $kvm, $machine_type, $kvm_off, $kvmver, $winversion, $gpu_passthrough) = @_;
+
+    my $cpuFlags = [];
+    my $ostype = $conf->{ostype};
+
+    my $cpu = $kvm ? "kvm64" : "qemu64";
+    if ($arch eq 'aarch64') {
+       $cpu = 'cortex-a57';
+    }
+    my $hv_vendor_id;
+    if (my $cputype = $conf->{cpu}) {
+       my $cpuconf = PVE::JSONSchema::parse_property_string($cpu_fmt, $cputype)
+           or die "Cannot parse cpu description: $cputype\n";
+       $cpu = $cpuconf->{cputype};
+       $kvm_off = 1 if $cpuconf->{hidden};
+       $hv_vendor_id = $cpuconf->{'hv-vendor-id'};
+
+       if (defined(my $flags = $cpuconf->{flags})) {
+           push @$cpuFlags, split(";", $flags);
+       }
+    }
+
+    push @$cpuFlags , '+lahf_lm' if $cpu eq 'kvm64' && $arch eq 'x86_64';
+
+    push @$cpuFlags , '-x2apic'
+       if $conf->{ostype} && $conf->{ostype} eq 'solaris';
+
+    push @$cpuFlags, '+sep' if $cpu eq 'kvm64' || $cpu eq 'kvm32';
+
+    push @$cpuFlags, '-rdtscp' if $cpu =~ m/^Opteron/;
+
+    if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3) && $arch eq 'x86_64') {
+
+       push @$cpuFlags , '+kvm_pv_unhalt' if $kvm;
+       push @$cpuFlags , '+kvm_pv_eoi' if $kvm;
+    }
+
+    add_hyperv_enlightenments($cpuFlags, $winversion, $machine_type, $kvmver, $conf->{bios}, $gpu_passthrough, $hv_vendor_id) if $kvm;
+
+    push @$cpuFlags, 'enforce' if $cpu ne 'host' && $kvm && $arch eq 'x86_64';
+
+    push @$cpuFlags, 'kvm=off' if $kvm_off;
+
+    if (my $cpu_vendor = $cpu_vendor_list->{$cpu}) {
+       push @$cpuFlags, "vendor=${cpu_vendor}"
+           if $cpu_vendor ne 'default';
+    } elsif ($arch ne 'aarch64') {
+       die "internal error"; # should not happen
+    }
+
+    $cpu .= "," . join(',', @$cpuFlags) if scalar(@$cpuFlags);
+
+    return ('-cpu', $cpu);
+}
+
 sub config_to_command {
     my ($storecfg, $vmid, $conf, $defaults, $forcemachine) = @_;
 
@@ -3282,17 +3571,17 @@ sub config_to_command {
     my $globalFlags = [];
     my $machineFlags = [];
     my $rtcFlags = [];
-    my $cpuFlags = [];
     my $devices = [];
     my $pciaddr = '';
     my $bridges = {};
-    my $kvmver = kvm_user_version();
     my $vernum = 0; # unknown
     my $ostype = $conf->{ostype};
     my $winversion = windows_version($ostype);
     my $kvm = $conf->{kvm};
 
     my ($arch, $machine_type) = get_basic_machine_info($conf, $forcemachine);
+    my $kvm_binary = get_command_for_arch($arch);
+    my $kvmver = kvm_user_version($kvm_binary);
     $kvm //= 1 if is_native($arch);
 
     if ($kvm) {
@@ -3318,7 +3607,7 @@ sub config_to_command {
     my $cpuunits = defined($conf->{cpuunits}) ?
             $conf->{cpuunits} : $defaults->{cpuunits};
 
-    push @$cmd, '/usr/bin/kvm';
+    push @$cmd, $kvm_binary;
 
     push @$cmd, '-id', $vmid;
 
@@ -3333,8 +3622,7 @@ sub config_to_command {
     push @$cmd, '-mon', "chardev=qmp,mode=control";
 
     if (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 12)) {
-       my $eventsocket = qmp_socket($vmid, 0, 'event');
-       push @$cmd, '-chardev', "socket,id=qmp-event,path=$eventsocket,server,nowait";
+       push @$cmd, '-chardev', "socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect=5";
        push @$cmd, '-mon', "chardev=qmp-event,mode=control";
     }
 
@@ -3343,15 +3631,35 @@ sub config_to_command {
     push @$cmd, '-daemonize';
 
     if ($conf->{smbios1}) {
-       push @$cmd, '-smbios', "type=1,$conf->{smbios1}";
+       my $smbios_conf = parse_smbios1($conf->{smbios1});
+       if ($smbios_conf->{base64}) {
+           # Do not pass base64 flag to qemu
+           delete $smbios_conf->{base64};
+           my $smbios_string = "";
+           foreach my $key (keys %$smbios_conf) {
+               my $value;
+               if ($key eq "uuid") {
+                   $value = $smbios_conf->{uuid}
+               } else {
+                   $value = decode_base64($smbios_conf->{$key});
+               }
+               # qemu accepts any binary data, only commas need escaping by double comma
+               $value =~ s/,/,,/g;
+               $smbios_string .= "," . $key . "=" . $value if $value;
+           }
+           push @$cmd, '-smbios', "type=1" . $smbios_string;
+       } else {
+           push @$cmd, '-smbios', "type=1,$conf->{smbios1}";
+       }
     }
 
     if ($conf->{vmgenid}) {
        push @$devices, '-device', 'vmgenid,guid='.$conf->{vmgenid};
     }
 
+    my ($ovmf_code, $ovmf_vars) = get_ovmf_files($arch);
     if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
-       die "uefi base image not found\n" if ! -f $OVMF_CODE;
+       die "uefi base image not found\n" if ! -f $ovmf_code;
 
        my $path;
        my $format;
@@ -3373,17 +3681,26 @@ sub config_to_command {
        } else {
            warn "no efidisk configured! Using temporary efivars disk.\n";
            $path = "/tmp/$vmid-ovmf.fd";
-           PVE::Tools::file_copy($OVMF_VARS, $path, -s $OVMF_VARS);
+           PVE::Tools::file_copy($ovmf_vars, $path, -s $ovmf_vars);
            $format = 'raw';
        }
 
-       push @$cmd, '-drive', "if=pflash,unit=0,format=raw,readonly,file=$OVMF_CODE";
+       push @$cmd, '-drive', "if=pflash,unit=0,format=raw,readonly,file=$ovmf_code";
        push @$cmd, '-drive', "if=pflash,unit=1,format=$format,id=drive-efidisk0,file=$path";
     }
 
+    # load q35 config
+    if ($q35) {
+       # we use different pcie-port hardware for qemu >= 4.0 for passthrough
+       if (qemu_machine_feature_enabled($machine_type, $kvmver, 4, 0)) {
+           push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35-4.0.cfg';
+       } else {
+           push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35.cfg';
+       }
+    }
 
     # add usb controllers
-    my @usbcontrollers = PVE::QemuServer::USB::get_usb_controllers($conf, $bridges, $q35, $usbdesc->{format}, $MAX_USB_DEVICES);
+    my @usbcontrollers = PVE::QemuServer::USB::get_usb_controllers($conf, $bridges, $arch, $machine_type, $usbdesc->{format}, $MAX_USB_DEVICES);
     push @$devices, @usbcontrollers if @usbcontrollers;
     my $vga = parse_vga($conf->{vga});
 
@@ -3391,7 +3708,9 @@ sub config_to_command {
     $vga->{type} = 'qxl' if $qxlnum;
 
     if (!$vga->{type}) {
-       if (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 9)) {
+       if ($arch eq 'aarch64') {
+           $vga->{type} = 'virtio';
+       } elsif (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 9)) {
            $vga->{type} = (!$winversion || $winversion >= 6) ? 'std' : 'cirrus';
        } else {
            $vga->{type} = ($winversion >= 6) ? 'std' : 'cirrus';
@@ -3408,7 +3727,11 @@ sub config_to_command {
        $tablet = 0 if $vga->{type} =~ m/^serial\d+$/; # disable if we use serial terminal (no vga card)
     }
 
-    push @$devices, '-device', print_tabletdevice_full($conf) if $tablet;
+    if ($tablet) {
+       push @$devices, '-device', print_tabletdevice_full($conf, $arch) if $tablet;
+       my $kbd = print_keyboarddevice_full($conf, $arch);
+       push @$devices, '-device', $kbd if defined($kbd);
+    }
 
     my $kvm_off = 0;
     my $gpu_passthrough;
@@ -3419,11 +3742,17 @@ sub config_to_command {
        next if !$d;
 
        my $pcie = $d->{pcie};
-       if($pcie){
+       if ($pcie) {
            die "q35 machine model is not enabled" if !$q35;
-           $pciaddr = print_pcie_addr("hostpci$i");
-       }else{
-           $pciaddr = print_pci_addr("hostpci$i", $bridges);
+           # win7 wants to have the pcie devices directly on the pcie bus
+           # instead of in the root port
+           if ($winversion == 7) {
+               $pciaddr = print_pcie_addr("hostpci${i}bus0");
+           } else {
+               $pciaddr = print_pcie_addr("hostpci$i");
+           }
+       } else {
+           $pciaddr = print_pci_addr("hostpci$i", $bridges, $arch, $machine_type);
        }
 
        my $rombar = defined($d->{rombar}) && !$d->{rombar} ? ',rombar=0' : '';
@@ -3433,7 +3762,7 @@ sub config_to_command {
        if ($d->{'x-vga'}) {
            $xvga = ',x-vga=on';
            $kvm_off = 1;
-           $vga->{type} = 'none';
+           $vga->{type} = 'none' if !defined($conf->{vga});
            $gpu_passthrough = 1;
 
            if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
@@ -3442,6 +3771,14 @@ sub config_to_command {
        }
        my $pcidevices = $d->{pciid};
        my $multifunction = 1 if @$pcidevices > 1;
+       my $sysfspath;
+       if ($d->{mdev} && scalar(@$pcidevices) == 1) {
+           my $id = $pcidevices->[0]->{id};
+           my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $i);
+           $sysfspath = "/sys/bus/pci/devices/0000:$id/$uuid";
+       } elsif ($d->{mdev}) {
+           warn "ignoring mediated device with multifunction device\n";
+       }
 
        my $j=0;
         foreach my $pcidevice (@$pcidevices) {
@@ -3450,7 +3787,13 @@ sub config_to_command {
            $id .= ".$j" if $multifunction;
            my $addr = $pciaddr;
            $addr .= ".$j" if $multifunction;
-           my $devicestr = "vfio-pci,host=$pcidevice->{id}.$pcidevice->{function},id=$id$addr";
+           my $devicestr = "vfio-pci";
+           if ($sysfspath) {
+               $devicestr .= ",sysfsdev=$sysfspath";
+           } else {
+               $devicestr .= ",host=$pcidevice->{id}";
+           }
+           $devicestr .= ",id=$id$addr";
 
            if($j == 0){
                $devicestr .= "$rombar$xvga";
@@ -3472,7 +3815,14 @@ sub config_to_command {
            if ($path eq 'socket') {
                my $socket = "/var/run/qemu-server/${vmid}.serial$i";
                push @$devices, '-chardev', "socket,id=serial$i,path=$socket,server,nowait";
-               push @$devices, '-device', "isa-serial,chardev=serial$i";
+               # On aarch64, serial0 is the UART device. Qemu only allows
+               # connecting UART devices via the '-serial' command line, as
+               # the device has a fixed slot on the hardware...
+               if ($arch eq 'aarch64' && $i == 0) {
+                   push @$devices, '-serial', "chardev:serial$i";
+               } else {
+                   push @$devices, '-device', "isa-serial,chardev=serial$i";
+               }
            } else {
                die "no such serial device\n" if ! -c $path;
                push @$devices, '-chardev', "tty,id=serial$i,path=$path";
@@ -3491,6 +3841,23 @@ sub config_to_command {
        }
     }
 
+    if (my $audio = conf_has_audio($conf)) {
+
+       my $audiopciaddr = print_pci_addr("audio0", $bridges, $arch, $machine_type);
+
+       my $id = $audio->{dev_id};
+       if ($audio->{dev} eq 'AC97') {
+           push @$devices, '-device', "AC97,id=${id}${audiopciaddr}";
+       } elsif ($audio->{dev} =~ /intel\-hda$/) {
+           push @$devices, '-device', "$audio->{dev},id=${id}${audiopciaddr}";
+           push @$devices, '-device', "hda-micro,id=${id}-codec0,bus=${id}.0,cad=0";
+           push @$devices, '-device', "hda-duplex,id=${id}-codec1,bus=${id}.0,cad=1";
+       } else {
+           die "unkown audio device '$audio->{dev}', implement me!";
+       }
+
+       push @$devices, '-audiodev', "$audio->{backend},id=$audio->{backend_id}";
+    }
 
     my $sockets = 1;
     $sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
@@ -3537,9 +3904,9 @@ sub config_to_command {
     push @$cmd, '-no-reboot' if  defined($conf->{reboot}) && $conf->{reboot} == 0;
 
     if ($vga->{type} && $vga->{type} !~ m/^serial\d+$/ && $vga->{type} ne 'none'){
-       push @$devices, '-device', print_vga_device($conf, $vga, undef, $qxlnum, $bridges);
+       push @$devices, '-device', print_vga_device($conf, $vga, $arch, $machine_type, undef, $qxlnum, $bridges);
        my $socket = vnc_socket($vmid);
-       push @$cmd,  '-vnc', "unix:$socket,x509,password";
+       push @$cmd,  '-vnc', "unix:$socket,password";
     } else {
        push @$cmd, '-vga', 'none' if $vga->{type} eq 'none';
        push @$cmd, '-nographic';
@@ -3574,69 +3941,23 @@ sub config_to_command {
        push @$machineFlags, "type=${machine_type}";
     }
 
-    if ($conf->{startdate}) {
+    if (($conf->{startdate}) && ($conf->{startdate} ne 'now')) {
        push @$rtcFlags, "base=$conf->{startdate}";
     } elsif ($useLocaltime) {
        push @$rtcFlags, 'base=localtime';
     }
 
-    my $cpu = $kvm ? "kvm64" : "qemu64";
-    if (my $cputype = $conf->{cpu}) {
-       my $cpuconf = PVE::JSONSchema::parse_property_string($cpu_fmt, $cputype)
-           or die "Cannot parse cpu description: $cputype\n";
-       $cpu = $cpuconf->{cputype};
-       $kvm_off = 1 if $cpuconf->{hidden};
-
-       if (defined(my $flags = $cpuconf->{flags})) {
-           push @$cpuFlags, split(";", $flags);
-       }
-    }
-
-    push @$cpuFlags , '+lahf_lm' if $cpu eq 'kvm64';
-
-    push @$cpuFlags , '-x2apic'
-       if $conf->{ostype} && $conf->{ostype} eq 'solaris';
-
-    push @$cpuFlags, '+sep' if $cpu eq 'kvm64' || $cpu eq 'kvm32';
-
-    push @$cpuFlags, '-rdtscp' if $cpu =~ m/^Opteron/;
-
-    if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
-
-       push @$cpuFlags , '+kvm_pv_unhalt' if $kvm;
-       push @$cpuFlags , '+kvm_pv_eoi' if $kvm;
-    }
-
-    add_hyperv_enlightenments($cpuFlags, $winversion, $machine_type, $kvmver, $conf->{bios}, $gpu_passthrough) if $kvm;
-
-    push @$cpuFlags, 'enforce' if $cpu ne 'host' && $kvm;
-
-    push @$cpuFlags, 'kvm=off' if $kvm_off;
-
-    my $cpu_vendor = $cpu_vendor_list->{$cpu} ||
-       die "internal error"; # should not happen
-
-    push @$cpuFlags, "vendor=${cpu_vendor}"
-       if $cpu_vendor ne 'default';
-
-    $cpu .= "," . join(',', @$cpuFlags) if scalar(@$cpuFlags);
-
-    push @$cmd, '-cpu', $cpu;
+    push @$cmd, get_cpu_options($conf, $arch, $kvm, $machine_type, $kvm_off, $kvmver, $winversion, $gpu_passthrough);
 
     PVE::QemuServer::Memory::config($conf, $vmid, $sockets, $cores, $defaults, $hotplug_features, $cmd);
-    
+
     push @$cmd, '-S' if $conf->{freeze};
 
     push @$cmd, '-k', $conf->{keyboard} if defined($conf->{keyboard});
 
-    # enable sound
-    #my $soundhw = $conf->{soundhw} || $defaults->{soundhw};
-    #push @$cmd, '-soundhw', 'es1370';
-    #push @$cmd, '-soundhw', $soundhw if $soundhw;
-
     if (parse_guest_agent($conf)->{enabled}) {
        my $qgasocket = qmp_socket($vmid, 1);
-       my $pciaddr = print_pci_addr("qga0", $bridges);
+       my $pciaddr = print_pci_addr("qga0", $bridges, $arch, $machine_type);
        push @$devices, '-chardev', "socket,path=$qgasocket,server,nowait,id=qga0";
        push @$devices, '-device', "virtio-serial,id=qga0$pciaddr";
        push @$devices, '-device', 'virtserialport,chardev=qga0,name=org.qemu.guest_agent.0';
@@ -3648,7 +3969,7 @@ sub config_to_command {
        if ($qxlnum > 1) {
            if ($winversion){
                for(my $i = 1; $i < $qxlnum; $i++){
-                   push @$devices, '-device', print_vga_device($conf, $vga, $i, $qxlnum, $bridges);
+                   push @$devices, '-device', print_vga_device($conf, $vga, $arch, $machine_type, $i, $qxlnum, $bridges);
                }
            } else {
                # assume other OS works like Linux
@@ -3662,7 +3983,7 @@ sub config_to_command {
            }
        }
 
-       my $pciaddr = print_pci_addr("spice", $bridges);
+       my $pciaddr = print_pci_addr("spice", $bridges, $arch, $machine_type);
 
        my $nodename = PVE::INotify::nodename();
        my $pfamily = PVE::Tools::get_host_address_family($nodename);
@@ -3680,13 +4001,13 @@ sub config_to_command {
 
     # enable balloon by default, unless explicitly disabled
     if (!defined($conf->{balloon}) || $conf->{balloon}) {
-       $pciaddr = print_pci_addr("balloon0", $bridges);
+       $pciaddr = print_pci_addr("balloon0", $bridges, $arch, $machine_type);
        push @$devices, '-device', "virtio-balloon-pci,id=balloon0$pciaddr";
     }
 
     if ($conf->{watchdog}) {
        my $wdopts = parse_watchdog($conf->{watchdog});
-       $pciaddr = print_pci_addr("watchdog", $bridges);
+       $pciaddr = print_pci_addr("watchdog", $bridges, $arch, $machine_type);
        my $watchdog = $wdopts->{model} || 'i6300esb';
        push @$devices, '-device', "$watchdog$pciaddr";
        push @$devices, '-watchdog-action', $wdopts->{action} if $wdopts->{action};
@@ -3734,7 +4055,7 @@ sub config_to_command {
 
            my ($maxdev, $controller, $controller_prefix) = scsihw_infos($conf, $drive);
 
-           $pciaddr = print_pci_addr("$controller_prefix$controller", $bridges);
+           $pciaddr = print_pci_addr("$controller_prefix$controller", $bridges, $arch, $machine_type);
            my $scsihw_type = $scsihw =~ m/^virtio-scsi-single/ ? "virtio-scsi-pci" : $scsihw;
 
            my $iothread = '';
@@ -3748,7 +4069,7 @@ sub config_to_command {
            my $queues = '';
            if($conf->{scsihw} && $conf->{scsihw} eq "virtio-scsi-single" && $drive->{queues}){
                $queues = ",num_queues=$drive->{queues}";
-           } 
+           }
 
            push @$devices, '-device', "$scsihw_type,id=$controller_prefix$controller$pciaddr$iothread$queues" if !$scsicontroller->{$controller};
            $scsicontroller->{$controller}=1;
@@ -3756,14 +4077,14 @@ sub config_to_command {
 
         if ($drive->{interface} eq 'sata') {
            my $controller = int($drive->{index} / $MAX_SATA_DISKS);
-           $pciaddr = print_pci_addr("ahci$controller", $bridges);
+           $pciaddr = print_pci_addr("ahci$controller", $bridges, $arch, $machine_type);
            push @$devices, '-device', "ahci,id=ahci$controller,multifunction=on$pciaddr" if !$ahcicontroller->{$controller};
            $ahcicontroller->{$controller}=1;
         }
 
        my $drive_cmd = print_drive_full($storecfg, $vmid, $drive);
        push @$devices, '-drive',$drive_cmd;
-       push @$devices, '-device', print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges);
+       push @$devices, '-device', print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges, $arch, $machine_type);
     });
 
     for (my $i = 0; $i < $MAX_NETS; $i++) {
@@ -3778,13 +4099,30 @@ sub config_to_command {
             $bootindex_hash->{n} += 1;
          }
 
-         my $netdevfull = print_netdev_full($vmid,$conf,$d,"net$i");
+         my $netdevfull = print_netdev_full($vmid, $conf, $arch, $d, "net$i");
          push @$devices, '-netdev', $netdevfull;
 
-         my $netdevicefull = print_netdevice_full($vmid, $conf, $d, "net$i", $bridges, $use_old_bios_files);
+         my $netdevicefull = print_netdevice_full($vmid, $conf, $d, "net$i", $bridges, $use_old_bios_files, $arch, $machine_type);
          push @$devices, '-device', $netdevicefull;
     }
 
+    if ($conf->{ivshmem}) {
+       my $ivshmem = PVE::JSONSchema::parse_property_string($ivshmem_fmt, $conf->{ivshmem});
+
+       my $bus;
+       if ($q35) {
+           $bus = print_pcie_addr("ivshmem");
+       } else {
+           $bus = print_pci_addr("ivshmem", $bridges, $arch, $machine_type);
+       }
+
+       my $ivshmem_name = $ivshmem->{name} // $vmid;
+       my $path = '/dev/shm/pve-shm-' . $ivshmem_name;
+
+       push @$devices, '-device', "ivshmem-plain,memdev=ivshmem$bus,";
+       push @$devices, '-object', "memory-backend-file,id=ivshmem,share=on,mem-path=$path,size=$ivshmem->{size}M";
+    }
+
     if (!$q35) {
        # add pci bridges
         if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
@@ -3795,17 +4133,11 @@ sub config_to_command {
        $bridges->{3} = 1 if $scsihw =~ m/^virtio-scsi-single/;
 
        while (my ($k, $v) = each %$bridges) {
-           $pciaddr = print_pci_addr("pci.$k");
+           $pciaddr = print_pci_addr("pci.$k", undef, $arch, $machine_type);
            unshift @$devices, '-device', "pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr" if $k > 0;
        }
     }
 
-    # add custom args
-    if ($conf->{args}) {
-       my $aa = PVE::Tools::split_args($conf->{args});
-       push @$cmd, @$aa;
-    }
-
     push @$cmd, @$devices;
     push @$cmd, '-rtc', join(',', @$rtcFlags)
        if scalar(@$rtcFlags);
@@ -3814,6 +4146,18 @@ sub config_to_command {
     push @$cmd, '-global', join(',', @$globalFlags)
        if scalar(@$globalFlags);
 
+    if (my $vmstate = $conf->{vmstate}) {
+       my $statepath = PVE::Storage::path($storecfg, $vmstate);
+       PVE::Storage::activate_volumes($storecfg, [$vmstate]);
+       push @$cmd, '-loadstate', $statepath;
+    }
+
+    # add custom args
+    if ($conf->{args}) {
+       my $aa = PVE::Tools::split_args($conf->{args});
+       push @$cmd, @$aa;
+    }
+
     return wantarray ? ($cmd, $vollist, $spice_port) : $cmd;
 }
 
@@ -3893,18 +4237,22 @@ sub vm_devices_list {
 }
 
 sub vm_deviceplug {
-    my ($storecfg, $conf, $vmid, $deviceid, $device) = @_;
+    my ($storecfg, $conf, $vmid, $deviceid, $device, $arch, $machine_type) = @_;
 
     my $q35 = machine_type_is_q35($conf);
 
     my $devices_list = vm_devices_list($vmid);
     return 1 if defined($devices_list->{$deviceid});
 
-    qemu_add_pci_bridge($storecfg, $conf, $vmid, $deviceid); # add PCI bridge if we need it for the device
+    qemu_add_pci_bridge($storecfg, $conf, $vmid, $deviceid, $arch, $machine_type); # add PCI bridge if we need it for the device
 
     if ($deviceid eq 'tablet') {
 
-       qemu_deviceadd($vmid, print_tabletdevice_full($conf));
+       qemu_deviceadd($vmid, print_tabletdevice_full($conf, $arch));
+
+    } elsif ($deviceid eq 'keyboard') {
+
+       qemu_deviceadd($vmid, print_keyboarddevice_full($conf, $arch));
 
     } elsif ($deviceid =~ m/^usb(\d+)$/) {
 
@@ -3919,7 +4267,7 @@ sub vm_deviceplug {
        qemu_iothread_add($vmid, $deviceid, $device);
 
         qemu_driveadd($storecfg, $vmid, $device);
-        my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device);
+        my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device, $arch, $machine_type);
 
         qemu_deviceadd($vmid, $devicefull);
        eval { qemu_deviceaddverify($vmid, $deviceid); };
@@ -3933,7 +4281,7 @@ sub vm_deviceplug {
 
 
         my $scsihw = defined($conf->{scsihw}) ? $conf->{scsihw} : "lsi";
-        my $pciaddr = print_pci_addr($deviceid);
+        my $pciaddr = print_pci_addr($deviceid, undef, $arch, $machine_type);
        my $scsihw_type = $scsihw eq 'virtio-scsi-single' ? "virtio-scsi-pci" : $scsihw;
 
         my $devicefull = "$scsihw_type,id=$deviceid$pciaddr";
@@ -3952,10 +4300,10 @@ sub vm_deviceplug {
 
     } elsif ($deviceid =~ m/^(scsi)(\d+)$/) {
 
-        qemu_findorcreatescsihw($storecfg,$conf, $vmid, $device);
+        qemu_findorcreatescsihw($storecfg,$conf, $vmid, $device, $arch, $machine_type);
         qemu_driveadd($storecfg, $vmid, $device);
 
-       my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device);
+       my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device, $arch, $machine_type);
        eval { qemu_deviceadd($vmid, $devicefull); };
        if (my $err = $@) {
            eval { qemu_drivedel($vmid, $deviceid); };
@@ -3965,25 +4313,28 @@ sub vm_deviceplug {
 
     } elsif ($deviceid =~ m/^(net)(\d+)$/) {
 
-        return undef if !qemu_netdevadd($vmid, $conf, $device, $deviceid);
+       return undef if !qemu_netdevadd($vmid, $conf, $arch, $device, $deviceid);
 
-        my $machine_type = PVE::QemuServer::qemu_machine_pxe($vmid, $conf); 
-        my $use_old_bios_files = undef;
-        ($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
+       my $machine_type = PVE::QemuServer::qemu_machine_pxe($vmid, $conf);
+       my $use_old_bios_files = undef;
+       ($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
 
-        my $netdevicefull = print_netdevice_full($vmid, $conf, $device, $deviceid, undef, $use_old_bios_files);
-        qemu_deviceadd($vmid, $netdevicefull);
-        eval { qemu_deviceaddverify($vmid, $deviceid); };
+       my $netdevicefull = print_netdevice_full($vmid, $conf, $device, $deviceid, undef, $use_old_bios_files, $arch, $machine_type);
+       qemu_deviceadd($vmid, $netdevicefull);
+       eval {
+           qemu_deviceaddverify($vmid, $deviceid);
+           qemu_set_link_status($vmid, $deviceid, !$device->{link_down});
+       };
        if (my $err = $@) {
            eval { qemu_netdevdel($vmid, $deviceid); };
            warn $@ if $@;
            die $err;
-        }
+       }
 
     } elsif (!$q35 && $deviceid =~ m/^(pci\.)(\d+)$/) {
 
        my $bridgeid = $2;
-       my $pciaddr = print_pci_addr($deviceid);
+       my $pciaddr = print_pci_addr($deviceid, undef, $arch, $machine_type);
        my $devicefull = "pci-bridge,id=pci.$bridgeid,chassis_nr=$bridgeid$pciaddr";
 
        qemu_deviceadd($vmid, $devicefull);
@@ -4005,7 +4356,7 @@ sub vm_deviceunplug {
 
     die "can't unplug bootdisk" if $conf->{bootdisk} && $conf->{bootdisk} eq $deviceid;
 
-    if ($deviceid eq 'tablet') {
+    if ($deviceid eq 'tablet' || $deviceid eq 'keyboard') {
 
        qemu_devicedel($vmid, $deviceid);
 
@@ -4077,7 +4428,11 @@ sub qemu_iothread_add {
 sub qemu_iothread_del {
     my($conf, $vmid, $deviceid) = @_;
 
-    my $device = parse_drive($deviceid, $conf->{$deviceid});
+    my $confid = $deviceid;
+    if ($deviceid =~ m/^(?:virtioscsi|scsihw)(\d+)$/) {
+       $confid = 'scsi' . $1;
+    }
+    my $device = parse_drive($confid, $conf->{$confid});
     if ($device->{iothread}) {
        my $iothreads = vm_iothreads_list($vmid);
        qemu_objectdel($vmid, "iothread-$deviceid") if $iothreads->{"iothread-$deviceid"};
@@ -4156,7 +4511,7 @@ sub qemu_devicedelverify {
 }
 
 sub qemu_findorcreatescsihw {
-    my ($storecfg, $conf, $vmid, $device) = @_;
+    my ($storecfg, $conf, $vmid, $device, $arch, $machine_type) = @_;
 
     my ($maxdev, $controller, $controller_prefix) = scsihw_infos($conf, $device);
 
@@ -4164,7 +4519,7 @@ sub qemu_findorcreatescsihw {
     my $devices_list = vm_devices_list($vmid);
 
     if(!defined($devices_list->{$scsihwid})) {
-       vm_deviceplug($storecfg, $conf, $vmid, $scsihwid, $device);
+       vm_deviceplug($storecfg, $conf, $vmid, $scsihwid, $device, $arch, $machine_type);
     }
 
     return 1;
@@ -4200,13 +4555,13 @@ sub qemu_deletescsihw {
 }
 
 sub qemu_add_pci_bridge {
-    my ($storecfg, $conf, $vmid, $device) = @_;
+    my ($storecfg, $conf, $vmid, $device, $arch, $machine_type) = @_;
 
     my $bridges = {};
 
     my $bridgeid;
 
-    print_pci_addr($device, $bridges);
+    print_pci_addr($device, $bridges, $arch, $machine_type);
 
     while (my ($k, $v) = each %$bridges) {
        $bridgeid = $k;
@@ -4217,7 +4572,7 @@ sub qemu_add_pci_bridge {
     my $devices_list = vm_devices_list($vmid);
 
     if (!defined($devices_list->{$bridge})) {
-       vm_deviceplug($storecfg, $conf, $vmid, $bridge);
+       vm_deviceplug($storecfg, $conf, $vmid, $bridge, $arch, $machine_type);
     }
 
     return 1;
@@ -4231,9 +4586,9 @@ sub qemu_set_link_status {
 }
 
 sub qemu_netdevadd {
-    my ($vmid, $conf, $device, $deviceid) = @_;
+    my ($vmid, $conf, $arch, $device, $deviceid) = @_;
 
-    my $netdev = print_netdev_full($vmid, $conf, $device, $deviceid, 1);
+    my $netdev = print_netdev_full($vmid, $conf, $arch, $device, $deviceid, 1);
     my %options =  split(/[=,]/, $netdev);
 
     vm_mon_cmd($vmid, "netdev_add",  %options);
@@ -4247,7 +4602,7 @@ sub qemu_netdevdel {
 }
 
 sub qemu_usb_hotplug {
-    my ($storecfg, $conf, $vmid, $deviceid, $device) = @_;
+    my ($storecfg, $conf, $vmid, $deviceid, $device, $arch, $machine_type) = @_;
 
     return if !$device;
 
@@ -4260,7 +4615,7 @@ sub qemu_usb_hotplug {
        my $devicelist = vm_devices_list($vmid);
 
        if (!$devicelist->{xhci}) {
-           my $pciaddr = print_pci_addr("xhci");
+           my $pciaddr = print_pci_addr("xhci", undef, $arch, $machine_type);
            qemu_deviceadd($vmid, "nec-usb-xhci,id=xhci$pciaddr");
        }
     }
@@ -4268,7 +4623,7 @@ sub qemu_usb_hotplug {
     $d->{usb3} = $device->{usb3};
 
     # add the new one
-    vm_deviceplug($storecfg, $conf, $vmid, $deviceid, $d);
+    vm_deviceplug($storecfg, $conf, $vmid, $deviceid, $d, $arch, $machine_type);
 }
 
 sub qemu_cpu_hotplug {
@@ -4492,6 +4847,7 @@ my $fast_plug_option = {
     'description' => 1,
     'protection' => 1,
     'vmstatestorage' => 1,
+    'hookscript' => 1,
 };
 
 # hotplug changes in [PENDING]
@@ -4502,6 +4858,7 @@ sub vmconfig_hotplug_pending {
     my ($vmid, $conf, $storecfg, $selection, $errors) = @_;
 
     my $defaults = load_defaults();
+    my ($arch, $machine_type) = get_basic_machine_info($conf, undef);
 
     # commit values which do not have any impact on running VM first
     # Note: those option cannot raise errors, we we do not care about
@@ -4537,9 +4894,12 @@ sub vmconfig_hotplug_pending {
            } elsif ($opt eq 'tablet') {
                die "skip\n" if !$hotplug_features->{usb};
                if ($defaults->{tablet}) {
-                   vm_deviceplug($storecfg, $conf, $vmid, $opt);
+                   vm_deviceplug($storecfg, $conf, $vmid, 'tablet', $arch, $machine_type);
+                   vm_deviceplug($storecfg, $conf, $vmid, 'keyboard', $arch, $machine_type)
+                       if $arch eq 'aarch64';
                } else {
-                   vm_deviceunplug($vmid, $conf, $opt);
+                   vm_deviceunplug($vmid, $conf, 'tablet');
+                   vm_deviceunplug($vmid, $conf, 'keyboard') if $arch eq 'aarch64';
                }
            } elsif ($opt =~ m/^usb\d+/) {
                die "skip\n";
@@ -4612,9 +4972,12 @@ sub vmconfig_hotplug_pending {
            } elsif ($opt eq 'tablet') {
                die "skip\n" if !$hotplug_features->{usb};
                if ($value == 1) {
-                   vm_deviceplug($storecfg, $conf, $vmid, $opt);
+                   vm_deviceplug($storecfg, $conf, $vmid, 'tablet', $arch, $machine_type);
+                   vm_deviceplug($storecfg, $conf, $vmid, 'keyboard', $arch, $machine_type)
+                       if $arch eq 'aarch64';
                } elsif ($value == 0) {
-                   vm_deviceunplug($vmid, $conf, $opt);
+                   vm_deviceunplug($vmid, $conf, 'tablet');
+                   vm_deviceunplug($vmid, $conf, 'keyboard') if $arch eq 'aarch64';
                }
            } elsif ($opt =~ m/^usb\d+$/) {
                die "skip\n";
@@ -4623,7 +4986,7 @@ sub vmconfig_hotplug_pending {
                die "skip\n" if !$hotplug_features->{usb} || $value =~ m/spice/i;
                my $d = eval { PVE::JSONSchema::parse_property_string($usbdesc->{format}, $value) };
                die "skip\n" if !$d;
-               qemu_usb_hotplug($storecfg, $conf, $vmid, $opt, $d);
+               qemu_usb_hotplug($storecfg, $conf, $vmid, $opt, $d, $arch, $machine_type);
            } elsif ($opt eq 'vcpus') {
                die "skip\n" if !$hotplug_features->{cpu};
                qemu_cpu_hotplug($vmid, $conf, $value);
@@ -4641,7 +5004,7 @@ sub vmconfig_hotplug_pending {
            } elsif ($opt =~ m/^net(\d+)$/) {
                # some changes can be done without hotplug
                vmconfig_update_net($storecfg, $conf, $hotplug_features->{network},
-                                   $vmid, $opt, $value);
+                                   $vmid, $opt, $value, $arch, $machine_type);
            } elsif (is_valid_drivename($opt)) {
                # some changes can be done without hotplug
                my $drive = parse_drive($opt, $value);
@@ -4649,7 +5012,7 @@ sub vmconfig_hotplug_pending {
                    &$apply_pending_cloudinit($opt, $value);
                }
                vmconfig_update_disk($storecfg, $conf, $hotplug_features->{disk},
-                                    $vmid, $opt, $value, 1);
+                                    $vmid, $opt, $value, 1, $arch, $machine_type);
            } elsif ($opt =~ m/^memory$/) { #dimms
                die "skip\n" if !$hotplug_features->{memory};
                $value = PVE::QemuServer::Memory::qemu_memory_hotplug($vmid, $conf, $defaults, $opt, $value);
@@ -4778,7 +5141,7 @@ my $safe_string_ne = sub {
 };
 
 sub vmconfig_update_net {
-    my ($storecfg, $conf, $hotplug, $vmid, $opt, $value) = @_;
+    my ($storecfg, $conf, $hotplug, $vmid, $opt, $value, $arch, $machine_type) = @_;
 
     my $newnet = parse_net($value);
 
@@ -4819,14 +5182,14 @@ sub vmconfig_update_net {
     }
 
     if ($hotplug) {
-       vm_deviceplug($storecfg, $conf, $vmid, $opt, $newnet);
+       vm_deviceplug($storecfg, $conf, $vmid, $opt, $newnet, $arch, $machine_type);
     } else {
        die "skip\n";
     }
 }
 
 sub vmconfig_update_disk {
-    my ($storecfg, $conf, $hotplug, $vmid, $opt, $value, $force) = @_;
+    my ($storecfg, $conf, $hotplug, $vmid, $opt, $value, $force, $arch, $machine_type) = @_;
 
     # fixme: do we need force?
 
@@ -4927,7 +5290,7 @@ sub vmconfig_update_disk {
     die "skip\n" if !$hotplug || $opt =~ m/(ide|sata)(\d+)/;
     # hotplug new disks
     PVE::Storage::activate_volumes($storecfg, [$drive->{file}]) if $drive->{file} !~ m|^/dev/.+|;
-    vm_deviceplug($storecfg, $conf, $vmid, $opt, $drive);
+    vm_deviceplug($storecfg, $conf, $vmid, $opt, $drive, $arch, $machine_type);
 }
 
 sub vm_start {
@@ -4939,7 +5302,10 @@ sub vm_start {
 
        die "you can't start a vm if it's a template\n" if PVE::QemuConfig->is_template($conf);
 
-       PVE::QemuConfig->check_lock($conf) if !$skiplock;
+       my $is_suspended = PVE::QemuConfig->has_lock($conf, 'suspended');
+
+       PVE::QemuConfig->check_lock($conf)
+           if !($skiplock || $is_suspended);
 
        die "VM $vmid already running\n" if check_running($vmid, undef, $migratedfrom);
 
@@ -5003,6 +5369,14 @@ sub vm_start {
            }
        }
 
+       PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-start', 1);
+
+       if ($is_suspended) {
+           # enforce machine type on suspended vm to ensure HW compatibility
+           $forcemachine = $conf->{runningmachine};
+           print "Resuming suspended VM\n";
+       }
+
        my ($cmd, $vollist, $spice_port) = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine);
 
        my $migrate_port = 0;
@@ -5063,29 +5437,38 @@ sub vm_start {
           next if !$d;
          my $pcidevices = $d->{pciid};
          foreach my $pcidevice (@$pcidevices) {
-               my $pciid = $pcidevice->{id}.".".$pcidevice->{function};
+               my $pciid = $pcidevice->{id};
 
-               my $info = pci_device_info("0000:$pciid");
-               die "IOMMU not present\n" if !check_iommu_support();
+               my $info = PVE::SysFSTools::pci_device_info("0000:$pciid");
+               die "IOMMU not present\n" if !PVE::SysFSTools::check_iommu_support();
                die "no pci device info for device '$pciid'\n" if !$info;
-               die "can't unbind/bind pci group to vfio '$pciid'\n" if !pci_dev_group_bind_to_vfio($pciid);
-               die "can't reset pci device '$pciid'\n" if $info->{has_fl_reset} and !pci_dev_reset($info);
+
+               if ($d->{mdev}) {
+                   my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $i);
+                   PVE::SysFSTools::pci_create_mdev_device($pciid, $uuid, $d->{mdev});
+               } else {
+                   die "can't unbind/bind pci group to vfio '$pciid'\n"
+                       if !PVE::SysFSTools::pci_dev_group_bind_to_vfio($pciid);
+                   die "can't reset pci device '$pciid'\n"
+                       if $info->{has_fl_reset} and !PVE::SysFSTools::pci_dev_reset($info);
+               }
          }
         }
 
        PVE::Storage::activate_volumes($storecfg, $vollist);
 
-       if (!check_running($vmid, 1)) {
-           eval {
-               run_command(['/bin/systemctl', 'stop', "$vmid.scope"],
-                   outfunc => sub {}, errfunc => sub {});
-           };
-       }
+       eval {
+           run_command(['/bin/systemctl', 'stop', "$vmid.scope"],
+               outfunc => sub {}, errfunc => sub {});
+       };
+       # Issues with the above 'stop' not being fully completed are extremely rare, a very low
+       # timeout should be more than enough here...
+       PVE::Systemd::wait_for_unit_removed("$vmid.scope", 5);
 
        my $cpuunits = defined($conf->{cpuunits}) ? $conf->{cpuunits}
                                                  : $defaults->{cpuunits};
 
-       my $start_timeout = $conf->{hugepages} ? 300 : 30;
+       my $start_timeout = ($conf->{hugepages} || $is_suspended) ? 300 : 30;
        my %run_params = (timeout => $statefile ? undef : $start_timeout, umask => 0077);
 
        my %properties = (
@@ -5192,6 +5575,15 @@ sub vm_start {
                    property => "guest-stats-polling-interval",
                    value => 2) if (!defined($conf->{balloon}) || $conf->{balloon});
 
+       if ($is_suspended && (my $vmstate = $conf->{vmstate})) {
+           print "Resumed VM, removing state\n";
+           delete $conf->@{qw(lock vmstate runningmachine)};
+           PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);
+           PVE::Storage::vdisk_free($storecfg, $vmstate);
+           PVE::QemuConfig->write_config($vmid, $conf);
+       }
+
+       PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'post-start');
     });
 }
 
@@ -5215,9 +5607,8 @@ sub vm_qmp_command {
     my $res;
 
     my $timeout;
-    if ($cmd->{arguments} && $cmd->{arguments}->{timeout}) {
-       $timeout = $cmd->{arguments}->{timeout};
-       delete $cmd->{arguments}->{timeout};
+    if ($cmd->{arguments}) {
+       $timeout = delete $cmd->{arguments}->{timeout};
     }
 
     eval {
@@ -5242,8 +5633,6 @@ sub vm_qmp_command {
 sub vm_human_monitor_command {
     my ($vmid, $cmdline) = @_;
 
-    my $res;
-
     my $cmd = {
        execute => 'human-monitor-command',
        arguments => { 'command-line' => $cmdline},
@@ -5253,10 +5642,19 @@ sub vm_human_monitor_command {
 }
 
 sub vm_commandline {
-    my ($storecfg, $vmid) = @_;
+    my ($storecfg, $vmid, $snapname) = @_;
 
     my $conf = PVE::QemuConfig->load_config($vmid);
 
+    if ($snapname) {
+       my $snapshot = $conf->{snapshots}->{$snapname};
+       die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
+
+       $snapshot->{digest} = $conf->{digest}; # keep file digest for API
+
+       $conf = $snapshot;
+    }
+
     my $defaults = load_defaults();
 
     my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults);
@@ -5309,6 +5707,27 @@ sub vm_stop_cleanup {
            unlink "/var/run/qemu-server/${vmid}.$ext";
        }
 
+       if ($conf->{ivshmem}) {
+           my $ivshmem = PVE::JSONSchema::parse_property_string($ivshmem_fmt, $conf->{ivshmem});
+           # just delete it for now, VMs which have this already open do not
+           # are affected, but new VMs will get a separated one. If this
+           # becomes an issue we either add some sort of ref-counting or just
+           # add a "don't delete on stop" flag to the ivshmem format.
+           unlink '/dev/shm/pve-shm-' . ($ivshmem->{name} // $vmid);
+       }
+
+       foreach my $key (keys %$conf) {
+           next if $key !~ m/^hostpci(\d+)$/;
+           my $hostpciindex = $1;
+           my $d = parse_hostpci($conf->{$key});
+           my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $hostpciindex);
+
+           foreach my $pci (@{$d->{pciid}}) {
+               my $pciid = $pci->{id};
+               PVE::SysFSTools::pci_cleanup_mdev_device($pciid, $uuid);
+           }
+       }
+
        vmconfig_apply_pending($vmid, $conf, $storecfg) if $apply_pending_changes;
     };
     warn $@ if $@; # avoid errors - just warn
@@ -5343,14 +5762,16 @@ sub vm_stop {
                my $opts = PVE::JSONSchema::pve_parse_startup_order($conf->{startup});
                $timeout = $opts->{down} if $opts->{down};
            }
+           PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-stop');
        }
 
-       $timeout = 60 if !defined($timeout);
-
        eval {
            if ($shutdown) {
                if (defined($conf) && parse_guest_agent($conf)->{enabled}) {
-                   vm_qmp_command($vmid, { execute => "guest-shutdown" }, $nocheck);
+                   vm_qmp_command($vmid, {
+                       execute => "guest-shutdown",
+                       arguments => { timeout => $timeout }
+                   }, $nocheck);
                } else {
                    vm_qmp_command($vmid, { execute => "system_powerdown" }, $nocheck);
                }
@@ -5361,6 +5782,8 @@ sub vm_stop {
        my $err = $@;
 
        if (!$err) {
+           $timeout = 60 if !defined($timeout);
+
            my $count = 0;
            while (($count < $timeout) && check_running($vmid, $nocheck)) {
                $count++;
@@ -5407,25 +5830,92 @@ sub vm_stop {
 }
 
 sub vm_suspend {
-    my ($vmid, $skiplock) = @_;
+    my ($vmid, $skiplock, $includestate, $statestorage) = @_;
+
+    my $conf;
+    my $path;
+    my $storecfg;
+    my $vmstate;
 
     PVE::QemuConfig->lock_config($vmid, sub {
 
-       my $conf = PVE::QemuConfig->load_config($vmid);
+       $conf = PVE::QemuConfig->load_config($vmid);
 
+       my $is_backing_up = PVE::QemuConfig->has_lock($conf, 'backup');
        PVE::QemuConfig->check_lock($conf)
-           if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
+           if !($skiplock || $is_backing_up);
+
+       die "cannot suspend to disk during backup\n"
+           if $is_backing_up && $includestate;
 
-       vm_mon_cmd($vmid, "stop");
+       if ($includestate) {
+           $conf->{lock} = 'suspending';
+           my $date = strftime("%Y-%m-%d", localtime(time()));
+           $storecfg = PVE::Storage::config();
+           $vmstate = PVE::QemuConfig->__snapshot_save_vmstate($vmid, $conf, "suspend-$date", $storecfg, $statestorage, 1);
+           $path = PVE::Storage::path($storecfg, $vmstate);
+           PVE::QemuConfig->write_config($vmid, $conf);
+       } else {
+           vm_mon_cmd($vmid, "stop");
+       }
     });
+
+    if ($includestate) {
+       # save vm state
+       PVE::Storage::activate_volumes($storecfg, [$vmstate]);
+
+       eval {
+           vm_mon_cmd($vmid, "savevm-start", statefile => $path);
+           for(;;) {
+               my $state = vm_mon_cmd_nocheck($vmid, "query-savevm");
+               if (!$state->{status}) {
+                   die "savevm not active\n";
+               } elsif ($state->{status} eq 'active') {
+                   sleep(1);
+                   next;
+               } elsif ($state->{status} eq 'completed') {
+                   print "State saved, quitting\n";
+                   last;
+               } elsif ($state->{status} eq 'failed' && $state->{error}) {
+                   die "query-savevm failed with error '$state->{error}'\n"
+               } else {
+                   die "query-savevm returned status '$state->{status}'\n";
+               }
+           }
+       };
+       my $err = $@;
+
+       PVE::QemuConfig->lock_config($vmid, sub {
+           $conf = PVE::QemuConfig->load_config($vmid);
+           if ($err) {
+               # cleanup, but leave suspending lock, to indicate something went wrong
+               eval {
+                   vm_mon_cmd($vmid, "savevm-end");
+                   PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);
+                   PVE::Storage::vdisk_free($storecfg, $vmstate);
+                   delete $conf->@{qw(vmstate runningmachine)};
+                   PVE::QemuConfig->write_config($vmid, $conf);
+               };
+               warn $@ if $@;
+               die $err;
+           }
+
+           die "lock changed unexpectedly\n"
+               if !PVE::QemuConfig->has_lock($conf, 'suspending');
+
+           vm_qmp_command($vmid, { execute => "quit" });
+           $conf->{lock} = 'suspended';
+           PVE::QemuConfig->write_config($vmid, $conf);
+       });
+    }
 }
 
 sub vm_resume {
     my ($vmid, $skiplock, $nocheck) = @_;
 
     PVE::QemuConfig->lock_config($vmid, sub {
-
-       my $res = vm_mon_cmd($vmid, 'query-status');
+       my $vm_mon_cmd = $nocheck ? \&vm_mon_cmd_nocheck : \&vm_mon_cmd;
+       my $res = $vm_mon_cmd->($vmid, 'query-status');
        my $resume_cmd = 'cont';
 
        if ($res->{status} && $res->{status} eq 'suspended') {
@@ -5438,12 +5928,9 @@ sub vm_resume {
 
            PVE::QemuConfig->check_lock($conf)
                if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
-
-           vm_mon_cmd($vmid, $resume_cmd);
-
-       } else {
-           vm_mon_cmd_nocheck($vmid, $resume_cmd);
        }
+
+       $vm_mon_cmd->($vmid, $resume_cmd);
     });
 }
 
@@ -5455,7 +5942,8 @@ sub vm_sendkey {
        my $conf = PVE::QemuConfig->load_config($vmid);
 
        # there is no qmp command, so we use the human monitor command
-       vm_human_monitor_command($vmid, "sendkey $key");
+       my $res = vm_human_monitor_command($vmid, "sendkey $key");
+       die $res if $res ne '';
     });
 }
 
@@ -5474,123 +5962,6 @@ sub vm_destroy {
     });
 }
 
-# pci helpers
-
-sub file_write {
-    my ($filename, $buf) = @_;
-
-    my $fh = IO::File->new($filename, "w");
-    return undef if !$fh;
-
-    my $res = print $fh $buf;
-
-    $fh->close();
-
-    return $res;
-}
-
-sub pci_device_info {
-    my ($name) = @_;
-
-    my $res;
-
-    return undef if $name !~ m/^([a-f0-9]{4}):([a-f0-9]{2}):([a-f0-9]{2})\.([a-f0-9])$/;
-    my ($domain, $bus, $slot, $func) = ($1, $2, $3, $4);
-
-    my $irq = file_read_firstline("$pcisysfs/devices/$name/irq");
-    return undef if !defined($irq) || $irq !~ m/^\d+$/;
-
-    my $vendor = file_read_firstline("$pcisysfs/devices/$name/vendor");
-    return undef if !defined($vendor) || $vendor !~ s/^0x//;
-
-    my $product = file_read_firstline("$pcisysfs/devices/$name/device");
-    return undef if !defined($product) || $product !~ s/^0x//;
-
-    $res = {
-       name => $name,
-       vendor => $vendor,
-       product => $product,
-       domain => $domain,
-       bus => $bus,
-       slot => $slot,
-       func => $func,
-       irq => $irq,
-       has_fl_reset => -f "$pcisysfs/devices/$name/reset" || 0,
-    };
-
-    return $res;
-}
-
-sub pci_dev_reset {
-    my ($dev) = @_;
-
-    my $name = $dev->{name};
-
-    my $fn = "$pcisysfs/devices/$name/reset";
-
-    return file_write($fn, "1");
-}
-
-sub pci_dev_bind_to_vfio {
-    my ($dev) = @_;
-
-    my $name = $dev->{name};
-
-    my $vfio_basedir = "$pcisysfs/drivers/vfio-pci";
-
-    if (!-d $vfio_basedir) {
-       system("/sbin/modprobe vfio-pci >/dev/null 2>/dev/null");
-    }
-    die "Cannot find vfio-pci module!\n" if !-d $vfio_basedir;
-
-    my $testdir = "$vfio_basedir/$name";
-    return 1 if -d $testdir;
-
-    my $data = "$dev->{vendor} $dev->{product}";
-    return undef if !file_write("$vfio_basedir/new_id", $data);
-
-    my $fn = "$pcisysfs/devices/$name/driver/unbind";
-    if (!file_write($fn, $name)) {
-       return undef if -f $fn;
-    }
-
-    $fn = "$vfio_basedir/bind";
-    if (! -d $testdir) {
-       return undef if !file_write($fn, $name);
-    }
-
-    return -d $testdir;
-}
-
-sub pci_dev_group_bind_to_vfio {
-    my ($pciid) = @_;
-
-    my $vfio_basedir = "$pcisysfs/drivers/vfio-pci";
-
-    if (!-d $vfio_basedir) {
-       system("/sbin/modprobe vfio-pci >/dev/null 2>/dev/null");
-    }
-    die "Cannot find vfio-pci module!\n" if !-d $vfio_basedir;
-
-    # get IOMMU group devices
-    opendir(my $D, "$pcisysfs/devices/0000:$pciid/iommu_group/devices/") || die "Cannot open iommu_group: $!\n";
-      my @devs = grep /^0000:/, readdir($D);
-    closedir($D);
-
-    foreach my $pciid (@devs) {
-       $pciid =~ m/^([:\.\da-f]+)$/ or die "PCI ID $pciid not valid!\n";
-
-        # pci bridges, switches or root ports are not supported
-        # they have a pci_bus subdirectory so skip them
-        next if (-e "$pcisysfs/devices/$pciid/pci_bus");
-
-       my $info = pci_device_info($1);
-       pci_dev_bind_to_vfio($info) || die "Cannot bind $pciid to vfio\n";
-    }
-
-    return 1;
-}
-
 # vzdump restore implementaion
 
 sub tar_archive_read_firstfile {
@@ -5679,7 +6050,6 @@ sub restore_update_config_line {
     return if $line =~ m/^lock:/;
     return if $line =~ m/^unused\d+:/;
     return if $line =~ m/^parent:/;
-    return if $line =~ m/^template:/; # restored VM is never a template
 
     my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
     if (($line =~ m/^(vlan(\d+)):\s*(\S+)\s*$/)) {
@@ -6040,6 +6410,24 @@ sub restore_vma_archive {
                $storage_limits{$storeid} = $bwlimit;
 
                $virtdev_hash->{$virtdev} = $devinfo->{$devname};
+           } elsif ($line =~ m/^((?:ide|sata|scsi)\d+):\s*(.*)\s*$/) {
+               my $virtdev = $1;
+               my $drive = parse_drive($virtdev, $2);
+               if (drive_is_cloudinit($drive)) {
+                   my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});
+                   my $scfg = PVE::Storage::storage_config($cfg, $storeid);
+                   my $format = qemu_img_format($scfg, $volname); # has 'raw' fallback
+
+                   my $d = {
+                       format => $format,
+                       storeid => $opts->{storage} // $storeid,
+                       size => PVE::QemuServer::Cloudinit::CLOUDINIT_DISK_SIZE,
+                       file => $drive->{file}, # to make drive_is_cloudinit check possible
+                       name => "vm-$vmid-cloudinit",
+                       is_cloudinit => 1,
+                   };
+                   $virtdev_hash->{$virtdev} = $d;
+               }
            }
        }
 
@@ -6061,10 +6449,9 @@ sub restore_vma_archive {
            foreach_drive($oldconf, sub {
                my ($ds, $drive) = @_;
 
-               return if drive_is_cdrom($drive);
+               return if !$drive->{is_cloudinit} && drive_is_cdrom($drive);
 
                my $volid = $drive->{file};
-
                return if !$volid || $volid =~ m|^/|;
 
                my ($path, $owner) = PVE::Storage::path($cfg, $volid);
@@ -6080,8 +6467,7 @@ sub restore_vma_archive {
                }
            });
 
-           # delete vmstate files
-           # since after the restore we have no snapshots anymore
+           # delete vmstate files, after the restore we have no snapshots anymore
            foreach my $snapname (keys %{$oldconf->{snapshots}}) {
                my $snap = $oldconf->{snapshots}->{$snapname};
                if ($snap->{vmstate}) {
@@ -6110,22 +6496,30 @@ sub restore_vma_archive {
            my $supported = grep { $_ eq $d->{format} } @$validFormats;
            $d->{format} = $defFormat if !$supported;
 
-           my $volid = PVE::Storage::vdisk_alloc($cfg, $storeid, $vmid,
-                                                 $d->{format}, undef, $alloc_size);
+           my $name;
+           if ($d->{is_cloudinit}) {
+               $name = $d->{name};
+               $name .= ".$d->{format}" if $d->{format} ne 'raw';
+           }
+
+           my $volid = PVE::Storage::vdisk_alloc($cfg, $storeid, $vmid, $d->{format}, $name, $alloc_size);
            print STDERR "new volume ID is '$volid'\n";
            $d->{volid} = $volid;
-           my $path = PVE::Storage::path($cfg, $volid);
 
-           PVE::Storage::activate_volumes($cfg,[$volid]);
+           PVE::Storage::activate_volumes($cfg, [$volid]);
 
            my $write_zeros = 1;
            if (PVE::Storage::volume_has_feature($cfg, 'sparseinit', $volid)) {
                $write_zeros = 0;
            }
 
-           print $fifofh "${map_opts}format=$d->{format}:${write_zeros}:$d->{devname}=$path\n";
+           if (!$d->{is_cloudinit}) {
+               my $path = PVE::Storage::path($cfg, $volid);
+
+               print $fifofh "${map_opts}format=$d->{format}:${write_zeros}:$d->{devname}=$path\n";
 
-           print "map '$d->{devname}' to '$path' (write zeros = ${write_zeros})\n";
+               print "map '$d->{devname}' to '$path' (write zeros = ${write_zeros})\n";
+           }
            $map->{$virtdev} = $volid;
        }
 
@@ -6361,9 +6755,9 @@ sub do_snapshots_with_qemu {
     my ($storecfg, $volid) = @_;
 
     my $storage_name = PVE::Storage::parse_volume_id($volid);
+    my $scfg = $storecfg->{ids}->{$storage_name};
 
-    if ($qemu_snap_storage->{$storecfg->{ids}->{$storage_name}->{type}} 
-       && !$storecfg->{ids}->{$storage_name}->{krbd}){
+    if ($qemu_snap_storage->{$scfg->{type}} && !$scfg->{krbd}){
        return 1;
     }
 
@@ -6406,6 +6800,23 @@ sub template_create {
     });
 }
 
+sub convert_iscsi_path {
+    my ($path) = @_;
+
+    if ($path =~ m|^iscsi://([^/]+)/([^/]+)/(.+)$|) {
+       my $portal = $1;
+       my $target = $2;
+       my $lun = $3;
+
+       my $initiator_name = get_initiator_name();
+
+       return "file.driver=iscsi,file.transport=tcp,file.initiator-name=$initiator_name,".
+              "file.portal=$portal,file.target=$target,file.lun=$lun,driver=raw";
+    }
+
+    die "cannot convert iscsi path '$path', unkown format\n";
+}
+
 sub qemu_img_convert {
     my ($src_volid, $dst_volid, $size, $snapname, $is_zero_initialized) = @_;
 
@@ -6426,13 +6837,32 @@ sub qemu_img_convert {
        my $src_path = PVE::Storage::path($storecfg, $src_volid, $snapname);
        my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
 
+       my $src_is_iscsi = ($src_path =~ m|^iscsi://|);
+       my $dst_is_iscsi = ($dst_path =~ m|^iscsi://|);
+
        my $cmd = [];
        push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';
        push @$cmd, '-l', "snapshot.name=$snapname" if($snapname && $src_format eq "qcow2");
        push @$cmd, '-t', 'none' if $dst_scfg->{type} eq 'zfspool';
        push @$cmd, '-T', 'none' if $src_scfg->{type} eq 'zfspool';
-       push @$cmd, '-f', $src_format, '-O', $dst_format, $src_path;
-       if ($is_zero_initialized) {
+
+       if ($src_is_iscsi) {
+           push @$cmd, '--image-opts';
+           $src_path = convert_iscsi_path($src_path);
+       } else {
+           push @$cmd, '-f', $src_format;
+       }
+
+       if ($dst_is_iscsi) {
+           push @$cmd, '--target-image-opts';
+           $dst_path = convert_iscsi_path($dst_path);
+       } else {
+           push @$cmd, '-O', $dst_format;
+       }
+
+       push @$cmd, $src_path;
+
+       if (!$dst_is_iscsi && $is_zero_initialized) {
            push @$cmd, "zeroinit:$dst_path";
        } else {
            push @$cmd, $dst_path;
@@ -6467,7 +6897,7 @@ sub qemu_img_format {
 }
 
 sub qemu_drive_mirror {
-    my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized, $jobs, $skipcomplete, $qga) = @_;
+    my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized, $jobs, $skipcomplete, $qga, $bwlimit) = @_;
 
     $jobs = {} if !$jobs;
 
@@ -6494,13 +6924,19 @@ sub qemu_drive_mirror {
     my $opts = { timeout => 10, device => "drive-$drive", mode => "existing", sync => "full", target => $qemu_target };
     $opts->{format} = $format if $format;
 
-    print "drive mirror is starting for drive-$drive\n";
-
-    eval { vm_mon_cmd($vmid, "drive-mirror", %$opts); }; #if a job already run for this device,it's throw an error
+    if (defined($bwlimit)) {
+       $opts->{speed} = $bwlimit * 1024;
+       print "drive mirror is starting for drive-$drive with bandwidth limit: ${bwlimit} KB/s\n";
+    } else {
+       print "drive mirror is starting for drive-$drive\n";
+    }
 
+    # if a job already runs for this device we get an error, catch it for cleanup
+    eval { vm_mon_cmd($vmid, "drive-mirror", %$opts); };
     if (my $err = $@) {
        eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
-       die "mirroring error: $err";
+       warn "$@\n" if $@;
+       die "mirroring error: $err\n";
     }
 
     qemu_drive_mirror_monitor ($vmid, $vmiddst, $jobs, $skipcomplete, $qga);
@@ -6638,7 +7074,7 @@ sub qemu_blockjobs_cancel {
 
 sub clone_disk {
     my ($storecfg, $vmid, $running, $drivename, $drive, $snapname,
-       $newvmid, $storage, $format, $full, $newvollist, $jobs, $skipcomplete, $qga) = @_;
+       $newvmid, $storage, $format, $full, $newvollist, $jobs, $skipcomplete, $qga, $bwlimit) = @_;
 
     my $newvolid;
 
@@ -6658,11 +7094,10 @@ sub clone_disk {
        my $name = undef;
        if (drive_is_cloudinit($drive)) {
            $name = "vm-$newvmid-cloudinit";
-           # cloudinit only supports raw and qcow2 atm:
-           if ($dst_format eq 'qcow2') {
-               $name .= '.qcow2';
-           } elsif ($dst_format ne 'raw') {
-               die "clone: unhandled format for cloudinit image\n";
+           $snapname = undef;
+           # we only get here if it's supported by QEMU_FORMAT_RE, so just accept
+           if ($dst_format ne 'raw') {
+               $name .= ".$dst_format";
            }
        }
        $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, $name, ($size/1024));
@@ -6672,6 +7107,7 @@ sub clone_disk {
 
        my $sparseinit = PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $newvolid);
        if (!$running || $snapname) {
+           # TODO: handle bwlimits
            qemu_img_convert($drive->{file}, $newvolid, $size, $snapname, $sparseinit);
        } else {
 
@@ -6681,7 +7117,7 @@ sub clone_disk {
                    if $drive->{iothread};
            }
 
-           qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit, $jobs, $skipcomplete, $qga);
+           qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit, $jobs, $skipcomplete, $qga, $bwlimit);
        }
     }
 
@@ -6775,43 +7211,25 @@ sub qemu_use_old_bios_files {
     return ($use_old_bios_files, $machine_type);
 }
 
-sub create_efidisk {
-    my ($storecfg, $storeid, $vmid, $fmt) = @_;
+sub create_efidisk($$$$$) {
+    my ($storecfg, $storeid, $vmid, $fmt, $arch) = @_;
 
-    die "EFI vars default image not found\n" if ! -f $OVMF_VARS;
+    my (undef, $ovmf_vars) = get_ovmf_files($arch);
+    die "EFI vars default image not found\n" if ! -f $ovmf_vars;
 
-    my $vars_size = PVE::Tools::convert_size(-s $OVMF_VARS, 'b' => 'kb');
+    my $vars_size = PVE::Tools::convert_size(-s $ovmf_vars, 'b' => 'kb');
     my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $vars_size);
     PVE::Storage::activate_volumes($storecfg, [$volid]);
 
     my $path = PVE::Storage::path($storecfg, $volid);
     eval {
-       run_command(['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $fmt, $OVMF_VARS, $path]);
+       run_command(['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $fmt, $ovmf_vars, $path]);
     };
     die "Copying EFI vars image failed: $@" if $@;
 
     return ($volid, $vars_size);
 }
 
-sub lspci {
-
-    my $devices = {};
-
-    dir_glob_foreach("$pcisysfs/devices", '[a-f0-9]{4}:([a-f0-9]{2}:[a-f0-9]{2})\.([0-9])', sub {
-            my (undef, $id, $function) = @_;
-           my $res = { id => $id, function => $function};
-           push @{$devices->{$id}}, $res;
-    });
-
-    # Entries should be sorted by functions.
-    foreach my $id (keys %$devices) {
-       my $dev = $devices->{$id};
-       $devices->{$id} = [ sort { $a->{function} <=> $b->{function} } @$dev ];
-    }
-
-    return $devices;
-}
-
 sub vm_iothreads_list {
     my ($vmid) = @_;
 
@@ -6845,12 +7263,15 @@ sub scsihw_infos {
 }
 
 sub add_hyperv_enlightenments {
-    my ($cpuFlags, $winversion, $machine_type, $kvmver, $bios, $gpu_passthrough) = @_;
+    my ($cpuFlags, $winversion, $machine_type, $kvmver, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
 
     return if $winversion < 6;
     return if $bios && $bios eq 'ovmf' && $winversion < 8;
 
-    push @$cpuFlags , 'hv_vendor_id=proxmox' if $gpu_passthrough;
+    if ($gpu_passthrough || defined($hv_vendor_id)) {
+       $hv_vendor_id //= 'proxmox';
+       push @$cpuFlags , "hv_vendor_id=$hv_vendor_id";
+    }
 
     if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
        push @$cpuFlags , 'hv_spinlocks=0x1fff';
@@ -6873,6 +7294,10 @@ sub add_hyperv_enlightenments {
            push @$cpuFlags , 'hv_synic';
            push @$cpuFlags , 'hv_stimer';
        }
+
+       if (qemu_machine_feature_enabled ($machine_type, $kvmver, 3, 1)) {
+           push @$cpuFlags , 'hv_ipi';
+       }
     }
 }