blob: a7c12ae56292083469c91f16222543b3bd6d289c [file] [log] [blame]
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! minigbm: implements swapchain allocation using ChromeOS's minigbm library.
//!
//! External code found at <https://chromium.googlesource.com/chromiumos/platform/minigbm>.
#![cfg(feature = "minigbm")]
use std::fs::File;
use std::io::Error;
use std::io::Seek;
use std::io::SeekFrom;
use std::os::fd::FromRawFd;
use std::sync::Arc;
use crate::rutabaga_gralloc::formats::DrmFormat;
use crate::rutabaga_gralloc::gralloc::Gralloc;
use crate::rutabaga_gralloc::gralloc::ImageAllocationInfo;
use crate::rutabaga_gralloc::gralloc::ImageMemoryRequirements;
use crate::rutabaga_gralloc::minigbm_bindings::*;
use crate::rutabaga_os::FromRawDescriptor;
use crate::rutabaga_utils::*;
struct MinigbmDeviceInner {
_fd: File,
gbm: *mut gbm_device,
}
// SAFETY:
// Safe because minigbm handles synchronization internally.
unsafe impl Send for MinigbmDeviceInner {}
// SAFETY:
// Safe because minigbm handles synchronization internally.
unsafe impl Sync for MinigbmDeviceInner {}
impl Drop for MinigbmDeviceInner {
fn drop(&mut self) {
// SAFETY:
// Safe because MinigbmDeviceInner is only constructed with a valid minigbm_device.
unsafe {
gbm_device_destroy(self.gbm);
}
}
}
/// A device capable of allocating `MinigbmBuffer`.
#[derive(Clone)]
pub struct MinigbmDevice {
minigbm_device: Arc<MinigbmDeviceInner>,
last_buffer: Option<Arc<MinigbmBuffer>>,
}
impl MinigbmDevice {
/// Returns a new `MinigbmDevice` if there is a rendernode in `/dev/dri/` that is accepted by
/// the minigbm library.
pub fn init() -> RutabagaResult<Box<dyn Gralloc>> {
let descriptor: File;
let gbm: *mut gbm_device;
// SAFETY:
// Safe because minigbm_create_default_device is safe to call with an unused fd,
// and fd is guaranteed to be overwritten with a valid descriptor when a non-null
// pointer is returned.
unsafe {
let mut fd = -1;
gbm = minigbm_create_default_device(&mut fd);
if gbm.is_null() {
return Err(RutabagaError::IoError(Error::last_os_error()));
}
descriptor = File::from_raw_fd(fd);
}
Ok(Box::new(MinigbmDevice {
minigbm_device: Arc::new(MinigbmDeviceInner {
_fd: descriptor,
gbm,
}),
last_buffer: None,
}))
}
}
impl Gralloc for MinigbmDevice {
fn supports_external_gpu_memory(&self) -> bool {
true
}
fn supports_dmabuf(&self) -> bool {
true
}
fn get_image_memory_requirements(
&mut self,
info: ImageAllocationInfo,
) -> RutabagaResult<ImageMemoryRequirements> {
// TODO(b/315870313): Add safety comment
#[allow(clippy::undocumented_unsafe_blocks)]
let bo = unsafe {
gbm_bo_create(
self.minigbm_device.gbm,
info.width,
info.height,
info.drm_format.0,
info.flags.0,
)
};
if bo.is_null() {
return Err(RutabagaError::IoError(Error::last_os_error()));
}
let mut reqs: ImageMemoryRequirements = Default::default();
let gbm_buffer = MinigbmBuffer(bo, self.clone());
if gbm_buffer.cached() {
reqs.map_info = RUTABAGA_MAP_CACHE_CACHED;
} else {
reqs.map_info = RUTABAGA_MAP_CACHE_WC;
}
reqs.modifier = gbm_buffer.format_modifier();
for plane in 0..gbm_buffer.num_planes() {
reqs.strides[plane] = gbm_buffer.plane_stride(plane);
reqs.offsets[plane] = gbm_buffer.plane_offset(plane);
}
let mut fd = gbm_buffer.export()?;
let size = fd.seek(SeekFrom::End(0))?;
// minigbm does have the ability to query image requirements without allocating memory
// via the TEST_ALLOC flag. However, support has only been added in i915. Until this
// flag is supported everywhere, do the actual allocation here and stash it away.
if self.last_buffer.is_some() {
return Err(RutabagaError::AlreadyInUse);
}
self.last_buffer = Some(Arc::new(gbm_buffer));
reqs.info = info;
reqs.size = size;
Ok(reqs)
}
fn allocate_memory(&mut self, reqs: ImageMemoryRequirements) -> RutabagaResult<RutabagaHandle> {
let last_buffer = self.last_buffer.take();
if let Some(gbm_buffer) = last_buffer {
if gbm_buffer.width() != reqs.info.width
|| gbm_buffer.height() != reqs.info.height
|| gbm_buffer.format() != reqs.info.drm_format
{
return Err(RutabagaError::InvalidGrallocDimensions);
}
let dmabuf = gbm_buffer.export()?.into();
return Ok(RutabagaHandle {
os_handle: dmabuf,
handle_type: RUTABAGA_MEM_HANDLE_TYPE_DMABUF,
});
}
// TODO(b/315870313): Add safety comment
#[allow(clippy::undocumented_unsafe_blocks)]
let bo = unsafe {
gbm_bo_create(
self.minigbm_device.gbm,
reqs.info.width,
reqs.info.height,
reqs.info.drm_format.0,
reqs.info.flags.0,
)
};
if bo.is_null() {
return Err(RutabagaError::IoError(Error::last_os_error()));
}
let gbm_buffer = MinigbmBuffer(bo, self.clone());
let dmabuf = gbm_buffer.export()?.into();
Ok(RutabagaHandle {
os_handle: dmabuf,
handle_type: RUTABAGA_MEM_HANDLE_TYPE_DMABUF,
})
}
}
/// An allocation from a `MinigbmDevice`.
pub struct MinigbmBuffer(*mut gbm_bo, MinigbmDevice);
// SAFETY:
// Safe because minigbm handles synchronization internally.
unsafe impl Send for MinigbmBuffer {}
// SAFETY:
// Safe because minigbm handles synchronization internally.
unsafe impl Sync for MinigbmBuffer {}
impl MinigbmBuffer {
/// Width in pixels.
pub fn width(&self) -> u32 {
// SAFETY:
// 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 {
// SAFETY:
// This is always safe to call with a valid gbm_bo pointer.
unsafe { gbm_bo_get_height(self.0) }
}
/// `DrmFormat` of the buffer.
pub fn format(&self) -> DrmFormat {
// SAFETY:
// This is always safe to call with a valid gbm_bo pointer.
unsafe { DrmFormat(gbm_bo_get_format(self.0)) }
}
/// DrmFormat modifier flags for the buffer.
pub fn format_modifier(&self) -> u64 {
// SAFETY:
// This is always safe to call with a valid gbm_bo pointer.
unsafe { gbm_bo_get_modifier(self.0) }
}
/// Number of planes present in this buffer.
pub fn num_planes(&self) -> usize {
// SAFETY:
// This is always safe to call with a valid gbm_bo pointer.
unsafe { gbm_bo_get_plane_count(self.0) as usize }
}
/// Offset in bytes for the given plane.
pub fn plane_offset(&self, plane: usize) -> u32 {
// SAFETY:
// This is always safe to call with a valid gbm_bo pointer.
unsafe { gbm_bo_get_offset(self.0, plane) }
}
/// Length in bytes of one row for the given plane.
pub fn plane_stride(&self, plane: usize) -> u32 {
// SAFETY:
// This is always safe to call with a valid gbm_bo pointer.
unsafe { gbm_bo_get_stride_for_plane(self.0, plane) }
}
/// Should buffer use cached mapping to guest
pub fn cached(&self) -> bool {
// SAFETY:
// This is always safe to call with a valid gbm_bo pointer.
let mode = unsafe { gbm_bo_get_map_info(self.0) };
mode == gbm_bo_map_cache_mode::GBM_BO_MAP_CACHE_CACHED
}
/// Exports a new dmabuf/prime file descriptor.
pub fn export(&self) -> RutabagaResult<File> {
// SAFETY:
// This is always safe to call with a valid gbm_bo pointer.
match unsafe { gbm_bo_get_fd(self.0) } {
fd if fd >= 0 => {
// SAFETY: fd is expected to be valid.
let dmabuf = unsafe { File::from_raw_descriptor(fd) };
Ok(dmabuf)
}
ret => Err(RutabagaError::ComponentError(ret)),
}
}
}
impl Drop for MinigbmBuffer {
fn drop(&mut self) {
// SAFETY:
// This is always safe to call with a valid gbm_bo pointer.
unsafe { gbm_bo_destroy(self.0) }
}
}