]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
Fix #1924: add snapshot parameter
[qemu-server.git] / PVE / QemuServer.pm
index 035d8b7084145d50283b34cb992bdde68a8b7350..3b642863fce715d55b66d3393fa48f480e71071b 100644 (file)
@@ -34,6 +34,7 @@ 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);
@@ -118,8 +119,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',
@@ -187,6 +186,13 @@ 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."
@@ -238,7 +244,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.",
@@ -278,7 +284,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)],
     },
     cpulimit => {
        optional => 1,
@@ -1203,8 +1209,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,
@@ -1245,6 +1250,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);
 
@@ -1378,7 +1394,9 @@ sub kvm_user_version {
 
 }
 
-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
@@ -1964,7 +1982,7 @@ sub print_netdev_full {
 
     my $vhostparam = '';
     if (is_native($arch)) {
-       $vhostparam = ',vhost=on' if $kernel_has_vhost_net && $net->{model} eq 'virtio';
+       $vhostparam = ',vhost=on' if kernel_has_vhost_net() && $net->{model} eq 'virtio';
     }
 
     my $vmname = $conf->{name} || "vm$vmid";
@@ -2014,7 +2032,7 @@ sub print_vga_device {
     my ($conf, $vga, $arch, $machine, $id, $qxlnum, $bridges) = @_;
 
     my $type = $vga_map->{$vga->{type}};
-    if ($type eq 'virtio-vga' && $arch eq 'aarch64') {
+    if ($arch eq 'aarch64' && defined($type) && $type eq 'virtio-vga') {
        $type = 'virtio-gpu';
     }
     my $vgamem_mb = $vga->{memory};
@@ -2102,16 +2120,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;
@@ -2455,15 +2469,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) = @_;
 
@@ -3333,11 +3338,13 @@ sub get_cpu_options {
     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);
@@ -3359,7 +3366,7 @@ sub get_cpu_options {
        push @$cpuFlags , '+kvm_pv_eoi' if $kvm;
     }
 
-    add_hyperv_enlightenments($cpuFlags, $winversion, $machine_type, $kvmver, $conf->{bios}, $gpu_passthrough) 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';
 
@@ -3528,7 +3535,13 @@ sub config_to_command {
        my $pcie = $d->{pcie};
        if($pcie){
            die "q35 machine model is not enabled" if !$q35;
-           $pciaddr = print_pcie_addr("hostpci$i");
+           # 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);
        }
@@ -3540,7 +3553,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') {
@@ -3549,6 +3562,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) {
@@ -3557,7 +3578,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";
@@ -3873,12 +3900,6 @@ sub config_to_command {
        }
     }
 
-    # 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);
@@ -3887,6 +3908,12 @@ sub config_to_command {
     push @$cmd, '-global', join(',', @$globalFlags)
        if scalar(@$globalFlags);
 
+    # add custom args
+    if ($conf->{args}) {
+       my $aa = PVE::Tools::split_args($conf->{args});
+       push @$cmd, @$aa;
+    }
+
     return wantarray ? ($cmd, $vollist, $spice_port) : $cmd;
 }
 
@@ -5147,13 +5174,21 @@ 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);
+               }
          }
         }
 
@@ -5337,10 +5372,21 @@ 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);
+       my $digest = $conf->{digest};
+
+       # we need the digest of the file
+       $snapshot->{digest} = $conf->{digest};
+       $conf = $snapshot;
+    }
+
     my $defaults = load_defaults();
 
     my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults);
@@ -5393,6 +5439,18 @@ sub vm_stop_cleanup {
            unlink "/var/run/qemu-server/${vmid}.$ext";
        }
 
+       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
@@ -5558,123 +5616,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 {
@@ -6742,6 +6683,7 @@ sub clone_disk {
        my $name = undef;
        if (drive_is_cloudinit($drive)) {
            $name = "vm-$newvmid-cloudinit";
+           $snapname = undef;
            # cloudinit only supports raw and qcow2 atm:
            if ($dst_format eq 'qcow2') {
                $name .= '.qcow2';
@@ -6878,25 +6820,6 @@ sub create_efidisk($$$$$) {
     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) = @_;
 
@@ -6930,12 +6853,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';