]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
add 'arch' vm configuration
[qemu-server.git] / PVE / QemuServer.pm
index cf84255cfc9631c124841228062d9df277c2aa17..4a570883b943dec233741e918a893526ff7a0fd2 100644 (file)
@@ -83,7 +83,7 @@ PVE::JSONSchema::register_standard_option('pve-qm-image-format', {
 PVE::JSONSchema::register_standard_option('pve-qemu-machine', {
        description => "Specifies the Qemu machine type.",
        type => 'string',
-       pattern => '(pc|pc(-i440fx)?-\d+\.\d+(\.pxe)?|q35|pc-q35-\d+\.\d+(\.pxe)?)',
+       pattern => '(pc|pc(-i440fx)?-\d+\.\d+(\.pxe)?|q35|pc-q35-\d+\.\d+(\.pxe)?|virt(?:-\d+\.\d+)?)',
        maxLength => 40,
        optional => 1,
 });
@@ -223,6 +223,24 @@ my $agent_fmt = {
     },
 };
 
+my $vga_fmt = {
+    type => {
+       description => "Select the VGA type.",
+       type => 'string',
+       default => 'std',
+       optional => 1,
+       default_key => 1,
+       enum => [qw(cirrus qxl qxl2 qxl3 qxl4 serial0 serial1 serial2 serial3 std virtio vmware)],
+    },
+    memory => {
+       description => "Sets the VGA memory (in MiB). Has no effect with serial display.",
+       type => 'integer',
+       optional => 1,
+       minimum => 4,
+       maximum => 512,
+    },
+};
+
 my $confdesc = {
     onboot => {
        optional => 1,
@@ -431,17 +449,16 @@ EODESC
     },
     vga => {
        optional => 1,
-       type => 'string',
-       description => "Select the VGA type.",
-        verbose_description => "Select the VGA type. If you want to use high resolution" .
-           " modes (>= 1280x1024x16) then you should use the options " .
-           "'std' or 'vmware'. Default is 'std' for win8/win7/w2k8, and " .
-           "'cirrus' for other OS types. The 'qxl' option enables the SPICE " .
-           "display sever. For win* OS you can select how many independent " .
-           "displays you want, Linux guests can add displays them self. " .
-           "You can also run without any graphic card, using a serial device" .
-           " as terminal.",
-       enum => [qw(std cirrus vmware qxl serial0 serial1 serial2 serial3 qxl2 qxl3 qxl4)],
+       type => 'string', format => $vga_fmt,
+       description => "Configure the VGA hardware.",
+       verbose_description => "Configure the VGA Hardware. If you want to use ".
+           "high resolution modes (>= 1280x1024x16) you may need to increase " .
+           "the vga memory option. Since QEMU 2.9 the default VGA display type " .
+           "is 'std' for all OS types besides some Windows versions (XP and " .
+           "older) which use 'cirrus'. The 'qxl' option enables the SPICE " .
+           "display server. For win* OS you can select how many independent " .
+           "displays you want, Linux guests can add displays them self.\n".
+           "You can also run without any graphic card, using a serial device as terminal.",
     },
     watchdog => {
        optional => 1,
@@ -540,6 +557,12 @@ EODESCR
        description => "Specifies the Qemu machine type of the running vm. This is used internally for snapshots.",
     }),
     machine => get_standard_option('pve-qemu-machine'),
+    arch => {
+       description => "Virtual processor architecture. Defaults to the host.",
+       optional => 1,
+       type => 'string',
+       enum => [qw(x86_64 aarch64)],
+    },
     smbios1 => {
        description => "Specify SMBIOS type 1 fields.",
        type => 'string', format => 'pve-qm-smbios1',
@@ -641,7 +664,7 @@ my $MAX_VIRTIO_DISKS = 16;
 my $MAX_SATA_DISKS = 6;
 my $MAX_USB_DEVICES = 5;
 my $MAX_NETS = 32;
-my $MAX_UNUSED_DISKS = 8;
+my $MAX_UNUSED_DISKS = 256;
 my $MAX_HOSTPCI_DEVICES = 4;
 my $MAX_SERIAL_PORTS = 4;
 my $MAX_PARALLEL_PORTS = 3;
@@ -1000,6 +1023,14 @@ my %scsiblock_fmt = (
     },
 );
 
+my %ssd_fmt = (
+    ssd => {
+       type => 'boolean',
+       description => "Whether to expose this drive as an SSD, rather than a rotational hard disk.",
+       optional => 1,
+    },
+);
+
 my $add_throttle_desc = sub {
     my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
     my $d = {
@@ -1047,6 +1078,7 @@ $drivedesc_base{'iops_wr_length'} = { alias => 'iops_wr_max_length' };
 my $ide_fmt = {
     %drivedesc_base,
     %model_fmt,
+    %ssd_fmt,
 };
 PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
 
@@ -1062,6 +1094,7 @@ my $scsi_fmt = {
     %iothread_fmt,
     %queues_fmt,
     %scsiblock_fmt,
+    %ssd_fmt,
 };
 my $scsidesc = {
     optional => 1,
@@ -1072,6 +1105,7 @@ PVE::JSONSchema::register_standard_option("pve-qm-scsi", $scsidesc);
 
 my $sata_fmt = {
     %drivedesc_base,
+    %ssd_fmt,
 };
 my $satadesc = {
     optional => 1,
@@ -1097,6 +1131,7 @@ my $alldrive_fmt = {
     %model_fmt,
     %queues_fmt,
     %scsiblock_fmt,
+    %ssd_fmt,
 };
 
 my $efidisk_fmt = {
@@ -1302,19 +1337,15 @@ for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++)  {
 my $kvm_api_version = 0;
 
 sub kvm_version {
-
     return $kvm_api_version if $kvm_api_version;
 
-    my $fh = IO::File->new("</dev/kvm") ||
-       return 0;
+    open my $fh, '<', '/dev/kvm'
+       or return undef;
 
-    if (my $v = $fh->ioctl(KVM_GET_API_VERSION(), 0)) {
-       $kvm_api_version = $v;
-    }
-
-    $fh->close();
+    # 0xae00 => KVM_GET_API_VERSION
+    $kvm_api_version = ioctl($fh, 0xae00, 0);
 
-    return  $kvm_api_version;
+    return $kvm_api_version;
 }
 
 my $kvm_user_version;
@@ -1704,21 +1735,33 @@ sub print_drivedevice_full {
            $device = "scsi-$devicetype,bus=$controller_prefix$controller.0,channel=0,scsi-id=0,lun=$drive->{index},drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
        }
 
-    } elsif ($drive->{interface} eq 'ide'){
-       $maxdev = 2;
+       if ($drive->{ssd} && ($devicetype eq 'block' || $devicetype eq 'hd')) {
+           $device .= ",rotation_rate=1";
+       }
+
+    } elsif ($drive->{interface} eq 'ide' || $drive->{interface} eq 'sata') {
+       my $maxdev = ($drive->{interface} eq 'sata') ? $MAX_SATA_DISKS : 2;
        my $controller = int($drive->{index} / $maxdev);
        my $unit = $drive->{index} % $maxdev;
        my $devicetype = ($drive->{media} && $drive->{media} eq 'cdrom') ? "cd" : "hd";
 
-       $device = "ide-$devicetype,bus=ide.$controller,unit=$unit,drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
-       if ($devicetype eq 'hd' && (my $model = $drive->{model})) {
-           $model = URI::Escape::uri_unescape($model);
-           $device .= ",model=$model";
+       $device = "ide-$devicetype";
+       if ($drive->{interface} eq 'ide') {
+           $device .= ",bus=ide.$controller,unit=$unit";
+       } else {
+           $device .= ",bus=ahci$controller.$unit";
+       }
+       $device .= ",drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
+
+       if ($devicetype eq 'hd') {
+           if (my $model = $drive->{model}) {
+               $model = URI::Escape::uri_unescape($model);
+               $device .= ",model=$model";
+           }
+           if ($drive->{ssd}) {
+               $device .= ",rotation_rate=1";
+           }
        }
-    } elsif ($drive->{interface} eq 'sata'){
-       my $controller = int($drive->{index} / $MAX_SATA_DISKS);
-       my $unit = $drive->{index} % $MAX_SATA_DISKS;
-       $device = "ide-drive,bus=ahci$controller.$unit,drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
     } 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
@@ -1937,6 +1980,55 @@ sub print_cpu_device {
     return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0";
 }
 
+my $vga_map = {
+    'cirrus' => 'cirrus-vga',
+    'std' => 'VGA',
+    'vmware' => 'vmware-svga',
+    'virtio' => 'virtio-vga',
+};
+
+sub print_vga_device {
+    my ($conf, $vga, $id, $qxlnum, $bridges) = @_;
+
+    my $type = $vga_map->{$vga->{type}};
+    my $vgamem_mb = $vga->{memory};
+    if ($qxlnum) {
+       $type = $id ? 'qxl' : 'qxl-vga';
+    }
+    die "no devicetype for $vga->{type}\n" if !$type;
+
+    my $memory = "";
+    if ($vgamem_mb) {
+       if ($vga->{type} eq 'virtio') {
+           my $bytes = PVE::Tools::convert_size($vgamem_mb, "mb" => "b");
+           $memory = ",max_hostmem=$bytes";
+       } elsif ($qxlnum) {
+           # from https://www.spice-space.org/multiple-monitors.html
+           $memory = ",vgamem_mb=$vga->{memory}";
+           my $ram = $vgamem_mb * 4;
+           my $vram = $vgamem_mb * 2;
+           $memory .= ",ram_size_mb=$ram,vram_size_mb=$vram";
+       } else {
+           $memory = ",vgamem_mb=$vga->{memory}";
+       }
+    } elsif ($qxlnum && $id) {
+       $memory = ",ram_size=67108864,vram_size=33554432";
+    }
+
+    my $q35 = machine_type_is_q35($conf);
+    my $vgaid = "vga" . ($id // '');
+    my $pciaddr;
+
+    if ($q35 && $vgaid eq 'vga') {
+       # the first display uses pcie.0 bus on q35 machines
+       $pciaddr = print_pcie_addr($vgaid, $bridges);
+    } else {
+       $pciaddr = print_pci_addr($vgaid, $bridges);
+    }
+
+    return "$type,id=${vgaid}${memory}${pciaddr}";
+}
+
 sub drive_is_cloudinit {
     my ($drive) = @_;
     return $drive->{file} =~ m@[:/]vm-\d+-cloudinit(?:\.$QEMU_FORMAT_RE)?$@;
@@ -2262,6 +2354,15 @@ sub parse_guest_agent {
     return $res;
 }
 
+sub parse_vga {
+    my ($value) = @_;
+
+    return {} if !$value;
+    my $res = eval { PVE::JSONSchema::parse_property_string($vga_fmt, $value) };
+    warn $@ if $@;
+    return $res;
+}
+
 PVE::JSONSchema::register_format('pve-qm-usb-device', \&verify_usb_device);
 sub verify_usb_device {
     my ($value, $noerr) = @_;
@@ -3143,11 +3244,37 @@ sub conf_has_serial {
 sub vga_conf_has_spice {
     my ($vga) = @_;
 
-    return 0 if !$vga || $vga !~ m/^qxl([234])?$/;
+    my $vgaconf = parse_vga($vga);
+    my $vgatype = $vgaconf->{type};
+    return 0 if !$vgatype || $vgatype !~ m/^qxl([234])?$/;
 
     return $1 || 1;
 }
 
+my $host_arch; # FIXME: fix PVE::Tools::get_host_arch
+sub get_host_arch() {
+    $host_arch = (POSIX::uname())[4] if !$host_arch;
+    return $host_arch;
+}
+
+sub is_native($) {
+    my ($arch) = @_;
+    return get_host_arch() eq $arch;
+}
+
+my $default_machines = {
+    x86_64 => 'pc',
+    aarch64 => 'virt',
+};
+
+sub get_basic_machine_info {
+    my ($conf, $forcemachine) = @_;
+
+    my $arch = $conf->{arch} // get_host_arch();
+    my $machine = $forcemachine || $conf->{machine} || $default_machines->{$arch};
+    return ($arch, $machine);
+}
+
 sub config_to_command {
     my ($storecfg, $vmid, $conf, $defaults, $forcemachine) = @_;
 
@@ -3163,9 +3290,15 @@ sub config_to_command {
     my $vernum = 0; # unknown
     my $ostype = $conf->{ostype};
     my $winversion = windows_version($ostype);
-    my $kvm = $conf->{kvm} // 1;
+    my $kvm = $conf->{kvm};
 
-    die "KVM virtualisation configured, but not available. Either disable in VM configuration or enable in BIOS.\n" if (!$cpuinfo->{hvm} && $kvm);
+    my ($arch, $machine_type) = get_basic_machine_info($conf, $forcemachine);
+    $kvm //= 1 if is_native($arch);
+
+    if ($kvm) {
+       die "KVM virtualisation configured, but not available. Either disable in VM configuration or enable in BIOS.\n"
+           if !defined kvm_version();
+    }
 
     if ($kvmver =~ m/^(\d+)\.(\d+)$/) {
        $vernum = $1*1000000+$2*1000;
@@ -3179,7 +3312,6 @@ sub config_to_command {
 
     my $q35 = machine_type_is_q35($conf);
     my $hotplug_features = parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1');
-    my $machine_type = $forcemachine || $conf->{machine};
     my $use_old_bios_files = undef;
     ($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
 
@@ -3200,6 +3332,11 @@ sub config_to_command {
     push @$cmd, '-chardev', "socket,id=qmp,path=$qmpsocket,server,nowait";
     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, '-mon', "chardev=qmp-event,mode=control";
+    }
 
     push @$cmd, '-pidfile' , pidfile_name($vmid);
 
@@ -3248,16 +3385,16 @@ sub config_to_command {
     # add usb controllers
     my @usbcontrollers = PVE::QemuServer::USB::get_usb_controllers($conf, $bridges, $q35, $usbdesc->{format}, $MAX_USB_DEVICES);
     push @$devices, @usbcontrollers if @usbcontrollers;
-    my $vga = $conf->{vga};
+    my $vga = parse_vga($conf->{vga});
 
-    my $qxlnum = vga_conf_has_spice($vga);
-    $vga = 'qxl' if $qxlnum;
+    my $qxlnum = vga_conf_has_spice($conf->{vga});
+    $vga->{type} = 'qxl' if $qxlnum;
 
-    if (!$vga) {
+    if (!$vga->{type}) {
        if (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 9)) {
-           $vga = (!$winversion || $winversion >= 6) ? 'std' : 'cirrus';
+           $vga->{type} = (!$winversion || $winversion >= 6) ? 'std' : 'cirrus';
        } else {
-           $vga = ($winversion >= 6) ? 'std' : 'cirrus';
+           $vga->{type} = ($winversion >= 6) ? 'std' : 'cirrus';
        }
     }
 
@@ -3268,7 +3405,7 @@ sub config_to_command {
     } else {
        $tablet = $defaults->{tablet};
        $tablet = 0 if $qxlnum; # disable for spice because it is not needed
-       $tablet = 0 if $vga =~ m/^serial\d+$/; # disable if we use serial terminal (no vga card)
+       $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;
@@ -3296,7 +3433,7 @@ sub config_to_command {
        if ($d->{'x-vga'}) {
            $xvga = ',x-vga=on';
            $kvm_off = 1;
-           $vga = 'none';
+           $vga->{type} = 'none';
            $gpu_passthrough = 1;
 
            if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
@@ -3399,12 +3536,12 @@ sub config_to_command {
 
     push @$cmd, '-no-reboot' if  defined($conf->{reboot}) && $conf->{reboot} == 0;
 
-    push @$cmd, '-vga', $vga if $vga && $vga !~ m/^serial\d+$/; # for kvm 77 and later
-
-    if ($vga && $vga !~ m/^serial\d+$/ && $vga ne 'none'){
+    if ($vga->{type} && $vga->{type} !~ m/^serial\d+$/ && $vga->{type} ne 'none'){
+       push @$devices, '-device', print_vga_device($conf, $vga, undef, $qxlnum, $bridges);
        my $socket = vnc_socket($vmid);
        push @$cmd,  '-vnc', "unix:$socket,x509,password";
     } else {
+       push @$cmd, '-vga', 'none' if $vga->{type} eq 'none';
        push @$cmd, '-nographic';
     }
 
@@ -3511,13 +3648,17 @@ sub config_to_command {
        if ($qxlnum > 1) {
            if ($winversion){
                for(my $i = 1; $i < $qxlnum; $i++){
-                   my $pciaddr = print_pci_addr("vga$i", $bridges);
-                   push @$devices, '-device', "qxl,id=vga$i,ram_size=67108864,vram_size=33554432$pciaddr";
+                   push @$devices, '-device', print_vga_device($conf, $vga, $i, $qxlnum, $bridges);
                }
            } else {
                # assume other OS works like Linux
-               push @$cmd, '-global', 'qxl-vga.ram_size=134217728';
-               push @$cmd, '-global', 'qxl-vga.vram_size=67108864';
+               my ($ram, $vram) = ("134217728", "67108864");
+               if ($vga->{memory}) {
+                   $ram = PVE::Tools::convert_size($qxlnum*4*$vga->{memory}, 'mb' => 'b');
+                   $vram = PVE::Tools::convert_size($qxlnum*2*$vga->{memory}, 'mb' => 'b');
+               }
+               push @$cmd, '-global', "qxl-vga.ram_size=$ram";
+               push @$cmd, '-global', "qxl-vga.vram_size=$vram";
            }
        }
 
@@ -3690,9 +3831,10 @@ sub spice_port {
 }
 
 sub qmp_socket {
-    my ($vmid, $qga) = @_;
+    my ($vmid, $qga, $name) = @_;
     my $sockettype = $qga ? 'qga' : 'qmp';
-    return "${var_run_tmpdir}/$vmid.$sockettype";
+    my $ext = $name ? '-'.$name : '';
+    return "${var_run_tmpdir}/$vmid$ext.$sockettype";
 }
 
 sub pidfile_name {
@@ -6286,7 +6428,7 @@ sub qemu_img_convert {
 
        my $cmd = [];
        push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';
-       push @$cmd, '-s', $snapname if($snapname && $src_format eq "qcow2");
+       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;
@@ -6583,7 +6725,7 @@ sub qemu_machine_feature_enabled {
     my $current_major;
     my $current_minor;
 
-    if ($machine && $machine =~ m/^(pc(-i440fx|-q35)?-(\d+)\.(\d+))/) {
+    if ($machine && $machine =~ m/^((?:pc(-i440fx|-q35)?|virt)-(\d+)\.(\d+))/) {
 
        $current_major = $3;
        $current_minor = $4;
@@ -6727,7 +6869,7 @@ sub add_hyperv_enlightenments {
     if ($winversion >= 7) {
        push @$cpuFlags , 'hv_relaxed';
 
-       if (qemu_machine_feature_enabled ($machine_type, $kvmver, 3, 0)) {
+       if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 12)) {
            push @$cpuFlags , 'hv_synic';
            push @$cpuFlags , 'hv_stimer';
        }
@@ -6796,6 +6938,12 @@ sub generate_smbios1_uuid {
     return "uuid=".generate_uuid();
 }
 
+sub nbd_stop {
+    my ($vmid) = @_;
+
+    vm_mon_cmd($vmid, 'nbd-server-stop');
+}
+
 # bash completion helper
 
 sub complete_backup_archives {
@@ -6869,10 +7017,4 @@ sub complete_storage {
     return $res;
 }
 
-sub nbd_stop {
-    my ($vmid) = @_;
-
-    vm_mon_cmd($vmid, 'nbd-server-stop');
-}
-
 1;