diff --git a/devices/src/virtio/gpu/backend.rs b/devices/src/virtio/gpu/backend.rs index f13904c133..88456121dc 100644 --- a/devices/src/virtio/gpu/backend.rs +++ b/devices/src/virtio/gpu/backend.rs @@ -11,6 +11,8 @@ use std::os::unix::io::AsRawFd; use std::rc::Rc; use std::usize; +use libc::EINVAL; + use data_model::*; use msg_socket::{MsgReceiver, MsgSender}; @@ -19,7 +21,8 @@ use sys_util::{error, GuestAddress, GuestMemory}; use gpu_display::*; use gpu_renderer::{ - Box3, Context as RendererContext, Renderer, Resource as GpuRendererResource, ResourceCreateArgs, + Box3, Context as RendererContext, Error as GpuRendererError, Renderer, + Resource as GpuRendererResource, ResourceCreateArgs, }; use super::protocol::{ @@ -73,6 +76,7 @@ impl VirtioGpuResource { let (query, dmabuf) = match self.gpu_resource.export() { Ok(export) => (export.0, export.1), + Err(GpuRendererError::Virglrenderer(e)) if e == -EINVAL => return None, Err(e) => { error!("failed to query resource: {}", e); return None; @@ -160,6 +164,8 @@ pub struct Backend { renderer: Renderer, resources: Map, contexts: Map, + // Maps event devices to scanout number. + event_devices: Map, gpu_device_socket: VmMemoryControlRequestSocket, scanout_surface: Option, cursor_surface: Option, @@ -187,9 +193,10 @@ impl Backend { display_width, display_height, renderer, - gpu_device_socket, resources: Default::default(), contexts: Default::default(), + event_devices: Default::default(), + gpu_device_socket, scanout_surface: None, cursor_surface: None, scanout_resource: 0, @@ -203,6 +210,25 @@ impl Backend { &self.display } + pub fn import_event_device(&mut self, event_device: EventDevice, scanout: u32) { + // TODO(zachr): support more than one scanout. + if scanout != 0 { + return; + } + + let mut display = self.display.borrow_mut(); + let event_device_id = match display.import_event_device(event_device) { + Ok(id) => id, + Err(e) => { + error!("error importing event device: {}", e); + return; + } + }; + self.scanout_surface + .map(|s| display.attach_event_device(s, event_device_id)); + self.event_devices.insert(event_device_id, scanout); + } + /// Processes the internal `display` events and returns `true` if the main display was closed. pub fn process_display(&mut self) -> bool { let mut display = self.display.borrow_mut(); @@ -281,9 +307,9 @@ impl Backend { } /// Sets the given resource id as the source of scanout to the display. - pub fn set_scanout(&mut self, id: u32) -> GpuResponse { + pub fn set_scanout(&mut self, _scanout_id: u32, resource_id: u32) -> GpuResponse { let mut display = self.display.borrow_mut(); - if id == 0 { + if resource_id == 0 { if let Some(surface) = self.scanout_surface.take() { display.release_surface(surface); } @@ -293,12 +319,19 @@ impl Backend { } self.cursor_resource = 0; GpuResponse::OkNoData - } else if self.resources.get_mut(&id).is_some() { - self.scanout_resource = id; + } else if self.resources.get_mut(&resource_id).is_some() { + self.scanout_resource = resource_id; if self.scanout_surface.is_none() { match display.create_surface(None, self.display_width, self.display_height) { - Ok(surface) => self.scanout_surface = Some(surface), + Ok(surface) => { + // TODO(zachr): do not assume every event device belongs to this scanout_id; + // in other words, support multiple display outputs. + for &event_device in self.event_devices.keys() { + display.attach_event_device(surface, event_device); + } + self.scanout_surface = Some(surface); + } Err(e) => error!("failed to create display surface: {}", e), } } diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs index 896e2ca664..637f22be3f 100644 --- a/devices/src/virtio/gpu/mod.rs +++ b/devices/src/virtio/gpu/mod.rs @@ -23,6 +23,7 @@ use sys_util::{ debug, error, warn, Error, EventFd, GuestAddress, GuestMemory, PollContext, PollToken, }; +pub use gpu_display::EventDevice; use gpu_display::*; use gpu_renderer::{Renderer, RendererFlags}; @@ -119,7 +120,9 @@ impl Frontend { GpuCommand::ResourceUnref(info) => { self.backend.unref_resource(info.resource_id.to_native()) } - GpuCommand::SetScanout(info) => self.backend.set_scanout(info.resource_id.to_native()), + GpuCommand::SetScanout(info) => self + .backend + .set_scanout(info.scanout_id.to_native(), info.resource_id.to_native()), GpuCommand::ResourceFlush(info) => self.backend.flush_resource( info.resource_id.to_native(), info.r.x.to_native(), @@ -672,6 +675,7 @@ fn build_backend( possible_displays: &[DisplayBackend], display_width: u32, display_height: u32, + event_devices: Vec, gpu_device_socket: VmMemoryControlRequestSocket, pci_bar: Alloc, ) -> Option { @@ -717,22 +721,29 @@ fn build_backend( } }; - Some(Backend::new( + let mut backend = Backend::new( display, display_width, display_height, renderer, gpu_device_socket, pci_bar, - )) + ); + + for event_device in event_devices { + backend.import_event_device(event_device, 0); + } + + Some(backend) } pub struct Gpu { - config_event: bool, exit_evt: EventFd, gpu_device_socket: Option, resource_bridges: Vec, + event_devices: Vec, kill_evt: Option, + config_event: bool, worker_thread: Option>, num_scanouts: NonZeroU8, display_backends: Vec, @@ -749,15 +760,17 @@ impl Gpu { resource_bridges: Vec, display_backends: Vec, gpu_parameters: &GpuParameters, + event_devices: Vec, ) -> Gpu { Gpu { - config_event: false, exit_evt, gpu_device_socket, + num_scanouts, resource_bridges, + event_devices, + config_event: false, kill_evt: None, worker_thread: None, - num_scanouts, display_backends, display_width: gpu_parameters.display_width, display_height: gpu_parameters.display_height, @@ -881,6 +894,7 @@ impl VirtioDevice for Gpu { let display_backends = self.display_backends.clone(); let display_width = self.display_width; let display_height = self.display_height; + let event_devices = self.event_devices.split_off(0); if let (Some(gpu_device_socket), Some(pci_bar)) = (self.gpu_device_socket.take(), self.pci_bar.take()) { @@ -892,6 +906,7 @@ impl VirtioDevice for Gpu { &display_backends, display_width, display_height, + event_devices, gpu_device_socket, pci_bar, ) { diff --git a/src/crosvm.rs b/src/crosvm.rs index 0e0e218f46..8a15c6b42d 100644 --- a/src/crosvm.rs +++ b/src/crosvm.rs @@ -155,6 +155,8 @@ pub struct Config { pub cras_audio: bool, pub cras_capture: bool, pub null_audio: bool, + pub display_window_keyboard: bool, + pub display_window_mouse: bool, pub serial_parameters: BTreeMap, pub syslog_tag: Option, pub virtio_single_touch: Option, @@ -194,6 +196,8 @@ impl Default for Config { wayland_socket_path: None, wayland_dmabuf: false, x_display: None, + display_window_keyboard: false, + display_window_mouse: false, shared_dirs: Vec::new(), sandbox: !cfg!(feature = "default-no-sandbox"), seccomp_policy_dir: PathBuf::from(SECCOMP_POLICY_DIR), diff --git a/src/linux.rs b/src/linux.rs index a9a3445755..57b3ef5a08 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -28,6 +28,8 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use libc::{self, c_int, gid_t, uid_t}; use audio_streams::DummyStreamSource; +#[cfg(feature = "gpu")] +use devices::virtio::EventDevice; use devices::virtio::{self, VirtioDevice}; use devices::{ self, HostBackendDeviceProvider, PciDevice, VfioDevice, VfioPciDevice, VirtioPciDevice, @@ -456,7 +458,7 @@ fn create_tpm_device(cfg: &Config) -> DeviceResult { } fn create_single_touch_device(cfg: &Config, single_touch_spec: &TouchDeviceOption) -> DeviceResult { - let socket = create_input_socket(&single_touch_spec.path).map_err(|e| { + let socket = single_touch_spec.path.into_unix_stream().map_err(|e| { error!("failed configuring virtio single touch: {:?}", e); e })?; @@ -470,7 +472,7 @@ fn create_single_touch_device(cfg: &Config, single_touch_spec: &TouchDeviceOptio } fn create_trackpad_device(cfg: &Config, trackpad_spec: &TouchDeviceOption) -> DeviceResult { - let socket = create_input_socket(&trackpad_spec.path).map_err(|e| { + let socket = trackpad_spec.path.into_unix_stream().map_err(|e| { error!("failed configuring virtio trackpad: {}", e); e })?; @@ -484,8 +486,8 @@ fn create_trackpad_device(cfg: &Config, trackpad_spec: &TouchDeviceOption) -> De }) } -fn create_mouse_device(cfg: &Config, mouse_socket: &Path) -> DeviceResult { - let socket = create_input_socket(&mouse_socket).map_err(|e| { +fn create_mouse_device(cfg: &Config, mouse_socket: T) -> DeviceResult { + let socket = mouse_socket.into_unix_stream().map_err(|e| { error!("failed configuring virtio mouse: {}", e); e })?; @@ -498,8 +500,8 @@ fn create_mouse_device(cfg: &Config, mouse_socket: &Path) -> DeviceResult { }) } -fn create_keyboard_device(cfg: &Config, keyboard_socket: &Path) -> DeviceResult { - let socket = create_input_socket(&keyboard_socket).map_err(|e| { +fn create_keyboard_device(cfg: &Config, keyboard_socket: T) -> DeviceResult { + let socket = keyboard_socket.into_unix_stream().map_err(|e| { error!("failed configuring virtio keyboard: {}", e); e })?; @@ -589,6 +591,7 @@ fn create_gpu_device( gpu_sockets: Vec, wayland_socket_path: Option, x_display: Option, + event_devices: Vec, ) -> DeviceResult { let jailed_wayland_path = Path::new("/wayland-0"); @@ -615,6 +618,7 @@ fn create_gpu_device( gpu_sockets, display_backends, cfg.gpu_parameters.as_ref().unwrap(), + event_devices, ); let jail = match simple_jail(&cfg, "gpu_device.policy")? { @@ -986,6 +990,31 @@ fn create_virtio_devices( #[cfg(feature = "gpu")] { if cfg.gpu_parameters.is_some() { + let mut event_devices = Vec::new(); + if cfg.display_window_mouse { + let (event_device_socket, virtio_dev_socket) = + UnixStream::pair().map_err(Error::CreateSocket)?; + // TODO(nkgold): the width/height here should match the display's height/width. When + // those settings are available as CLI options, we should use the CLI options here + // as well. + let dev = virtio::new_single_touch(virtio_dev_socket, 1280, 1024) + .map_err(Error::InputDeviceNew)?; + devs.push(VirtioDeviceStub { + dev: Box::new(dev), + jail: simple_jail(&cfg, "input_device.policy")?, + }); + event_devices.push(EventDevice::touchscreen(event_device_socket)); + } + if cfg.display_window_keyboard { + let (event_device_socket, virtio_dev_socket) = + UnixStream::pair().map_err(Error::CreateSocket)?; + let dev = virtio::new_keyboard(virtio_dev_socket).map_err(Error::InputDeviceNew)?; + devs.push(VirtioDeviceStub { + dev: Box::new(dev), + jail: simple_jail(&cfg, "input_device.policy")?, + }); + event_devices.push(EventDevice::keyboard(event_device_socket)); + } devs.push(create_gpu_device( cfg, _exit_evt, @@ -993,6 +1022,7 @@ fn create_virtio_devices( resource_bridges, cfg.wayland_socket_path.clone(), cfg.x_display.clone(), + event_devices, )?); } } @@ -1160,12 +1190,29 @@ fn raw_fd_from_path(path: &Path) -> Result { validate_raw_fd(raw_fd).map_err(Error::ValidateRawFd) } -fn create_input_socket(path: &Path) -> Result { - if path.parent() == Some(Path::new("/proc/self/fd")) { - // Safe because we will validate |raw_fd|. - unsafe { Ok(UnixStream::from_raw_fd(raw_fd_from_path(path)?)) } - } else { - UnixStream::connect(path).map_err(Error::InputEventsOpen) +trait IntoUnixStream { + fn into_unix_stream(self) -> Result; +} + +impl<'a> IntoUnixStream for &'a Path { + fn into_unix_stream(self) -> Result { + if self.parent() == Some(Path::new("/proc/self/fd")) { + // Safe because we will validate |raw_fd|. + unsafe { Ok(UnixStream::from_raw_fd(raw_fd_from_path(self)?)) } + } else { + UnixStream::connect(self).map_err(Error::InputEventsOpen) + } + } +} +impl<'a> IntoUnixStream for &'a PathBuf { + fn into_unix_stream(self) -> Result { + self.as_path().into_unix_stream() + } +} + +impl IntoUnixStream for UnixStream { + fn into_unix_stream(self) -> Result { + Ok(self) } } diff --git a/src/main.rs b/src/main.rs index 9c97c4dbaf..7f12841155 100644 --- a/src/main.rs +++ b/src/main.rs @@ -511,6 +511,12 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: } cfg.x_display = Some(value.unwrap().to_owned()); } + "display-window-keyboard" => { + cfg.display_window_keyboard = true; + } + "display-window-mouse" => { + cfg.display_window_mouse = true; + } "socket" => { if cfg.socket_path.is_some() { return Err(argument::Error::TooManyArguments( @@ -931,6 +937,8 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> { "), Argument::value("syslog-tag", "TAG", "When logging to syslog, use the provided tag."), Argument::value("x-display", "DISPLAY", "X11 display name to use."), + Argument::flag("display-window-keyboard", "Capture keyboard input from the display window."), + Argument::flag("display-window-mouse", "Capture keyboard input from the display window."), Argument::value("wayland-sock", "PATH", "Path to the Wayland socket to use."), #[cfg(feature = "wl-dmabuf")] Argument::flag("wayland-dmabuf", "Enable support for DMABufs in Wayland device."),