summary refs log tree commit diff
path: root/gpu_buffer/src/lib.rs
diff options
context:
space:
mode:
authorZach Reizner <zachr@google.com>2018-04-25 17:49:56 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-05-08 04:57:58 -0700
commit6f6854312df5691763f3ee80be84fde16e82f6b8 (patch)
tree2d853cff0b8f3e5ad442a198af7c8840243b5435 /gpu_buffer/src/lib.rs
parente0823392f4dde29f3bde7f98d0bc481654843d27 (diff)
downloadcrosvm-6f6854312df5691763f3ee80be84fde16e82f6b8.tar
crosvm-6f6854312df5691763f3ee80be84fde16e82f6b8.tar.gz
crosvm-6f6854312df5691763f3ee80be84fde16e82f6b8.tar.bz2
crosvm-6f6854312df5691763f3ee80be84fde16e82f6b8.tar.lz
crosvm-6f6854312df5691763f3ee80be84fde16e82f6b8.tar.xz
crosvm-6f6854312df5691763f3ee80be84fde16e82f6b8.tar.zst
crosvm-6f6854312df5691763f3ee80be84fde16e82f6b8.zip
gpu_buffer: create bindings to minigbm
These bindings are needed to allocate dmabufs that will be used for
accelerated rendering and zero-copy virtio-wayland support.

TEST=cargo test -p gpu_buffer
BUG=chromium:837073

Change-Id: I96d7bcdeaa1eda616a25fdcfedcbb734cd585ae7
Reviewed-on: https://chromium-review.googlesource.com/1029410
Commit-Ready: David Reveman <reveman@chromium.org>
Tested-by: Zach Reizner <zachr@chromium.org>
Reviewed-by: Zach Reizner <zachr@chromium.org>
Reviewed-by: Dylan Reid <dgreid@chromium.org>
Diffstat (limited to 'gpu_buffer/src/lib.rs')
-rw-r--r--gpu_buffer/src/lib.rs553
1 files changed, 553 insertions, 0 deletions
diff --git a/gpu_buffer/src/lib.rs b/gpu_buffer/src/lib.rs
new file mode 100644
index 0000000..6d0cfa9
--- /dev/null
+++ b/gpu_buffer/src/lib.rs
@@ -0,0 +1,553 @@
+// Copyright 2018 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.
+
+//! A crate for creating [DRM](https://en.wikipedia.org/wiki/Direct_Rendering_Manager) managed
+//! buffer objects. Such objects are useful for exporting as DMABUFs/prime FDs, texturing, render
+//! targets, memory mapping, and scanout.
+//!
+//! # Examples
+//!
+//! ```rust
+//! # use std::error::Error;
+//! # use std::fs::File;
+//! # use std::result::Result;
+//! # use gpu_buffer::*;
+//! # fn test() -> Result<(), Box<Error>> {
+//! let drm_card = File::open("/dev/dri/card0")?;
+//! let device = Device::new(drm_card).map_err(|_| "failed to create device")?;
+//! let bo = device
+//!     .create_buffer(1024,
+//!                    512,
+//!                    Format::new(b'X', b'R', b'2', b'4'),
+//!                    Flags::empty().use_scanout(true))
+//!     .map_err(|_| "failed to create buffer")?;
+//! assert_eq!(bo.width(), 1024);
+//! assert_eq!(bo.height(), 512);
+//! assert_eq!(bo.format(), Format::new(b'X', b'R', b'2', b'4'));
+//! assert_eq!(bo.num_planes(), 1);
+//! # Ok(())
+//! # }
+//! ```
+
+extern crate data_model;
+
+mod raw;
+
+use std::os::raw::c_void;
+use std::fmt;
+use std::cmp::min;
+use std::fs::File;
+use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
+use std::ptr::{copy_nonoverlapping, null_mut};
+use std::rc::Rc;
+use std::result::Result;
+
+use data_model::VolatileSlice;
+
+use raw::*;
+
+const MAP_FAILED: *mut c_void = (-1isize as *mut _);
+
+/// A [fourcc](https://en.wikipedia.org/wiki/FourCC) format identifier.
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub struct Format(u32);
+
+impl Format {
+    /// Constructs a format identifer using a fourcc byte sequence.
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use gpu_buffer::Format;
+    ///
+    /// let format = Format::new(b'X', b'R', b'2', b'4');
+    /// println!("format: {:?}", format);
+    /// ```
+    #[inline(always)]
+    pub fn new(a: u8, b: u8, c: u8, d: u8) -> Format {
+        Format(a as u32 | (b as u32) << 8 | (c as u32) << 16 | (d as u32) << 24)
+    }
+
+    /// Returns the fourcc code as a sequence of bytes.
+    #[inline(always)]
+    pub fn to_bytes(&self) -> [u8; 4] {
+        let f = self.0;
+        [f as u8, (f >> 8) as u8, (f >> 16) as u8, (f >> 24) as u8]
+    }
+}
+
+impl From<u32> for Format {
+    fn from(u: u32) -> Format {
+        Format(u)
+    }
+}
+
+impl From<Format> for u32 {
+    fn from(f: Format) -> u32 {
+        f.0
+    }
+}
+
+impl fmt::Debug for Format {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let b = self.to_bytes();
+        if b.iter().all(u8::is_ascii_graphic) {
+            write!(f,
+                   "fourcc({}{}{}{})",
+                   b[0] as char,
+                   b[1] as char,
+                   b[2] as char,
+                   b[3] as char)
+        } else {
+            write!(f,
+                   "fourcc(0x{:02x}{:02x}{:02x}{:02x})",
+                   b[0],
+                   b[1],
+                   b[2],
+                   b[3])
+        }
+    }
+}
+
+/// Usage flags for constructing a buffer object.
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub struct Flags(u32);
+
+impl Flags {
+    /// Returns empty set of flags.
+    #[inline(always)]
+    pub fn empty() -> Flags {
+        Flags(0)
+    }
+
+    /// Returns the given set of raw `GBM_BO` flags wrapped in a `Flags` struct.
+    #[inline(always)]
+    pub fn new(raw: u32) -> Flags {
+        Flags(raw)
+    }
+
+    /// Sets the scanout flag's presence
+    #[inline(always)]
+    pub fn use_scanout(self, e: bool) -> Flags {
+        if e {
+            Flags(self.0 | GBM_BO_USE_SCANOUT)
+        } else {
+            Flags(self.0 & !GBM_BO_USE_SCANOUT)
+        }
+    }
+
+    /// Sets the cursor flag's presence
+    #[inline(always)]
+    pub fn use_cursor(self, e: bool) -> Flags {
+        if e {
+            Flags(self.0 | GBM_BO_USE_CURSOR)
+        } else {
+            Flags(self.0 & !GBM_BO_USE_CURSOR)
+        }
+    }
+
+    /// Sets the cursor 64x64 flag's presence
+    #[inline(always)]
+    pub fn use_cursor64(self, e: bool) -> Flags {
+        if e {
+            Flags(self.0 | GBM_BO_USE_CURSOR_64X64)
+        } else {
+            Flags(self.0 & !GBM_BO_USE_CURSOR_64X64)
+        }
+    }
+
+    /// Sets the rendering flag's presence
+    #[inline(always)]
+    pub fn use_rendering(self, e: bool) -> Flags {
+        if e {
+            Flags(self.0 | GBM_BO_USE_RENDERING)
+        } else {
+            Flags(self.0 & !GBM_BO_USE_RENDERING)
+        }
+    }
+
+    /// Sets the linear flag's presence
+    #[inline(always)]
+    pub fn use_linear(self, e: bool) -> Flags {
+        if e {
+            Flags(self.0 | GBM_BO_USE_LINEAR)
+        } else {
+            Flags(self.0 & !GBM_BO_USE_LINEAR)
+        }
+    }
+
+    /// Sets the texturing flag's presence
+    #[inline(always)]
+    pub fn use_texturing(self, e: bool) -> Flags {
+        if e {
+            Flags(self.0 | GBM_BO_USE_TEXTURING)
+        } else {
+            Flags(self.0 & !GBM_BO_USE_TEXTURING)
+        }
+    }
+}
+
+
+struct DeviceInner {
+    _fd: File,
+    gbm: *mut gbm_device,
+}
+
+impl Drop for DeviceInner {
+    fn drop(self: &mut DeviceInner) {
+        // Safe because DeviceInner is only constructed with a valid gbm_device.
+        unsafe {
+            gbm_device_destroy(self.gbm);
+        }
+    }
+}
+
+/// A device capable of allocating `Buffer`.
+#[derive(Clone)]
+pub struct Device(Rc<DeviceInner>);
+
+impl Device {
+    /// Returns a new `Device` using the given `fd` opened from a device in `/dev/dri/`.
+    pub fn new(fd: File) -> Result<Device, ()> {
+        // gbm_create_device is safe to call with a valid fd, and we check that a valid one is
+        // returned. The FD is not of the appropriate kind (i.e. not a DRM device),
+        // gbm_create_device should reject it.
+        let gbm = unsafe { gbm_create_device(fd.as_raw_fd()) };
+        if gbm.is_null() {
+            Err(())
+        } else {
+            Ok(Device(Rc::new(DeviceInner { _fd: fd, gbm })))
+        }
+    }
+
+    /// Creates a new buffer with the given metadata.
+    pub fn create_buffer(&self,
+                         width: u32,
+                         height: u32,
+                         format: Format,
+                         usage: Flags)
+                         -> Result<Buffer, ()> {
+        // This is safe because only a valid gbm_device is used and the return value is checked.
+        let bo = unsafe { gbm_bo_create(self.0.gbm, width, height, format.0, usage.0) };
+        if bo.is_null() {
+            Err(())
+        } else {
+            Ok(Buffer(bo, self.clone()))
+        }
+    }
+}
+
+/// An allocation from a `Device`.
+pub struct Buffer(*mut gbm_bo, Device);
+
+impl Buffer {
+    /// The device
+    pub fn device(&self) -> &Device {
+        &self.1
+    }
+
+    /// Width in pixels.
+    pub fn width(&self) -> u32 {
+        // This is always safe to call with a valid gbm_bo pointer.
+        unsafe { gbm_bo_get_width(self.0) }
+    }
+
+    /// Height in pixels.
+    pub fn height(&self) -> u32 {
+        // This is always safe to call with a valid gbm_bo pointer.
+        unsafe { gbm_bo_get_height(self.0) }
+    }
+
+    /// Length in bytes of one row of the buffer.
+    pub fn stride(&self) -> u32 {
+        // This is always safe to call with a valid gbm_bo pointer.
+        unsafe { gbm_bo_get_stride(self.0) }
+    }
+
+    /// Length in bytes of the stride or tiling.
+    pub fn stride_or_tiling(&self) -> u32 {
+        // This is always safe to call with a valid gbm_bo pointer.
+        unsafe { gbm_bo_get_stride_or_tiling(self.0) }
+    }
+
+    /// `Format` of the buffer.
+    pub fn format(&self) -> Format {
+        // This is always safe to call with a valid gbm_bo pointer.
+        unsafe { Format(gbm_bo_get_format(self.0)) }
+    }
+
+    /// Format modifier flags for the buffer.
+    pub fn format_modifier(&self) -> u64 {
+        // This is always safe to call with a valid gbm_bo pointer.
+        unsafe { gbm_bo_get_format_modifier(self.0) }
+    }
+
+    /// Number of planes present in this buffer.
+    pub fn num_planes(&self) -> usize {
+        // This is always safe to call with a valid gbm_bo pointer.
+        unsafe { gbm_bo_get_num_planes(self.0) }
+    }
+
+    /// Exports a new dmabuf/prime file descriptor for the given plane.
+    pub fn export_plane_fd(&self, plane: usize) -> Result<File, i32> {
+        // This is always safe to call with a valid gbm_bo pointer.
+        match unsafe { gbm_bo_get_plane_fd(self.0, plane) } {
+            fd if fd >= 0 => Ok(unsafe { File::from_raw_fd(fd) }),
+            ret => Err(ret),
+        }
+    }
+
+    /// Reads the given subsection of the buffer to `dst`.
+    pub fn read_to_volatile(&self,
+                            x: u32,
+                            y: u32,
+                            width: u32,
+                            height: u32,
+                            plane: usize,
+                            dst: VolatileSlice)
+                            -> Result<(), ()> {
+        let mut stride = 0;
+        let mut map_data = null_mut();
+        // Safe because only a valid gbm_bo object is used and the return value is checked. Only
+        // pointers coerced from stack references are used for returned values, and we trust gbm to
+        // only write as many bytes as the size of the pointed to values.
+        let mapping = unsafe {
+            gbm_bo_map(self.0,
+                       x,
+                       y,
+                       width,
+                       height,
+                       GBM_BO_TRANSFER_READ,
+                       &mut stride,
+                       &mut map_data,
+                       plane)
+        };
+        if mapping == MAP_FAILED {
+            return Err(());
+        }
+
+        let copy_size = (y as u64) * (stride as u64);
+
+        let res = if copy_size <= dst.size() {
+            // The two buffers can not be overlapping because we just made a new mapping in this
+            // scope.
+            unsafe {
+                copy_nonoverlapping(mapping as *mut u8, dst.as_ptr(), copy_size as usize);
+            }
+            Ok(())
+        } else {
+            Err(())
+        };
+
+        // safe because the gbm_bo is assumed to be valid and the map_data is the same one given by
+        // gbm_bo_map.
+        unsafe {
+            gbm_bo_unmap(self.0, map_data);
+        }
+
+        res
+    }
+
+    /// Writes to the given subsection of the buffer from `src`.
+    pub fn write_from_slice(&self,
+                            x: u32,
+                            y: u32,
+                            width: u32,
+                            height: u32,
+                            plane: usize,
+                            src: &[u8])
+                            -> Result<(), ()> {
+        let mut stride = 0;
+        let mut map_data = null_mut();
+        // Safe because only a valid gbm_bo object is used and the return value is checked. Only
+        // pointers coerced from stack references are used for returned values, and we trust gbm to
+        // only write as many bytes as the size of the pointed to values.
+        let mapping = unsafe {
+            gbm_bo_map(self.0,
+                       x,
+                       y,
+                       width,
+                       height,
+                       GBM_BO_TRANSFER_WRITE,
+                       &mut stride,
+                       &mut map_data,
+                       plane)
+        };
+        if mapping == MAP_FAILED {
+            return Err(());
+        }
+
+        let copy_size = (height as u64) * (stride as u64);
+        let copy_sg_size = min(src.len() as u64, copy_size);
+
+        // The two buffers can not be overlapping because we just made a new mapping in this scope.
+        unsafe {
+            copy_nonoverlapping(src.as_ptr(), mapping as *mut u8, copy_sg_size as usize);
+        }
+
+        // safe because the gbm_bo is assumed to be valid and the map_data is the same one given by
+        // gbm_bo_map.
+        unsafe {
+            gbm_bo_unmap(self.0, map_data);
+        }
+
+        Ok(())
+    }
+
+    /// Writes to the given subsection of the buffer from `sgs`.
+    pub fn write_from_sg<'a, S: Iterator<Item = VolatileSlice<'a>>>(&self,
+                                                                    x: u32,
+                                                                    y: u32,
+                                                                    width: u32,
+                                                                    height: u32,
+                                                                    plane: usize,
+                                                                    sgs: S)
+                                                                    -> Result<(), ()> {
+        let mut stride = 0;
+        let mut map_data = null_mut();
+        // Safe because only a valid gbm_bo object is used and the return value is checked. Only
+        // pointers coerced from stack references are used for returned values, and we trust gbm to
+        // only write as many bytes as the size of the pointed to values.
+        let mut mapping = unsafe {
+            gbm_bo_map(self.0,
+                       x,
+                       y,
+                       width,
+                       height,
+                       GBM_BO_TRANSFER_WRITE,
+                       &mut stride,
+                       &mut map_data,
+                       plane)
+        };
+        if mapping == MAP_FAILED {
+            return Err(());
+        }
+
+        let mut copy_size = (height as u64) * (stride as u64);
+
+        for sg in sgs {
+            let copy_sg_size = min(sg.size(), copy_size);
+            // The two buffers can not be overlapping because we just made a new mapping in this
+            // scope.
+            unsafe {
+                copy_nonoverlapping(sg.as_ptr(), mapping as *mut u8, copy_sg_size as usize);
+            }
+
+            mapping = mapping.wrapping_offset(copy_sg_size as isize);
+            copy_size -= copy_sg_size;
+            if copy_size == 0 {
+                break;
+            }
+        }
+
+        // safe because the gbm_bo is assumed to be valid and the map_data is the same one given by
+        // gbm_bo_map.
+        unsafe {
+            gbm_bo_unmap(self.0, map_data);
+        }
+
+        Ok(())
+    }
+}
+
+impl Drop for Buffer {
+    fn drop(&mut self) {
+        // This is always safe to call with a valid gbm_bo pointer.
+        unsafe { gbm_bo_destroy(self.0) }
+    }
+}
+
+impl AsRawFd for Buffer {
+    fn as_raw_fd(&self) -> RawFd {
+        // This is always safe to call with a valid gbm_bo pointer.
+        unsafe { gbm_bo_get_fd(self.0) }
+    }
+}
+
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::fmt::Write;
+    use data_model::VolatileMemory;
+
+    #[test]
+    fn format_debug() {
+        let f = Format::new(b'X', b'R', b'2', b'4');
+        let mut buf = String::new();
+        write!(&mut buf, "{:?}", f).unwrap();
+        assert_eq!(buf, "fourcc(XR24)");
+
+        let f = Format::new(0, 1, 2, 16);
+        let mut buf = String::new();
+        write!(&mut buf, "{:?}", f).unwrap();
+        assert_eq!(buf, "fourcc(0x00010210)");
+    }
+
+    #[test]
+    #[ignore] // no access to /dev/dri
+    fn open_device() {
+        let drm_card = File::open("/dev/dri/card0").expect("failed to open card");
+        Device::new(drm_card).expect("failed to create device with card");
+    }
+
+    #[test]
+    #[ignore] // no access to /dev/dri
+    fn create_buffer() {
+        let drm_card = File::open("/dev/dri/card0").expect("failed to open card");
+        let device = Device::new(drm_card).expect("failed to create device with card");
+        let bo = device
+            .create_buffer(1024,
+                           512,
+                           Format::new(b'X', b'R', b'2', b'4'),
+                           Flags::empty().use_scanout(true))
+            .expect("failed to create buffer");
+
+        assert_eq!(bo.width(), 1024);
+        assert_eq!(bo.height(), 512);
+        assert_eq!(bo.format(), Format::new(b'X', b'R', b'2', b'4'));
+        assert_eq!(bo.num_planes(), 1);
+    }
+
+    #[test]
+    #[ignore] // no access to /dev/dri
+    fn export_buffer() {
+        let drm_card = File::open("/dev/dri/card0").expect("failed to open card");
+        let device = Device::new(drm_card).expect("failed to create device with card");
+        let bo = device
+            .create_buffer(1024,
+                           1024,
+                           Format::new(b'X', b'R', b'2', b'4'),
+                           Flags::empty().use_scanout(true))
+            .expect("failed to create buffer");
+        bo.export_plane_fd(0).expect("failed to export plane");
+    }
+
+
+    #[test]
+    #[ignore] // no access to /dev/dri
+    fn buffer_transfer() {
+        let drm_card = File::open("/dev/dri/card0").expect("failed to open card");
+        let device = Device::new(drm_card).expect("failed to create device with card");
+        let bo = device
+            .create_buffer(1024,
+                           1024,
+                           Format::new(b'X', b'R', b'2', b'4'),
+                           Flags::empty().use_scanout(true).use_linear(true))
+            .expect("failed to create buffer");
+        let mut dst: Vec<u8> = Vec::new();
+        dst.resize((bo.stride() * bo.height()) as usize, 0x4A);
+        let dst_len = dst.len() as u64;
+        bo.write_from_slice(0, 0, 1024, 1024, 0, dst.as_slice())
+            .expect("failed to read bo");
+        bo.read_to_volatile(0,
+                              0,
+                              1024,
+                              1024,
+                              0,
+                              dst.as_mut_slice().get_slice(0, dst_len).unwrap())
+            .expect("failed to read bo");
+        assert!(dst.iter().all(|&x| x == 0x4A));
+    }
+}