diff --git a/devices/src/virtio/gpu/backend.rs b/devices/src/virtio/gpu/backend.rs index 75dbdafbdc..f13904c133 100644 --- a/devices/src/virtio/gpu/backend.rs +++ b/devices/src/virtio/gpu/backend.rs @@ -30,9 +30,6 @@ use crate::virtio::resource_bridge::*; use vm_control::{MaybeOwnedFd, VmMemoryControlRequestSocket, VmMemoryRequest, VmMemoryResponse}; -const DEFAULT_WIDTH: u32 = 1280; -const DEFAULT_HEIGHT: u32 = 1024; - struct VirtioGpuResource { width: u32, height: u32, @@ -52,12 +49,15 @@ impl VirtioGpuResource { } } - pub fn v2_new(kvm_slot: u32, gpu_resource: GpuRendererResource) -> VirtioGpuResource { - // Choose DEFAULT_WIDTH and DEFAULT_HEIGHT, since that matches the default modes - // for virtgpu-kms. + pub fn v2_new( + width: u32, + height: u32, + kvm_slot: u32, + gpu_resource: GpuRendererResource, + ) -> VirtioGpuResource { VirtioGpuResource { - width: DEFAULT_WIDTH, - height: DEFAULT_HEIGHT, + width, + height, gpu_resource, display_import: None, kvm_slot: Some(kvm_slot), @@ -155,6 +155,8 @@ impl VirtioGpuResource { /// failure, or requested data for the given command. pub struct Backend { display: Rc>, + display_width: u32, + display_height: u32, renderer: Renderer, resources: Map, contexts: Map, @@ -174,12 +176,16 @@ impl Backend { /// data is copied as needed. pub fn new( display: GpuDisplay, + display_width: u32, + display_height: u32, renderer: Renderer, gpu_device_socket: VmMemoryControlRequestSocket, pci_bar: Alloc, ) -> Backend { Backend { display: Rc::new(RefCell::new(display)), + display_width, + display_height, renderer, gpu_device_socket, resources: Default::default(), @@ -231,8 +237,8 @@ impl Backend { } /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples. - pub fn display_info(&self) -> &[(u32, u32)] { - &[(DEFAULT_WIDTH, DEFAULT_HEIGHT)] + pub fn display_info(&self) -> [(u32, u32); 1] { + [(self.display_width, self.display_height)] } /// Creates a 2D resource with the given properties and associated it with the given id. @@ -291,7 +297,7 @@ impl Backend { self.scanout_resource = id; if self.scanout_surface.is_none() { - match display.create_surface(None, DEFAULT_WIDTH, DEFAULT_HEIGHT) { + match display.create_surface(None, self.display_width, self.display_height) { Ok(surface) => self.scanout_surface = Some(surface), Err(e) => error!("failed to create display surface: {}", e), } @@ -328,7 +334,13 @@ impl Backend { return GpuResponse::OkNoData; } - let fb = match display.framebuffer_region(surface_id, 0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT) { + let fb = match display.framebuffer_region( + surface_id, + 0, + 0, + self.display_width, + self.display_height, + ) { Some(fb) => fb, None => { error!("failed to access framebuffer for surface {}", surface_id); @@ -339,8 +351,8 @@ impl Backend { resource.read_to_volatile( 0, 0, - DEFAULT_WIDTH, - DEFAULT_HEIGHT, + self.display_width, + self.display_height, fb.as_volatile_slice(), fb.stride(), ); @@ -866,7 +878,12 @@ impl Backend { Ok(_resq) => match self.gpu_device_socket.recv() { Ok(response) => match response { VmMemoryResponse::RegisterMemory { pfn: _, slot } => { - entry.insert(VirtioGpuResource::v2_new(slot, resource)); + entry.insert(VirtioGpuResource::v2_new( + self.display_width, + self.display_height, + slot, + resource, + )); GpuResponse::OkNoData } VmMemoryResponse::Err(e) => { @@ -891,8 +908,8 @@ impl Backend { } _ => { entry.insert(VirtioGpuResource::new( - DEFAULT_WIDTH, - DEFAULT_HEIGHT, + self.display_width, + self.display_height, resource, )); diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs index c9cd5cc94f..896e2ca664 100644 --- a/devices/src/virtio/gpu/mod.rs +++ b/devices/src/virtio/gpu/mod.rs @@ -39,6 +39,20 @@ use crate::pci::{PciBarConfiguration, PciBarPrefetchable, PciBarRegionType}; use vm_control::VmMemoryControlRequestSocket; +pub const DEFAULT_DISPLAY_WIDTH: u32 = 1280; +pub const DEFAULT_DISPLAY_HEIGHT: u32 = 1024; + +#[derive(Debug)] +pub struct GpuParameters { + pub display_width: u32, + pub display_height: u32, +} + +pub const DEFAULT_GPU_PARAMS: GpuParameters = GpuParameters { + display_width: DEFAULT_DISPLAY_WIDTH, + display_height: DEFAULT_DISPLAY_HEIGHT, +}; + // First queue is for virtio gpu commands. Second queue is for cursor commands, which we expect // there to be fewer of. const QUEUE_SIZES: &[u16] = &[256, 16]; @@ -656,6 +670,8 @@ impl DisplayBackend { // failed. fn build_backend( possible_displays: &[DisplayBackend], + display_width: u32, + display_height: u32, gpu_device_socket: VmMemoryControlRequestSocket, pci_bar: Alloc, ) -> Option { @@ -701,7 +717,14 @@ fn build_backend( } }; - Some(Backend::new(display, renderer, gpu_device_socket, pci_bar)) + Some(Backend::new( + display, + display_width, + display_height, + renderer, + gpu_device_socket, + pci_bar, + )) } pub struct Gpu { @@ -713,6 +736,8 @@ pub struct Gpu { worker_thread: Option>, num_scanouts: NonZeroU8, display_backends: Vec, + display_width: u32, + display_height: u32, pci_bar: Option, } @@ -723,6 +748,7 @@ impl Gpu { num_scanouts: NonZeroU8, resource_bridges: Vec, display_backends: Vec, + gpu_parameters: &GpuParameters, ) -> Gpu { Gpu { config_event: false, @@ -733,6 +759,8 @@ impl Gpu { worker_thread: None, num_scanouts, display_backends, + display_width: gpu_parameters.display_width, + display_height: gpu_parameters.display_height, pci_bar: None, } } @@ -851,6 +879,8 @@ impl VirtioDevice for Gpu { let cursor_queue = queues.remove(0); let cursor_evt = queue_evts.remove(0); let display_backends = self.display_backends.clone(); + let display_width = self.display_width; + let display_height = self.display_height; if let (Some(gpu_device_socket), Some(pci_bar)) = (self.gpu_device_socket.take(), self.pci_bar.take()) { @@ -858,11 +888,16 @@ impl VirtioDevice for Gpu { thread::Builder::new() .name("virtio_gpu".to_string()) .spawn(move || { - let backend = - match build_backend(&display_backends, gpu_device_socket, pci_bar) { - Some(backend) => backend, - None => return, - }; + let backend = match build_backend( + &display_backends, + display_width, + display_height, + gpu_device_socket, + pci_bar, + ) { + Some(backend) => backend, + None => return, + }; Worker { interrupt, diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs index de2266d891..c0b84fe66d 100644 --- a/devices/src/virtio/mod.rs +++ b/devices/src/virtio/mod.rs @@ -7,8 +7,6 @@ mod balloon; mod block; mod descriptor_utils; -#[cfg(feature = "gpu")] -mod gpu; mod input; mod interrupt; mod net; @@ -24,6 +22,8 @@ mod virtio_pci_device; mod wl; pub mod fs; +#[cfg(feature = "gpu")] +pub mod gpu; pub mod resource_bridge; pub mod vhost; diff --git a/src/crosvm.rs b/src/crosvm.rs index ff539ca396..de235739bd 100644 --- a/src/crosvm.rs +++ b/src/crosvm.rs @@ -17,6 +17,8 @@ use std::path::PathBuf; use std::str::FromStr; use devices::virtio::fs::passthrough; +#[cfg(feature = "gpu")] +use devices::virtio::gpu::GpuParameters; use devices::SerialParameters; use libc::{getegid, geteuid}; @@ -146,7 +148,8 @@ pub struct Config { pub sandbox: bool, pub seccomp_policy_dir: PathBuf, pub seccomp_log_failures: bool, - pub gpu: bool, + #[cfg(feature = "gpu")] + pub gpu_parameters: Option, pub software_tpm: bool, pub cras_audio: bool, pub cras_capture: bool, @@ -184,7 +187,8 @@ impl Default for Config { vhost_net: false, tap_fd: Vec::new(), cid: None, - gpu: false, + #[cfg(feature = "gpu")] + gpu_parameters: None, software_tpm: false, wayland_socket_path: None, wayland_dmabuf: false, diff --git a/src/linux.rs b/src/linux.rs index 0cc341daf4..1dd10048de 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -618,6 +618,7 @@ fn create_gpu_device( NonZeroU8::new(1).unwrap(), // number of scanouts gpu_sockets, display_backends, + cfg.gpu_parameters.as_ref().unwrap(), ); let jail = match simple_jail(&cfg, "gpu_device.policy")? { @@ -970,7 +971,7 @@ fn create_virtio_devices( #[cfg(feature = "gpu")] { - if cfg.gpu { + if cfg.gpu_parameters.is_some() { let (wl_socket, gpu_socket) = virtio::resource_bridge::pair().map_err(Error::CreateSocket)?; resource_bridges.push(gpu_socket); @@ -988,7 +989,7 @@ fn create_virtio_devices( #[cfg(feature = "gpu")] { - if cfg.gpu { + if cfg.gpu_parameters.is_some() { devs.push(create_gpu_device( cfg, _exit_evt, diff --git a/src/main.rs b/src/main.rs index 6ddef59afd..8d9c8906be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,8 @@ use crosvm::{ argument::{self, print_help, set_arguments, Argument}, linux, BindMount, Config, DiskOption, Executable, GidMap, SharedDir, TouchDeviceOption, }; +#[cfg(feature = "gpu")] +use devices::virtio::gpu::{GpuParameters, DEFAULT_GPU_PARAMS}; use devices::{SerialParameters, SerialType}; use msg_socket::{MsgReceiver, MsgSender, MsgSocket}; use qcow::QcowFile; @@ -108,6 +110,48 @@ fn parse_cpu_set(s: &str) -> argument::Result> { Ok(cpuset) } +#[cfg(feature = "gpu")] +fn parse_gpu_options(s: Option<&str>) -> argument::Result { + let mut gpu_params = DEFAULT_GPU_PARAMS; + + if let Some(s) = s { + let opts = s + .split(",") + .map(|frag| frag.split("=")) + .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or(""))); + + for (k, v) in opts { + match k { + "width" => { + gpu_params.display_width = + v.parse::() + .map_err(|_| argument::Error::InvalidValue { + value: v.to_string(), + expected: "gpu parameter 'width' must be a valid integer", + })?; + } + "height" => { + gpu_params.display_height = + v.parse::() + .map_err(|_| argument::Error::InvalidValue { + value: v.to_string(), + expected: "gpu parameter 'height' must be a valid integer", + })?; + } + "" => {} + _ => { + return Err(argument::Error::UnknownArgument(format!( + "gpu parameter {}", + k + ))); + } + } + } + } + + Ok(gpu_params) +} + fn parse_serial_options(s: &str) -> argument::Result { let mut serial_setting = SerialParameters { type_: SerialType::Sink, @@ -713,8 +757,10 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: })?, ); } + #[cfg(feature = "gpu")] "gpu" => { - cfg.gpu = true; + let params = parse_gpu_options(value)?; + cfg.gpu_parameters = Some(params); } "software-tpm" => { cfg.software_tpm = true; @@ -864,7 +910,7 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> { Argument::flag("null-audio", "Add an audio device to the VM that plays samples to /dev/null"), Argument::value("serial", "type=TYPE,[num=NUM,path=PATH,console,stdin]", - "Comma seperated key=value pairs for setting up serial devices. Can be given more than once. + "Comma separated key=value pairs for setting up serial devices. Can be given more than once. Possible key values: type=(stdout,syslog,sink,file) - Where to route the serial device num=(1,2,3,4) - Serial Device Number. If not provided, num will default to 1. @@ -909,7 +955,13 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa "fd", "File descriptor for configured tap device. A different virtual network card will be added each time this argument is given."), #[cfg(feature = "gpu")] - Argument::flag("gpu", "(EXPERIMENTAL) enable virtio-gpu device"), + Argument::flag_or_value("gpu", + "[width=INT,height=INT]", + "(EXPERIMENTAL) Comma separated key=value pairs for setting up a virtio-gpu device + Possible key values: + width=INT - The width of the virtual display connected to the virtio-gpu. + height=INT - The height of the virtual display connected to the virtio-gpu. + "), #[cfg(feature = "tpm")] Argument::flag("software-tpm", "enable a software emulated trusted platform module device"), Argument::value("evdev", "PATH", "Path to an event device node. The device will be grabbed (unusable from the host) and made available to the guest with the same configuration it shows on the host"),