use std::fs::File;
use std::io;
use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
use std::os::unix::fs::MetadataExt;
use drm::buffer::{DrmFourcc, DrmModifier, Handle as BufferHandle, PlanarBuffer};
use drm::control::{Device as ControlDevice, FbCmd2Flags, framebuffer};
pub use gbm::BufferObjectFlags;
use gbm::{AsRaw, BufferObject, Device as GbmDevice};
use crate::kms::Card;
struct DupFd(OwnedFd);
impl AsFd for DupFd {
fn as_fd(&self) -> BorrowedFd<'_> {
self.0.as_fd()
}
}
pub struct Allocator {
dev: GbmDevice<DupFd>,
}
impl Allocator {
pub fn new(card: &Card) -> io::Result<Allocator> {
let fd = DupFd(card.as_fd().try_clone_to_owned()?);
Ok(Allocator {
dev: GbmDevice::new(fd)?,
})
}
#[allow(dead_code)] pub fn backend(&self) -> &str {
self.dev.backend_name()
}
pub fn device_ptr(&self) -> *mut std::ffi::c_void {
self.dev.as_raw() as *mut std::ffi::c_void
}
pub fn alloc(
&self,
card: &Card,
width: u32,
height: u32,
format: DrmFourcc,
flags: BufferObjectFlags,
) -> io::Result<Scanout> {
let bo = self
.dev
.create_buffer_object::<()>(width, height, format, flags)?;
self.register(card, bo, width, height)
}
pub fn alloc_with_modifiers(
&self,
card: &Card,
width: u32,
height: u32,
format: DrmFourcc,
modifiers: &[u64],
) -> io::Result<Scanout> {
if modifiers.is_empty() || modifiers.contains(&u64::from(DrmModifier::Invalid)) {
return Err(io::Error::other("explicit GBM allocation requires valid modifiers"));
}
let bo = self.dev.create_buffer_object_with_modifiers2::<()>(
width, height, format,
modifiers.iter().copied().map(DrmModifier::from),
BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING,
)?;
let modifier = explicit_modifier(bo.modifier())
.ok_or_else(|| io::Error::other("GBM returned an implicit layout for an explicit allocation"))?;
if !modifiers.contains(&modifier) {
return Err(io::Error::other(format!("GBM returned unrequested modifier {modifier:#x}")));
}
self.register(card, bo, width, height)
}
fn register(
&self, card: &Card, bo: BufferObject<()>, width: u32, height: u32,
) -> io::Result<Scanout> {
validate_plane_count(bo.plane_count() as usize)?;
let fb = card
.add_planar_framebuffer(&BoFb(&bo), fb_flags(&bo))
.map_err(io::Error::other)?;
Ok(Scanout {
bo,
fb,
width,
height,
})
}
}
pub struct Scanout {
pub bo: BufferObject<()>,
pub fb: framebuffer::Handle,
pub width: u32,
pub height: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct PlaneLayout {
pub offset: u32,
pub pitch: u32,
}
#[derive(Clone, Debug)]
pub(crate) struct ScanoutLayout {
pub width: u32,
pub height: u32,
pub fourcc: u32,
pub modifier: Option<u64>,
pub planes: Vec<PlaneLayout>,
}
pub(crate) struct ExportedScanout {
pub layout: ScanoutLayout,
pub planes: Vec<File>,
}
impl ExportedScanout {
pub fn shares_backing(&self) -> io::Result<bool> {
let identities = self
.planes
.iter()
.map(|fd| {
let m = fd.metadata()?;
Ok((m.dev(), m.ino()))
})
.collect::<io::Result<Vec<_>>>()?;
Ok(same_backing(&identities))
}
}
fn same_backing(identities: &[(u64, u64)]) -> bool {
!identities.is_empty() && identities.iter().all(|id| id == &identities[0])
}
pub(crate) fn validate_plane_count(count: usize) -> io::Result<()> {
if !(1..=4).contains(&count) {
return Err(io::Error::other(format!(
"gbm: unsupported memory plane count {count} (expected 1..=4)"
)));
}
Ok(())
}
fn explicit_modifier(modifier: DrmModifier) -> Option<u64> {
match modifier {
DrmModifier::Invalid => None,
modifier => Some(modifier.into()),
}
}
impl Scanout {
pub(crate) fn export(&self) -> io::Result<ExportedScanout> {
let count = self.bo.plane_count() as usize;
validate_plane_count(count)?;
let mut planes = Vec::with_capacity(count);
let mut layouts = Vec::with_capacity(count);
for i in 0..count as i32 {
let pitch = self.bo.stride_for_plane(i);
if pitch == 0 {
return Err(io::Error::other(format!("gbm: zero pitch for memory plane {i}")));
}
layouts.push(PlaneLayout {
offset: self.bo.offset(i),
pitch,
});
planes.push(File::from(
self.bo.fd_for_plane(i).map_err(|_| {
io::Error::other(format!("gbm: export dmabuf memory plane {i}"))
})?,
));
}
Ok(ExportedScanout {
layout: ScanoutLayout {
width: self.width,
height: self.height,
fourcc: self.fourcc(),
modifier: explicit_modifier(self.bo.modifier()),
planes: layouts,
},
planes,
})
}
pub fn stride(&self) -> u32 {
self.bo.stride()
}
pub fn offset(&self) -> u32 {
self.bo.offset(0)
}
pub fn modifier(&self) -> u64 {
u64::from(self.bo.modifier())
}
pub fn fourcc(&self) -> u32 {
self.bo.format() as u32
}
pub fn fill(&mut self, mut f: impl FnMut(&mut [u8], usize)) -> io::Result<()> {
let (w, h) = (self.width, self.height);
self.bo
.map_mut(0, 0, w, h, |m| {
let stride = m.stride() as usize;
f(m.buffer_mut(), stride);
})
.map_err(io::Error::other)?;
Ok(())
}
}
struct BoFb<'a>(&'a BufferObject<()>);
impl PlanarBuffer for BoFb<'_> {
fn size(&self) -> (u32, u32) {
(self.0.width(), self.0.height())
}
fn format(&self) -> DrmFourcc {
self.0.format()
}
fn modifier(&self) -> Option<DrmModifier> {
match self.0.modifier() {
DrmModifier::Invalid => None,
m => Some(m),
}
}
fn pitches(&self) -> [u32; 4] {
let mut p = [0; 4];
for i in 0..self.0.plane_count() as i32 {
p[i as usize] = self.0.stride_for_plane(i);
}
p
}
fn offsets(&self) -> [u32; 4] {
let mut o = [0; 4];
for i in 0..self.0.plane_count() as i32 {
o[i as usize] = self.0.offset(i);
}
o
}
fn handles(&self) -> [Option<BufferHandle>; 4] {
let mut h = [None; 4];
for i in 0..self.0.plane_count() as i32 {
let raw = unsafe { self.0.handle_for_plane(i).u32_ };
h[i as usize] = drm::control::from_u32(raw);
}
h
}
}
fn fb_flags(bo: &BufferObject<()>) -> FbCmd2Flags {
match bo.modifier() {
DrmModifier::Invalid => FbCmd2Flags::empty(),
_ => FbCmd2Flags::MODIFIERS,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn implicit_modifier_is_distinct_from_linear() {
assert_eq!(explicit_modifier(DrmModifier::Invalid), None);
assert_eq!(explicit_modifier(DrmModifier::Linear), Some(0));
}
#[test]
fn memory_plane_count_is_bounded_by_drm_arrays() {
for count in 1..=4 {
assert!(validate_plane_count(count).is_ok());
}
for count in [0, 5, usize::MAX] {
assert!(validate_plane_count(count).is_err());
}
}
#[test]
fn single_allocation_requires_matching_device_and_inode() {
assert!(same_backing(&[(1, 10), (1, 10), (1, 10)]));
assert!(!same_backing(&[(1, 10), (1, 11)]));
assert!(!same_backing(&[(1, 10), (2, 10)]));
assert!(!same_backing(&[]));
}
}