]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
Add audio device support
[qemu-server.git] / PVE / QemuServer.pm
index 9d560ec3a91031d95fe6fd056e3f2c8298514a7c..72641162f694526999a5eaed3e4e2f4d7b27c0d1 100644 (file)
@@ -21,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);
@@ -54,7 +55,7 @@ my $OVMF = {
     ],
 };
 
-my $qemu_snap_storage = {rbd => 1, sheepdog => 1};
+my $qemu_snap_storage = { rbd => 1 };
 
 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
 
@@ -166,7 +167,7 @@ my $cpu_vendor_list = {
     max => 'default',
 };
 
-my $cpu_flag = qr/[+-](pcid|spec-ctrl|ibpb|ssbd|virt-ssbd|amd-ssbd|amd-no-ssb|pdpe1gb)/;
+my $cpu_flag = qr/[+-](pcid|spec-ctrl|ibpb|ssbd|virt-ssbd|amd-ssbd|amd-no-ssb|pdpe1gb|md-clear)/;
 
 my $cpu_fmt = {
     cputype => {
@@ -192,7 +193,7 @@ my $cpu_fmt = {
     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: 'pcid', 'spec-ctrl', 'ibpb', 'ssbd', 'virt-ssbd', 'amd-ssbd', 'amd-no-ssb', 'pdpe1gb', 'md-clear'.",
        format_description => '+FLAG[;-FLAG...]',
        type => 'string',
        pattern => qr/$cpu_flag(;$cpu_flag)*/,
@@ -591,7 +592,7 @@ EODESCR
     smbios1 => {
        description => "Specify SMBIOS type 1 fields.",
        type => 'string', format => 'pve-qm-smbios1',
-       maxLength => 256,
+       maxLength => 512,
        optional => 1,
     },
     protection => {
@@ -636,7 +637,13 @@ EODESCR
        format => $ivshmem_fmt,
        description => "Inter-VM shared memory. Useful for direct communication between VMs, or to the host.",
        optional => 1,
-    }
+    },
+    audio0 => {
+       type => 'string',
+       enum => [qw(ich9-intel-hda intel-hda AC97)],
+       description => "Configure a audio device.",
+       optional => 1
+    },
 };
 
 my $cicustom_fmt = {
@@ -2358,7 +2365,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',
@@ -2369,46 +2376,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 {
@@ -2918,6 +2930,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) = @_;
 
@@ -3295,7 +3346,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;
 
@@ -3313,11 +3364,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}}) {
@@ -3524,7 +3576,26 @@ 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}) {
@@ -3563,6 +3634,15 @@ sub config_to_command {
        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, $arch, $machine_type, $usbdesc->{format}, $MAX_USB_DEVICES);
@@ -3706,6 +3786,18 @@ sub config_to_command {
        }
     }
 
+    if ($conf->{"audio0"}) {
+       my $audiodevice = $conf->{audio0};
+       my $audiopciaddr = print_pci_addr("audio0", $bridges, $arch, $machine_type);
+
+       if ($audiodevice eq 'AC97') {
+           push @$devices, '-device', "AC97,id=sound0${audiopciaddr}";
+       } else {
+           push @$devices, '-device', "${audiodevice},id=sound5${audiopciaddr}";
+           push @$devices, '-device', "hda-micro,id=sound5-codec0,bus=sound5.0,cad=0";
+           push @$devices, '-device', "hda-duplex,id=sound5-codec1,bus=sound5.0,cad=1";
+       }
+    }
 
     my $sockets = 1;
     $sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
@@ -3754,7 +3846,7 @@ sub config_to_command {
     if ($vga->{type} && $vga->{type} !~ m/^serial\d+$/ && $vga->{type} ne 'none'){
        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';
@@ -3789,7 +3881,7 @@ 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';
@@ -3803,11 +3895,6 @@ sub config_to_command {
 
     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, $arch, $machine_type);
@@ -5310,12 +5397,13 @@ sub vm_start {
 
        PVE::Storage::activate_volumes($storecfg, $vollist);
 
-       if (-d "/sys/fs/cgroup/systemd/qemu.slice/$vmid.scope") {
-           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};
@@ -5459,9 +5547,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 {
@@ -5620,12 +5707,13 @@ sub vm_stop {
            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);
                }
@@ -5636,6 +5724,8 @@ sub vm_stop {
        my $err = $@;
 
        if (!$err) {
+           $timeout = 60 if !defined($timeout);
+
            my $count = 0;
            while (($count < $timeout) && check_running($vmid, $nocheck)) {
                $count++;
@@ -5766,8 +5856,8 @@ 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') {
@@ -5780,12 +5870,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);
     });
 }
 
@@ -6264,6 +6351,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;
+               }
            }
        }
 
@@ -6285,10 +6390,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);
@@ -6304,8 +6408,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}) {
@@ -6334,22 +6437,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;
        }
 
@@ -6585,9 +6696,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;
     }
 
@@ -6925,11 +7036,9 @@ sub clone_disk {
        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';
-           } elsif ($dst_format ne 'raw') {
-               die "clone: unhandled format for cloudinit image\n";
+           # 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));
@@ -7126,6 +7235,13 @@ 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_tlbflush';
+           push @$cpuFlags , 'hv_ipi';
+           # FIXME: AMD does not supports this currently, only add with special flag??
+           #push @$cpuFlags , 'hv_evmcs';
+       }
     }
 }