summary refs log tree commit diff
path: root/devices/src/virtio/gpu/virtio_backend.rs
diff options
context:
space:
mode:
Diffstat (limited to 'devices/src/virtio/gpu/virtio_backend.rs')
-rw-r--r--devices/src/virtio/gpu/virtio_backend.rs281
1 files changed, 281 insertions, 0 deletions
diff --git a/devices/src/virtio/gpu/virtio_backend.rs b/devices/src/virtio/gpu/virtio_backend.rs
new file mode 100644
index 0000000..605cf5a
--- /dev/null
+++ b/devices/src/virtio/gpu/virtio_backend.rs
@@ -0,0 +1,281 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cell::RefCell;
+use std::collections::BTreeMap as Map;
+use std::num::NonZeroU32;
+use std::rc::Rc;
+
+use super::protocol::GpuResponse;
+use data_model::*;
+use gpu_display::*;
+use sys_util::{error, GuestMemory};
+
+pub trait VirtioResource {
+    fn width(&self) -> u32;
+
+    fn height(&self) -> u32;
+
+    fn import_to_display(&mut self, display: &Rc<RefCell<GpuDisplay>>) -> Option<u32>;
+
+    /// Performs a transfer to the given resource in the host from its backing in guest memory.
+    fn write_from_guest_memory(
+        &mut self,
+        x: u32,
+        y: u32,
+        width: u32,
+        height: u32,
+        src_offset: u64,
+        _mem: &GuestMemory,
+    );
+
+    /// Reads from this resource in the host to a volatile slice of memory.
+    fn read_to_volatile(
+        &mut self,
+        x: u32,
+        y: u32,
+        width: u32,
+        height: u32,
+        dst: VolatileSlice,
+        dst_stride: u32,
+    );
+}
+
+/// Handles some of the common functionality across the virtio 2D and 3D backends.
+pub struct VirtioBackend {
+    pub display: Rc<RefCell<GpuDisplay>>,
+    pub display_width: u32,
+    pub display_height: u32,
+    pub scanout_resource_id: Option<NonZeroU32>,
+    pub scanout_surface_id: Option<u32>,
+    pub cursor_resource_id: Option<NonZeroU32>,
+    pub cursor_surface_id: Option<u32>,
+    // Maps event devices to scanout number.
+    pub event_devices: Map<u32, u32>,
+}
+
+impl VirtioBackend {
+    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_id
+            .map(|s| display.attach_event_device(s, event_device_id));
+        self.event_devices.insert(event_device_id, scanout);
+    }
+
+    /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples.
+    pub fn display_info(&self) -> [(u32, u32); 1] {
+        [(self.display_width, self.display_height)]
+    }
+
+    /// 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();
+        display.dispatch_events();
+        self.scanout_surface_id
+            .map(|s| display.close_requested(s))
+            .unwrap_or(false)
+    }
+
+    /// Sets the given resource id as the source of scanout to the display.
+    pub fn set_scanout(&mut self, resource_id: u32) -> GpuResponse {
+        let mut display = self.display.borrow_mut();
+        if resource_id == 0 {
+            if let Some(surface_id) = self.scanout_surface_id.take() {
+                display.release_surface(surface_id);
+            }
+            self.scanout_resource_id = None;
+            GpuResponse::OkNoData
+        } else {
+            self.scanout_resource_id = NonZeroU32::new(resource_id);
+
+            if self.scanout_surface_id.is_none() {
+                match display.create_surface(None, self.display_width, self.display_height) {
+                    Ok(surface_id) => {
+                        self.scanout_surface_id = Some(surface_id);
+                    }
+                    Err(e) => error!("failed to create display surface: {}", e),
+                }
+            }
+            GpuResponse::OkNoData
+        }
+    }
+
+    pub fn flush_resource(
+        &mut self,
+        resource: &mut dyn VirtioResource,
+        resource_id: u32,
+    ) -> GpuResponse {
+        let mut response = GpuResponse::OkNoData;
+
+        if let Some(scannout_resource_id) = self.scanout_resource_id {
+            if scannout_resource_id.get() == resource_id {
+                response = self.flush_scannout_resource_to_surface(resource);
+            }
+        }
+
+        if response != GpuResponse::OkNoData {
+            return response;
+        }
+
+        if let Some(cursor_resource_id) = self.cursor_resource_id {
+            if cursor_resource_id.get() == resource_id {
+                response = self.flush_cursor_resource_to_surface(resource);
+            }
+        }
+
+        response
+    }
+
+    pub fn flush_scannout_resource_to_surface(
+        &mut self,
+        resource: &mut dyn VirtioResource,
+    ) -> GpuResponse {
+        match self.scanout_surface_id {
+            Some(surface_id) => self.flush_resource_to_surface(resource, surface_id),
+            None => GpuResponse::OkNoData,
+        }
+    }
+
+    pub fn flush_cursor_resource_to_surface(
+        &mut self,
+        resource: &mut dyn VirtioResource,
+    ) -> GpuResponse {
+        match self.cursor_surface_id {
+            Some(surface_id) => self.flush_resource_to_surface(resource, surface_id),
+            None => GpuResponse::OkNoData,
+        }
+    }
+
+    pub fn flush_resource_to_surface(
+        &mut self,
+        resource: &mut dyn VirtioResource,
+        surface_id: u32,
+    ) -> GpuResponse {
+        if let Some(import_id) = resource.import_to_display(&self.display) {
+            self.display.borrow_mut().flip_to(surface_id, import_id);
+            return GpuResponse::OkNoData;
+        }
+
+        // Import failed, fall back to a copy.
+        let mut display = self.display.borrow_mut();
+        // Prevent overwriting a buffer that is currently being used by the compositor.
+        if display.next_buffer_in_use(surface_id) {
+            return GpuResponse::OkNoData;
+        }
+
+        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);
+                return GpuResponse::ErrUnspec;
+            }
+        };
+
+        resource.read_to_volatile(
+            0,
+            0,
+            self.display_width,
+            self.display_height,
+            fb.as_volatile_slice(),
+            fb.stride(),
+        );
+
+        display.flip(surface_id);
+
+        GpuResponse::OkNoData
+    }
+
+    /// Updates the cursor's memory to the given id, and sets its position to the given coordinates.
+    pub fn update_cursor(
+        &mut self,
+        id: u32,
+        x: u32,
+        y: u32,
+        resource: Option<&mut dyn VirtioResource>,
+    ) -> GpuResponse {
+        if id == 0 {
+            if let Some(surface_id) = self.cursor_surface_id.take() {
+                self.display.borrow_mut().release_surface(surface_id);
+            }
+            self.cursor_resource_id = None;
+            GpuResponse::OkNoData
+        } else if let Some(resource) = resource {
+            self.cursor_resource_id = NonZeroU32::new(id);
+
+            if self.cursor_surface_id.is_none() {
+                match self.display.borrow_mut().create_surface(
+                    self.scanout_surface_id,
+                    resource.width(),
+                    resource.height(),
+                ) {
+                    Ok(surface_id) => self.cursor_surface_id = Some(surface_id),
+                    Err(e) => {
+                        error!("failed to create cursor surface: {}", e);
+                        return GpuResponse::ErrUnspec;
+                    }
+                }
+            }
+
+            let cursor_surface_id = self.cursor_surface_id.unwrap();
+            self.display
+                .borrow_mut()
+                .set_position(cursor_surface_id, x, y);
+
+            // Gets the resource's pixels into the display by importing the buffer.
+            if let Some(import_id) = resource.import_to_display(&self.display) {
+                self.display
+                    .borrow_mut()
+                    .flip_to(cursor_surface_id, import_id);
+                return GpuResponse::OkNoData;
+            }
+
+            // Importing failed, so try copying the pixels into the surface's slower shared memory
+            // framebuffer.
+            if let Some(fb) = self.display.borrow_mut().framebuffer(cursor_surface_id) {
+                resource.read_to_volatile(
+                    0,
+                    0,
+                    resource.width(),
+                    resource.height(),
+                    fb.as_volatile_slice(),
+                    fb.stride(),
+                )
+            }
+            self.display.borrow_mut().flip(cursor_surface_id);
+            GpuResponse::OkNoData
+        } else {
+            GpuResponse::ErrInvalidResourceId
+        }
+    }
+
+    /// Moves the cursor's position to the given coordinates.
+    pub fn move_cursor(&mut self, x: u32, y: u32) -> GpuResponse {
+        if let Some(cursor_surface_id) = self.cursor_surface_id {
+            if let Some(scanout_surface_id) = self.scanout_surface_id {
+                let mut display = self.display.borrow_mut();
+                display.set_position(cursor_surface_id, x, y);
+                display.commit(scanout_surface_id);
+            }
+        }
+        GpuResponse::OkNoData
+    }
+}