6694b5af9e
V2GraphicsHandle::from_file() unwrapped/asserted the DumbBuffer capability, but the display fd is O_NONBLOCK and the graphics driver often has not finished coming up at handoff time — so from_file failed with EAGAIN and the assert panicked fbcond. Since fbcond owns the console and serial mirror, its death froze the whole boot right after 'Performing handoff' with no login prompt. Return an error from graphics-ipc when the driver does not yet report DumbBuffer, and have fbcond log-and-return so the handoff is retried when the driver publishes its surface, instead of panicking. (Change found as uncommitted work in the fork; committing to preserve it and unblock the clean-fork build gate.)
138 lines
4.5 KiB
Rust
138 lines
4.5 KiB
Rust
use std::fs::File;
|
|
use std::os::fd::{AsFd, BorrowedFd};
|
|
use std::{io, mem, ptr};
|
|
|
|
use drm::buffer::Buffer;
|
|
use drm::control::connector::{self, State};
|
|
use drm::control::dumbbuffer::{DumbBuffer, DumbMapping};
|
|
use drm::control::Device as _;
|
|
use drm::{Device as _, DriverCapability};
|
|
|
|
/// A graphics handle using the v2 graphics API.
|
|
///
|
|
/// The v2 graphics API allows creating framebuffers on the fly, using them for page flipping and
|
|
/// handles all displays using a single fd. This is basically a subset of the Linux DRM interface
|
|
/// with a couple of custom ioctls in the place of the KMS ioctls that are missing.
|
|
pub struct V2GraphicsHandle {
|
|
file: File,
|
|
}
|
|
|
|
impl AsFd for V2GraphicsHandle {
|
|
fn as_fd(&self) -> BorrowedFd<'_> {
|
|
self.file.as_fd()
|
|
}
|
|
}
|
|
|
|
impl drm::Device for V2GraphicsHandle {}
|
|
impl drm::control::Device for V2GraphicsHandle {}
|
|
|
|
impl V2GraphicsHandle {
|
|
pub fn from_file(file: File) -> io::Result<Self> {
|
|
let handle = V2GraphicsHandle { file };
|
|
// Return an error rather than panicking when the driver does not (yet)
|
|
// report the DumbBuffer capability. During a display handoff the driver
|
|
// may still be coming up, and a panic here would take down the caller
|
|
// (e.g. fbcond, which owns the console) and freeze boot. Callers treat
|
|
// this as a transient and retry.
|
|
if handle.get_driver_capability(DriverCapability::DumbBuffer)? != 1 {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::Other,
|
|
"graphics driver does not report DumbBuffer capability yet",
|
|
));
|
|
}
|
|
Ok(handle)
|
|
}
|
|
|
|
pub fn first_display(&self) -> io::Result<connector::Handle> {
|
|
for &connector in self.resource_handles().unwrap().connectors() {
|
|
if self.get_connector(connector, true)?.state() == State::Connected {
|
|
return Ok(connector);
|
|
}
|
|
}
|
|
Err(io::Error::other("no connected display"))
|
|
}
|
|
}
|
|
|
|
pub struct CpuBackedBuffer {
|
|
buffer: DumbBuffer,
|
|
map: DumbMapping<'static>,
|
|
shadow: Option<Box<[u8]>>,
|
|
}
|
|
|
|
impl CpuBackedBuffer {
|
|
pub fn new(
|
|
display_handle: &V2GraphicsHandle,
|
|
size: (u32, u32),
|
|
format: drm::buffer::DrmFourcc,
|
|
bpp: u32,
|
|
) -> io::Result<CpuBackedBuffer> {
|
|
let mut buffer = display_handle.create_dumb_buffer(size, format, bpp)?;
|
|
|
|
let map = display_handle.map_dumb_buffer(&mut buffer)?;
|
|
let map = unsafe { mem::transmute::<DumbMapping<'_>, DumbMapping<'static>>(map) };
|
|
|
|
let shadow = if display_handle
|
|
.get_driver_capability(DriverCapability::DumbPreferShadow)
|
|
.unwrap_or(1)
|
|
== 0
|
|
{
|
|
None
|
|
} else {
|
|
Some(vec![0; map.len()].into_boxed_slice())
|
|
};
|
|
|
|
Ok(CpuBackedBuffer {
|
|
buffer,
|
|
map,
|
|
shadow,
|
|
})
|
|
}
|
|
|
|
pub fn buffer(&self) -> &DumbBuffer {
|
|
&self.buffer
|
|
}
|
|
|
|
pub fn has_shadow_buf(&self) -> bool {
|
|
self.shadow.is_some()
|
|
}
|
|
|
|
pub fn shadow_buf(&mut self) -> &mut [u8] {
|
|
self.shadow.as_deref_mut().unwrap_or(&mut *self.map)
|
|
}
|
|
|
|
pub fn sync_rect(&mut self, x: u32, y: u32, width: u32, height: u32) {
|
|
let Some(shadow) = &self.shadow else {
|
|
return; // No shadow buffer; all writes are already propagated to the GPU.
|
|
};
|
|
|
|
assert!(x.checked_add(width).unwrap() <= self.buffer.size().0);
|
|
assert!(y.checked_add(height).unwrap() <= self.buffer.size().1);
|
|
|
|
let start_x: usize = x.try_into().unwrap();
|
|
let start_y: usize = y.try_into().unwrap();
|
|
let w: usize = width.try_into().unwrap();
|
|
let h: usize = height.try_into().unwrap();
|
|
|
|
let offscreen_ptr = shadow.as_ptr().cast::<u32>();
|
|
let onscreen_ptr = self.map.as_mut_ptr().cast::<u32>();
|
|
|
|
for row in start_y..start_y + h {
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(
|
|
offscreen_ptr.add(row * self.buffer.pitch() as usize / 4 + start_x),
|
|
onscreen_ptr.add(row * self.buffer.pitch() as usize / 4 + start_x),
|
|
w,
|
|
);
|
|
}
|
|
}
|
|
|
|
// No need for a wbinvd to flush the write combining writes as they are
|
|
// already flushed on the next syscall anyway. And the user will need
|
|
// to do a DRM ioctl to actually present the changes on the display.
|
|
}
|
|
|
|
pub fn destroy(self, display_handle: &V2GraphicsHandle) -> io::Result<()> {
|
|
display_handle.destroy_dumb_buffer(self.buffer)
|
|
}
|
|
}
|