X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FQemuServer.pm;h=49356f2558a95610bb9a4c3e311458c940ee578f;hb=cd11416f7a5dbc3204aafdd89d372cd867a7d08;hp=0104c2748e6473b07a5e5c456d9a04973bac39be;hpb=5b952ff541fb0838c6f59348ed415aa8eb1ff8f9;p=qemu-server.git diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 0104c274..49356f25 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -22,7 +22,7 @@ use PVE::SafeSyslog; use Storable qw(dclone); 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); +use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline dir_glob_foreach); use PVE::JSONSchema qw(get_standard_option); use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file); use PVE::INotify; @@ -300,6 +300,13 @@ EODESC minimum => 1, default => 1, }, + maxcpus => { + optional => 1, + type => 'integer', + description => "Maximum cpus for hotplug.", + minimum => 1, + default => 1, + }, acpi => { optional => 1, type => 'boolean', @@ -405,7 +412,7 @@ EODESCR optional => 1, description => "Emulated CPU type.", type => 'string', - enum => [ qw(486 athlon pentium pentium2 pentium3 coreduo core2duo kvm32 kvm64 qemu32 qemu64 phenom Conroe Penryn Nehalem Westmere SandyBridge Haswell Opteron_G1 Opteron_G2 Opteron_G3 Opteron_G4 Opteron_G5 host) ], + enum => [ qw(486 athlon pentium pentium2 pentium3 coreduo core2duo kvm32 kvm64 qemu32 qemu64 phenom Conroe Penryn Nehalem Westmere SandyBridge Haswell Broadwell Opteron_G1 Opteron_G2 Opteron_G3 Opteron_G4 Opteron_G5 host) ], default => 'kvm64', }, parent => get_standard_option('pve-snapshot-name', { @@ -430,6 +437,13 @@ EODESCR maxLength => 40, optional => 1, }, + smbios1 => { + description => "Specify SMBIOS type 1 fields.", + type => 'string', format => 'pve-qm-smbios1', + typetext => "[manufacturer=str][,product=str][,version=str][,serial=str] [,uuid=uuid][,sku=str][,family=str]", + maxLength => 256, + optional => 1, + }, }; # what about other qemu settings ? @@ -460,7 +474,7 @@ my $MAX_SATA_DISKS = 6; my $MAX_USB_DEVICES = 5; my $MAX_NETS = 32; my $MAX_UNUSED_DISKS = 8; -my $MAX_HOSTPCI_DEVICES = 2; +my $MAX_HOSTPCI_DEVICES = 4; my $MAX_SERIAL_PORTS = 4; my $MAX_PARALLEL_PORTS = 3; @@ -471,7 +485,7 @@ my $nic_model_list_txt = join(' ', sort @$nic_model_list); my $netdesc = { optional => 1, type => 'string', format => 'pve-qm-net', - typetext => "MODEL=XX:XX:XX:XX:XX:XX [,bridge=][,rate=][,tag=]", + typetext => "MODEL=XX:XX:XX:XX:XX:XX [,bridge=][,queues=][,rate=][,tag=][,firewall=0|1]", description => < 1, type => 'string', format => 'pve-qm-hostpci', - typetext => "HOSTPCIDEVICE", + typetext => "[host=]HOSTPCIDEVICE [,driver=kvm|vfio] [,rombar=on|off] [,pcie=0|1] [,x-vga=on|off]", description => <{mbps_wr} && $res->{mbps}; return undef if $res->{mbps} && $res->{mbps} !~ m/^\d+(\.\d+)?$/; + return undef if $res->{mbps_max} && $res->{mbps_max} !~ m/^\d+(\.\d+)?$/; return undef if $res->{mbps_rd} && $res->{mbps_rd} !~ m/^\d+(\.\d+)?$/; + return undef if $res->{mbps_rd_max} && $res->{mbps_rd_max} !~ m/^\d+(\.\d+)?$/; return undef if $res->{mbps_wr} && $res->{mbps_wr} !~ m/^\d+(\.\d+)?$/; + return undef if $res->{mbps_wr_max} && $res->{mbps_wr_max} !~ m/^\d+(\.\d+)?$/; return undef if $res->{iops_rd} && $res->{iops}; return undef if $res->{iops_wr} && $res->{iops}; + + return undef if $res->{iops} && $res->{iops} !~ m/^\d+$/; + return undef if $res->{iops_max} && $res->{iops_max} !~ m/^\d+$/; return undef if $res->{iops_rd} && $res->{iops_rd} !~ m/^\d+$/; + return undef if $res->{iops_rd_max} && $res->{iops_rd_max} !~ m/^\d+$/; return undef if $res->{iops_wr} && $res->{iops_wr} !~ m/^\d+$/; + return undef if $res->{iops_wr_max} && $res->{iops_wr_max} !~ m/^\d+$/; if ($res->{size}) { @@ -962,13 +986,13 @@ sub parse_drive { return $res; } -my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio discard iops iops_rd iops_wr); +my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio discard iops iops_rd iops_wr iops_max iops_rd_max iops_wr_max); sub print_drive { my ($vmid, $drive) = @_; my $opts = ''; - foreach my $o (@qemu_drive_options, 'mbps', 'mbps_rd', 'mbps_wr', 'backup') { + foreach my $o (@qemu_drive_options, 'mbps', 'mbps_rd', 'mbps_wr', 'mbps_max', 'mbps_rd_max', 'mbps_wr_max', 'backup') { $opts .= ",$o=$drive->{$o}" if $drive->{$o}; } @@ -1040,6 +1064,23 @@ sub path_is_scsi { return $res; } +sub machine_type_is_q35 { + my ($conf) = @_; + + return $conf->{machine} && ($conf->{machine} =~ m/q35/) ? 1 : 0; +} + +sub print_tabletdevice_full { + my ($conf) = @_; + + 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"; + + return "usb-tablet,id=tablet,bus=$usbbus.0,port=1"; +} + sub print_drivedevice_full { my ($storecfg, $conf, $vmid, $drive, $bridges) = @_; @@ -1106,6 +1147,20 @@ sub print_drivedevice_full { return $device; } +sub get_initiator_name { + my $initiator; + + my $fh = IO::File->new('/etc/iscsi/initiatorname.iscsi') || return undef; + while (defined(my $line = <$fh>)) { + next if $line !~ m/^\s*InitiatorName\s*=\s*([\.\-:\w]+)/; + $initiator = $1; + last; + } + $fh->close(); + + return $initiator; +} + sub print_drive_full { my ($storecfg, $vmid, $drive) = @_; @@ -1157,6 +1212,11 @@ sub print_netdevice_full { my $extra = ($bootorder !~ m/n/) ? "romfile=," : ''; my $pciaddr = print_pci_addr("$netid", $bridges); my $tmpstr = "$device,${extra}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) + my $vectors = $net->{queues} * 2 + 2; + $tmpstr .= ",vectors=$vectors,mq=on"; + } $tmpstr .= ",bootindex=$net->{bootindex}" if $net->{bootindex} ; return $tmpstr; } @@ -1182,11 +1242,17 @@ sub print_netdev_full { my $vmname = $conf->{name} || "vm$vmid"; + my $netdev = ""; + if ($net->{bridge}) { - return "type=tap,id=$netid,ifname=${ifname},script=/var/lib/qemu-server/pve-bridge$vhostparam"; + $netdev = "type=tap,id=$netid,ifname=${ifname},script=/var/lib/qemu-server/pve-bridge,downscript=/var/lib/qemu-server/pve-bridgedown$vhostparam"; } else { - return "type=user,id=$netid,hostname=$vmname"; + $netdev = "type=user,id=$netid,hostname=$vmname"; } + + $netdev .= ",queues=$net->{queues}" if ($net->{queues} && $net->{model} eq 'virtio'); + + return $netdev; } sub drive_is_cdrom { @@ -1201,14 +1267,37 @@ sub parse_hostpci { return undef if !$value; + + my @list = split(/,/, $value); + my $found; + my $res = {}; + foreach my $kv (@list) { - if ($value =~ m/^[a-f0-9]{2}:[a-f0-9]{2}\.[a-f0-9]$/) { - $res->{pciid} = $value; - } else { - return undef; + if ($kv =~ m/^(host=)?([a-f0-9]{2}:[a-f0-9]{2})(\.([a-f0-9]))?$/) { + $found = 1; + if(defined($4)){ + push @{$res->{pciid}}, { id => $2 , function => $4}; + + }else{ + my $pcidevices = lspci($2); + $res->{pciid} = $pcidevices->{$2}; + } + } elsif ($kv =~ m/^driver=(kvm|vfio)$/) { + $res->{driver} = $1; + } elsif ($kv =~ m/^rombar=(on|off)$/) { + $res->{rombar} = $1; + } elsif ($kv =~ m/^x-vga=(on|off)$/) { + $res->{'x-vga'} = $1; + } elsif ($kv =~ m/^pcie=(\d+)$/) { + $res->{pcie} = 1 if $1 == 1; + } else { + warn "unknown hostpci setting '$kv'\n"; + } } + return undef if !$found; + return $res; } @@ -1227,10 +1316,14 @@ sub parse_net { $res->{macaddr} = $mac; } elsif ($kvp =~ m/^bridge=(\S+)$/) { $res->{bridge} = $1; + } elsif ($kvp =~ m/^queues=(\d+)$/) { + $res->{queues} = $1; } elsif ($kvp =~ m/^rate=(\d+(\.\d+)?)$/) { $res->{rate} = $1; } elsif ($kvp =~ m/^tag=(\d+)$/) { $res->{tag} = $1; + } elsif ($kvp =~ m/^firewall=(\d+)$/) { + $res->{firewall} = $1; } else { return undef; } @@ -1250,6 +1343,7 @@ sub print_net { $res .= ",bridge=$net->{bridge}" if $net->{bridge}; $res .= ",rate=$net->{rate}" if $net->{rate}; $res .= ",tag=$net->{tag}" if $net->{tag}; + $res .= ",firewall=$net->{firewall}" if $net->{firewall}; return $res; } @@ -1285,6 +1379,58 @@ sub add_unused_volume { return $key; } +my $valid_smbios1_options = { + manufacturer => '\S+', + product => '\S+', + version => '\S+', + serial => '\S+', + uuid => '[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}', + sku => '\S+', + family => '\S+', +}; + +# smbios: [manufacturer=str][,product=str][,version=str][,serial=str][,uuid=uuid][,sku=str][,family=str] +sub parse_smbios1 { + my ($data) = @_; + + my $res = {}; + + foreach my $kvp (split(/,/, $data)) { + return undef if $kvp !~ m/^(\S+)=(.+)$/; + my ($k, $v) = split(/=/, $kvp); + return undef if !defined($k) || !defined($v); + return undef if !$valid_smbios1_options->{$k}; + return undef if $v !~ m/^$valid_smbios1_options->{$k}$/; + $res->{$k} = $v; + } + + return $res; +} + +sub print_smbios1 { + my ($smbios1) = @_; + + my $data = ''; + foreach my $k (keys %$smbios1) { + next if !defined($smbios1->{$k}); + next if !$valid_smbios1_options->{$k}; + $data .= ',' if $data; + $data .= "$k=$smbios1->{$k}"; + } + return $data; +} + +PVE::JSONSchema::register_format('pve-qm-smbios1', \&verify_smbios1); +sub verify_smbios1 { + my ($value, $noerr) = @_; + + return $value if parse_smbios1($value); + + return undef if $noerr; + + die "unable to parse smbios (type 1) options\n"; +} + PVE::JSONSchema::register_format('pve-qm-bootdisk', \&verify_bootdisk); sub verify_bootdisk { my ($value, $noerr) = @_; @@ -1716,6 +1862,10 @@ sub write_vm_config { delete $conf->{smp}; } + if ($conf->{maxcpus} && $conf->{sockets}) { + delete $conf->{sockets}; + } + my $used_volids = {}; my $cleanup_config = sub { @@ -2265,6 +2415,8 @@ sub config_to_command { my $have_ovz = -f '/proc/vz/vestat'; + my $q35 = machine_type_is_q35($conf); + push @$cmd, '/usr/bin/kvm'; push @$cmd, '-id', $vmid; @@ -2282,16 +2434,26 @@ sub config_to_command { push @$cmd, '-daemonize'; - $pciaddr = print_pci_addr("piix3", $bridges); - push @$devices, '-device', "piix3-usb-uhci,id=uhci$pciaddr.0x2"; + if ($conf->{smbios1}) { + push @$cmd, '-smbios', "type=1,$conf->{smbios1}"; + } - my $use_usb2 = 0; - for (my $i = 0; $i < $MAX_USB_DEVICES; $i++) { - next if !$conf->{"usb$i"}; - $use_usb2 = 1; + if ($q35) { + # the q35 chipset support native usb2, so we enable usb controller + # by default for this machine type + push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35.cfg'; + } else { + $pciaddr = print_pci_addr("piix3", $bridges); + push @$devices, '-device', "piix3-usb-uhci,id=uhci$pciaddr.0x2"; + + my $use_usb2 = 0; + for (my $i = 0; $i < $MAX_USB_DEVICES; $i++) { + next if !$conf->{"usb$i"}; + $use_usb2 = 1; + } + # include usb device config + push @$devices, '-readconfig', '/usr/share/qemu-server/pve-usb.cfg' if $use_usb2; } - # include usb device config - push @$devices, '-readconfig', '/usr/share/qemu-server/pve-usb.cfg' if $use_usb2; my $vga = $conf->{vga}; @@ -2299,8 +2461,8 @@ sub config_to_command { $vga = 'qxl' if $qxlnum; if (!$vga) { - if ($conf->{ostype} && ($conf->{ostype} eq 'win8' || - $conf->{ostype} eq 'win7' || + if ($conf->{ostype} && ($conf->{ostype} eq 'win8' || + $conf->{ostype} eq 'win7' || $conf->{ostype} eq 'w2k8')) { $vga = 'std'; } else { @@ -2318,14 +2480,49 @@ sub config_to_command { $tablet = 0 if $vga =~ m/^serial\d+$/; # disable if we use serial terminal (no vga card) } - push @$devices, '-device', 'usb-tablet,id=tablet,bus=uhci.0,port=1' if $tablet; - + push @$devices, '-device', print_tabletdevice_full($conf) if $tablet; + # host pci devices for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) { - my $d = parse_hostpci($conf->{"hostpci$i"}); - next if !$d; - $pciaddr = print_pci_addr("hostpci$i", $bridges); - push @$devices, '-device', "pci-assign,host=$d->{pciid},id=hostpci$i$pciaddr"; + my $d = parse_hostpci($conf->{"hostpci$i"}); + next if !$d; + + my $pcie = $d->{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); + } + + my $rombar = $d->{rombar} && $d->{rombar} eq 'off' ? ",rombar=0" : ""; + my $driver = $d->{driver} && $d->{driver} eq 'vfio' ? "vfio-pci" : "pci-assign"; + my $xvga = $d->{'x-vga'} && $d->{'x-vga'} eq 'on' ? ",x-vga=on" : ""; + if ($xvga && $xvga ne '') { + push @$cpuFlags, 'kvm=off'; + $vga = 'none'; + } + $driver = "vfio-pci" if $xvga ne ''; + my $pcidevices = $d->{pciid}; + my $multifunction = 1 if @$pcidevices > 1; + + my $j=0; + foreach my $pcidevice (@$pcidevices) { + + my $id = "hostpci$i"; + $id .= ".$j" if $multifunction; + my $addr = $pciaddr; + $addr .= ".$j" if $multifunction; + my $devicestr = "$driver,host=$pcidevice->{id}.$pcidevice->{function},id=$id$addr"; + + if($j == 0){ + $devicestr .= "$rombar$xvga"; + $devicestr .= ",multifunction=on" if $multifunction; + } + + push @$devices, '-device', $devicestr; + $j++; + } } # usb devices @@ -2377,7 +2574,13 @@ sub config_to_command { $sockets = $conf->{sockets} if $conf->{sockets}; my $cores = $conf->{cores} || 1; - push @$cmd, '-smp', "sockets=$sockets,cores=$cores"; + my $maxcpus = $conf->{maxcpus} if $conf->{maxcpus}; + + if ($maxcpus) { + push @$cmd, '-smp', "cpus=$cores,maxcpus=$maxcpus"; + } else { + push @$cmd, '-smp', "sockets=$sockets,cores=$cores"; + } push @$cmd, '-nodefaults'; @@ -2461,6 +2664,8 @@ sub config_to_command { $cpu .= "," . join(',', @$cpuFlags) if scalar(@$cpuFlags); + # Note: enforce needs kernel 3.10, so we do not use it for now + # push @$cmd, '-cpu', "$cpu,enforce"; push @$cmd, '-cpu', $cpu; push @$cmd, '-S' if $conf->{freeze}; @@ -2500,13 +2705,13 @@ sub config_to_command { my $pciaddr = print_pci_addr("spice", $bridges); - $spice_port = PVE::Tools::next_unused_port(61000, 61099); + $spice_port = PVE::Tools::next_spice_port(); - push @$cmd, '-spice', "tls-port=${spice_port},addr=127.0.0.1,tls-ciphers=DES-CBC3-SHA,seamless-migration=on"; + push @$devices, '-spice', "tls-port=${spice_port},addr=127.0.0.1,tls-ciphers=DES-CBC3-SHA,seamless-migration=on"; - push @$cmd, '-device', "virtio-serial,id=spice$pciaddr"; - push @$cmd, '-chardev', "spicevmc,id=vdagent,name=vdagent"; - push @$cmd, '-device', "virtserialport,chardev=vdagent,name=com.redhat.spice.0"; + push @$devices, '-device', "virtio-serial,id=spice$pciaddr"; + push @$devices, '-chardev', "spicevmc,id=vdagent,name=vdagent"; + push @$devices, '-device', "virtserialport,chardev=vdagent,name=com.redhat.spice.0"; } # enable balloon by default, unless explicitly disabled @@ -2528,6 +2733,11 @@ sub config_to_command { my $ahcicontroller = {}; my $scsihw = defined($conf->{scsihw}) ? $conf->{scsihw} : $defaults->{scsihw}; + # Add iscsi initiator name if available + if (my $initiator = get_initiator_name()) { + push @$devices, '-iscsi', "initiator-name=$initiator"; + } + foreach_drive($conf, sub { my ($ds, $drive) = @_; @@ -2565,8 +2775,9 @@ sub config_to_command { $ahcicontroller->{$controller}=1; } - push @$devices, '-drive',print_drive_full($storecfg, $vmid, $drive); - push @$devices, '-device',print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges); + 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 @$cmd, '-m', $conf->{memory} || $defaults->{memory}; @@ -2590,13 +2801,14 @@ sub config_to_command { push @$devices, '-device', $netdevicefull; } - #bridges - while (my ($k, $v) = each %$bridges) { - $pciaddr = print_pci_addr("pci.$k"); - unshift @$devices, '-device', "pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr" if $k > 0; + if (!$q35) { + # add pci bridges + while (my ($k, $v) = each %$bridges) { + $pciaddr = print_pci_addr("pci.$k"); + unshift @$devices, '-device', "pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr" if $k > 0; + } } - # hack: virtio with fairsched is unreliable, so we do not use fairsched # when the VM uses virtio devices. if (!$use_virtio && $have_ovz) { @@ -2676,9 +2888,10 @@ sub vm_deviceplug { return 1 if !check_running($vmid); + my $q35 = machine_type_is_q35($conf); + if ($deviceid eq 'tablet') { - my $devicefull = "usb-tablet,id=tablet,bus=uhci.0,port=1"; - qemu_deviceadd($vmid, $devicefull); + qemu_deviceadd($vmid, print_tabletdevice_full($conf)); return 1; } @@ -2728,7 +2941,8 @@ sub vm_deviceplug { } } - if ($deviceid =~ m/^(pci\.)(\d+)$/) { + + if (!$q35 && $deviceid =~ m/^(pci\.)(\d+)$/) { my $bridgeid = $2; my $pciaddr = print_pci_addr($deviceid); my $devicefull = "pci-bridge,id=pci.$bridgeid,chassis_nr=$bridgeid$pciaddr"; @@ -2901,6 +3115,29 @@ sub qemu_netdevdel { return 1; } +sub qemu_cpu_hotplug { + my ($vmid, $conf, $cores) = @_; + + die "new cores config is not defined" if !$cores; + die "you can't add more cores than maxcpus" + if $conf->{maxcpus} && ($cores > $conf->{maxcpus}); + return if !check_running($vmid); + + my $currentcores = $conf->{cores} if $conf->{cores}; + die "current cores is not defined" if !$currentcores; + die "maxcpus is not defined" if !$conf->{maxcpus}; + raise_param_exc({ 'cores' => "online cpu unplug is not yet possible" }) + if($cores < $currentcores); + + my $currentrunningcores = vm_mon_cmd($vmid, "query-cpus"); + raise_param_exc({ 'cores' => "cores number if running vm is different than configuration" }) + if scalar (@{$currentrunningcores}) != $currentcores; + + for(my $i = $currentcores; $i < $cores; $i++) { + vm_mon_cmd($vmid, "cpu-add", id => int($i)); + } +} + sub qemu_block_set_io_throttle { my ($vmid, $deviceid, $bps, $bps_rd, $bps_wr, $iops, $iops_rd, $iops_wr) = @_; @@ -3068,6 +3305,30 @@ sub qga_unfreezefs { #need to impplement call to qemu-ga } +sub set_migration_caps { + my ($vmid) = @_; + + my $cap_ref = []; + + my $enabled_cap = { + "auto-converge" => 1, + "xbzrle" => 0, + "x-rdma-pin-all" => 0, + "zero-blocks" => 0, + }; + + my $supported_capabilities = vm_mon_cmd_nocheck($vmid, "query-migrate-capabilities"); + + for my $supported_capability (@$supported_capabilities) { + push @$cap_ref, { + capability => $supported_capability->{capability}, + state => $enabled_cap->{$supported_capability->{capability}} ? JSON::true : JSON::false, + }; + } + + vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => $cap_ref); +} + sub vm_start { my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused, $forcemachine, $spice_ticket) = @_; @@ -3112,11 +3373,22 @@ sub vm_start { for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) { my $d = parse_hostpci($conf->{"hostpci$i"}); next if !$d; - my $info = pci_device_info("0000:$d->{pciid}"); - die "IOMMU not present\n" if !check_iommu_support(); - die "no pci device info for device '$d->{pciid}'\n" if !$info; - die "can't unbind pci device '$d->{pciid}'\n" if !pci_dev_bind_to_stub($info); - die "can't reset pci device '$d->{pciid}'\n" if !pci_dev_reset($info); + my $pcidevices = $d->{pciid}; + foreach my $pcidevice (@$pcidevices) { + my $pciid = $pcidevice->{id}.".".$pcidevice->{function}; + + my $info = pci_device_info("0000:$pciid"); + die "IOMMU not present\n" if !check_iommu_support(); + die "no pci device info for device '$pciid'\n" if !$info; + + if ($d->{driver} && $d->{driver} eq "vfio") { + die "can't unbind/bind pci group to vfio '$pciid'\n" if !pci_dev_group_bind_to_vfio($pciid); + } else { + die "can't unbind/bind to stub pci device '$pciid'\n" if !pci_dev_bind_to_stub($info); + } + + die "can't reset pci device '$pciid'\n" if $info->{has_fl_reset} and !pci_dev_reset($info); + } } PVE::Storage::activate_volumes($storecfg, $vollist); @@ -3134,12 +3406,12 @@ sub vm_start { } if ($migratedfrom) { - my $capabilities = {}; - $capabilities->{capability} = "xbzrle"; - $capabilities->{state} = JSON::true; - eval { vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => [$capabilities]); }; + + eval { + PVE::QemuServer::set_migration_caps($vmid); + }; warn $@ if $@; - + if ($spice_port) { print "spice listens on port $spice_port\n"; if ($spice_ticket) { @@ -3510,6 +3782,61 @@ sub pci_dev_bind_to_stub { return -d $testdir; } +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"; + my $info = pci_device_info($1); + pci_dev_bind_to_vfio($info) || die "Cannot bind $pciid to vfio\n"; + } + + return 1; +} + sub print_pci_addr { my ($id, $bridges) = @_; @@ -3541,6 +3868,8 @@ sub print_pci_addr { vga1 => { bus => 0, addr => 24 }, vga2 => { bus => 0, addr => 25 }, vga3 => { bus => 0, addr => 26 }, + hostpci2 => { bus => 0, addr => 27 }, + hostpci3 => { bus => 0, addr => 28 }, #addr29 : usb-host (pve-usb.cfg) 'pci.1' => { bus => 0, addr => 30 }, 'pci.2' => { bus => 0, addr => 31 }, @@ -3592,6 +3921,26 @@ sub print_pci_addr { } +sub print_pcie_addr { + my ($id) = @_; + + my $res = ''; + my $devices = { + hostpci0 => { bus => "ich9-pcie-port-1", addr => 0 }, + hostpci1 => { bus => "ich9-pcie-port-2", addr => 0 }, + hostpci2 => { bus => "ich9-pcie-port-3", addr => 0 }, + hostpci3 => { bus => "ich9-pcie-port-4", addr => 0 }, + }; + + if (defined($devices->{$id}->{bus}) && defined($devices->{$id}->{addr})) { + my $addr = sprintf("0x%x", $devices->{$id}->{addr}); + my $bus = $devices->{$id}->{bus}; + $res = ",bus=$bus,addr=$addr"; + } + return $res; + +} + # vzdump restore implementaion sub tar_archive_read_firstfile { @@ -4065,6 +4414,10 @@ sub restore_vma_archive { my ($dev_id, $size, $devname) = ($1, $2, $3); $devinfo->{$devname} = { size => $size, dev_id => $dev_id }; } elsif ($line =~ m/^CTIME: /) { + # we correctly received the vma config, so we can disable + # the timeout now for disk allocation (set to 10 minutes, so + # that we always timeout if something goes wrong) + alarm(600); &$print_devmap(); print $fifofh "done\n"; my $tmp = $oldtimeout || 0; @@ -4601,10 +4954,12 @@ sub snapshot_delete { die "snapshot '$snapname' does not exist\n" if !defined($snap); # remove parent refs - &$unlink_parent($conf, $snap->{parent}); - foreach my $sn (keys %{$conf->{snapshots}}) { - next if $sn eq $snapname; - &$unlink_parent($conf->{snapshots}->{$sn}, $snap->{parent}); + if (!$prepare) { + &$unlink_parent($conf, $snap->{parent}); + foreach my $sn (keys %{$conf->{snapshots}}) { + next if $sn eq $snapname; + &$unlink_parent($conf->{snapshots}->{$sn}, $snap->{parent}); + } } if ($remove_drive) { @@ -4856,7 +5211,7 @@ sub clone_disk { if (!$full) { print "create linked clone of drive $drivename ($drive->{file})\n"; - $newvolid = PVE::Storage::vdisk_clone($storecfg, $drive->{file}, $newvmid); + $newvolid = PVE::Storage::vdisk_clone($storecfg, $drive->{file}, $newvmid, $snapname); push @$newvollist, $newvolid; } else { my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file}); @@ -4911,21 +5266,17 @@ sub get_current_qemu_machine { return $current || $default || 'pc'; } -sub read_x509_subject_spice { - my ($filename) = @_; +sub lspci { - # read x509 subject - my $bio = Net::SSLeay::BIO_new_file($filename, 'r'); - my $x509 = Net::SSLeay::PEM_read_bio_X509($bio); - Net::SSLeay::BIO_free($bio); - my $nameobj = Net::SSLeay::X509_get_subject_name($x509); - my $subject = Net::SSLeay::X509_NAME_oneline($nameobj); - Net::SSLeay::X509_free($x509); - - # remote-viewer wants comma as seperator (not '/') - $subject =~ s!^/!!; - $subject =~ s!/(\w+=)!,$1!g; + 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; + }); - return $subject; + return $devices; } + 1;