Merge branch 'virtio_gpud_refactor' into 'master'

Various virtio-gpud improvements

See merge request redox-os/drivers!222
This commit is contained in:
Jeremy Soller
2024-12-27 01:32:28 +00:00
4 changed files with 202 additions and 210 deletions
+18 -9
View File
@@ -15,6 +15,8 @@ use crate::dma::phys_contiguous_fd;
pub struct Sgl {
/// A raw pointer to the SGL in virtual memory
virt: *mut u8,
/// The length of the allocated memory, guaranteed to be a multiple of [PAGE_SIZE].
aligned_length: usize,
/// The length of the allocated memory. This value is NOT guaranteed to be a multiple of [PAGE_SIZE]
unaligned_length: NonZeroUsize,
/// The vector of chunks tracked by this [Sgl] object. This is the sparsely-populated vector in the SGL algorithm.
@@ -39,15 +41,20 @@ impl Sgl {
///
/// # Arguments
///
/// 'unaligned_length: [usize]' - The length of the SGL, not aligned to the nearest page.
/// 'unaligned_length: [usize]' - The length of the SGL, not necessarily aligned to the nearest
/// page.
pub fn new(unaligned_length: usize) -> Result<Self> {
let unaligned_length = NonZeroUsize::new(unaligned_length).ok_or(Error::new(EINVAL))?;
// TODO: Both PAGE_SIZE and MAX_ALLOC_SIZE should be dynamic.
let aligned_length = unaligned_length.get().next_multiple_of(PAGE_SIZE);
const MAX_ALLOC_SIZE: usize = 1 << 22;
unsafe {
let virt = libredox::call::mmap(MmapArgs {
flags: MAP_PRIVATE,
prot: PROT_READ | PROT_WRITE,
length: unaligned_length.get(),
length: aligned_length,
offset: 0,
fd: !0,
@@ -57,21 +64,23 @@ impl Sgl {
let mut this = Self {
virt,
aligned_length,
unaligned_length,
chunks: Vec::new(),
};
let phys_contiguous_fd = phys_contiguous_fd()?;
// TODO: Both PAGE_SIZE and MAX_ALLOC_SIZE should be dynamic.
let aligned_length = unaligned_length.get().next_multiple_of(PAGE_SIZE);
const MAX_ALLOC_SIZE: usize = 1 << 22;
let mut offset = 0;
while offset < unaligned_length.get() {
let chunk_length = (aligned_length - offset)
while offset < aligned_length {
let preferred_chunk_length = (aligned_length - offset)
.min(MAX_ALLOC_SIZE)
.next_power_of_two();
let chunk_length = if preferred_chunk_length > aligned_length - offset {
preferred_chunk_length / 2
} else {
preferred_chunk_length
};
libredox::call::mmap(MmapArgs {
addr: virt.add(offset).cast(),
flags: MAP_PRIVATE | (MAP_FIXED.bits() as u32),
@@ -112,7 +121,7 @@ impl Sgl {
impl Drop for Sgl {
fn drop(&mut self) {
unsafe {
let _ = libredox::call::munmap(self.virt.cast(), self.unaligned_length.get());
let _ = libredox::call::munmap(self.virt.cast(), self.aligned_length);
}
}
}
+57 -56
View File
@@ -124,9 +124,13 @@ impl TextScreen {
buf: &[u8],
input: &mut VecDeque<u8>,
) -> Vec<Damage> {
self.console.state.w = map.width / 8;
self.console.state.h = map.height / 16;
self.console.state.bottom_margin = (map.height / 16).saturating_sub(1);
self.console.resize(map.width / 8, map.height / 16);
if self.console.state.x > self.console.state.w {
self.console.state.x = self.console.state.w;
}
if self.console.state.y > self.console.state.h {
self.console.state.y = self.console.state.h;
}
if self.console.state.cursor
&& self.console.state.x < self.console.state.w
@@ -138,68 +142,65 @@ impl TextScreen {
self.changed.insert(y);
}
{
let changed = &mut self.changed;
self.console.write(buf, |event| match event {
ransid::Event::Char {
x,
y,
c,
color,
bold,
..
} => {
Self::char(map, x * 8, y * 16, c, color.as_rgb(), bold, false);
changed.insert(y);
self.console.write(buf, |event| match event {
ransid::Event::Char {
x,
y,
c,
color,
bold,
..
} => {
Self::char(map, x * 8, y * 16, c, color.as_rgb(), bold, false);
self.changed.insert(y);
}
ransid::Event::Input { data } => input.extend(data),
ransid::Event::Rect { x, y, w, h, color } => {
Self::rect(map, x * 8, y * 16, w * 8, h * 16, color.as_rgb());
for y2 in y..y + h {
self.changed.insert(y2);
}
ransid::Event::Input { data } => input.extend(data),
ransid::Event::Rect { x, y, w, h, color } => {
Self::rect(map, x * 8, y * 16, w * 8, h * 16, color.as_rgb());
for y2 in y..y + h {
changed.insert(y2);
}
}
ransid::Event::ScreenBuffer { .. } => (),
ransid::Event::Move {
from_x,
from_y,
to_x,
to_y,
w,
h,
} => {
let width = map.width;
let pixels = unsafe { &mut *map.offscreen };
}
ransid::Event::ScreenBuffer { .. } => (),
ransid::Event::Move {
from_x,
from_y,
to_x,
to_y,
w,
h,
} => {
let width = map.width;
let pixels = unsafe { &mut *map.offscreen };
for raw_y in 0..h {
let y = if from_y > to_y { raw_y } else { h - raw_y - 1 };
for raw_y in 0..h {
let y = if from_y > to_y { raw_y } else { h - raw_y - 1 };
for pixel_y in 0..16 {
{
let off_from = ((from_y + y) * 16 + pixel_y) * width + from_x * 8;
let off_to = ((to_y + y) * 16 + pixel_y) * width + to_x * 8;
let len = w * 8;
for pixel_y in 0..16 {
{
let off_from = ((from_y + y) * 16 + pixel_y) * width + from_x * 8;
let off_to = ((to_y + y) * 16 + pixel_y) * width + to_x * 8;
let len = w * 8;
if off_from + len <= pixels.len() && off_to + len <= pixels.len() {
unsafe {
let data_ptr = pixels.as_mut_ptr() as *mut u32;
ptr::copy(
data_ptr.offset(off_from as isize),
data_ptr.offset(off_to as isize),
len,
);
}
if off_from + len <= pixels.len() && off_to + len <= pixels.len() {
unsafe {
let data_ptr = pixels.as_mut_ptr() as *mut u32;
ptr::copy(
data_ptr.offset(off_from as isize),
data_ptr.offset(off_to as isize),
len,
);
}
}
}
changed.insert(to_y + y);
}
self.changed.insert(to_y + y);
}
ransid::Event::Resize { .. } => (),
ransid::Event::Title { .. } => (),
});
}
}
ransid::Event::Resize { .. } => (),
ransid::Event::Title { .. } => (),
});
if self.console.state.cursor
&& self.console.state.x < self.console.state.w
+13 -1
View File
@@ -61,11 +61,17 @@ impl Display {
let (mut new_display_file, width, height) = Self::open_display(&self.input_handle).unwrap();
eprintln!("fbcond: Opened new display");
eprintln!("fbcond: Opened new display with size {width}x{height}");
match display_fd_map(width, height, &mut new_display_file) {
Ok(offscreen) => {
unsafe {
display_fd_unmap(self.offscreen);
}
self.offscreen = offscreen;
self.width = width;
self.height = height;
self.display_file = new_display_file;
eprintln!("fbcond: Mapped new display");
@@ -107,7 +113,13 @@ impl Display {
pub fn resize(&mut self, width: usize, height: usize) {
match display_fd_map(width, height, &mut self.display_file) {
Ok(offscreen) => {
unsafe {
display_fd_unmap(self.offscreen);
}
self.offscreen = offscreen;
self.width = width;
self.height = height;
}
Err(err) => {
eprintln!("failed to resize display to {}x{}: {}", width, height, err);
+114 -144
View File
@@ -1,4 +1,3 @@
use std::cell::RefCell;
use std::collections::{BTreeMap, HashMap};
use std::sync::atomic::{AtomicUsize, Ordering};
@@ -8,7 +7,7 @@ use common::{dma::Dma, sgl};
use inputd::{Damage, VtEvent, VtEventKind};
use redox_scheme::Scheme;
use syscall::{Error as SysError, MapFlags, EINVAL, PAGE_SIZE};
use syscall::{Error as SysError, MapFlags, EBADF, EINVAL, PAGE_SIZE};
use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags};
use virtio_core::transport::{Error, Queue, Transport};
@@ -27,52 +26,18 @@ impl Into<GpuRect> for Damage {
}
}
pub struct Display<'a> {
control_queue: Arc<Queue<'a>>,
cursor_queue: Arc<Queue<'a>>,
transport: Arc<dyn Transport>,
active_vt: RefCell<usize>,
vts_map: RefCell<HashMap<usize, sgl::Sgl>>,
vts_res: RefCell<HashMap<usize, ResourceId>>,
width: u32,
height: u32,
id: usize,
struct GpuResource {
id: ResourceId,
sgl: sgl::Sgl,
}
impl<'a> Display<'a> {
pub fn new(
control_queue: Arc<Queue<'a>>,
cursor_queue: Arc<Queue<'a>>,
transport: Arc<dyn Transport>,
id: usize,
) -> Self {
Self {
control_queue,
cursor_queue,
active_vt: RefCell::new(0),
vts_map: RefCell::new(HashMap::new()),
vts_res: RefCell::new(HashMap::new()),
width: 1920,
height: 1080,
transport,
id,
}
}
async fn get_fpath(&self, buffer: &mut [u8]) -> Result<usize, Error> {
let path = format!("display.virtio-gpu:3.0/{}/{}", self.width, self.height);
// Copy the path into the target buffer.
buffer[..path.len()].copy_from_slice(path.as_bytes());
Ok(path.len())
}
#[derive(Debug, Copy, Clone)]
pub struct Display {
width: u32,
height: u32,
}
impl GpuScheme<'_> {
async fn send_request<T>(&self, request: Dma<T>) -> Result<Dma<ControlHeader>, Error> {
let header = Dma::new(ControlHeader::default())?;
let command = ChainBuilder::new()
@@ -91,41 +56,31 @@ impl<'a> Display<'a> {
Ok(())
}
async fn mmap_screen(&self, vt: usize, offset: usize) -> Result<*mut u8, Error> {
if let Some(sgl) = self.vts_map.borrow().get(&vt) {
return Ok(sgl.as_ptr().wrapping_add(offset));
async fn res_for_screen(
&mut self,
vt: usize,
screen: usize,
) -> Result<(ResourceId, *mut u8), Error> {
if let Some(res) = self.vts.entry(vt).or_default().get(&screen) {
return Ok((res.id, res.sgl.as_ptr()));
}
let bpp = 32;
let fb_size = self.width as usize * self.height as usize * bpp / 8;
let mapped = sgl::Sgl::new(fb_size)?;
let fb_size =
self.displays[screen].width as usize * self.displays[screen].height as usize * bpp / 8;
let sgl = sgl::Sgl::new(fb_size)?;
unsafe {
core::ptr::write_bytes(mapped.as_ptr() as *mut u8, 255, fb_size);
core::ptr::write_bytes(sgl.as_ptr() as *mut u8, 255, fb_size);
}
let mut mapped_vts = self.vts_map.borrow_mut();
let sgl = mapped_vts.entry(vt).or_insert(mapped);
Ok(sgl.as_ptr().wrapping_add(offset))
}
async fn create_res_for_screen(&self, vt: usize) -> Result<ResourceId, Error> {
if let Some(&res_id) = self.vts_res.borrow().get(&vt) {
return Ok(res_id);
}
self.mmap_screen(vt, 0).await?;
let vts_map = self.vts_map.borrow();
let mapped = &vts_map.get(&vt).unwrap();
let res_id = ResourceId::alloc();
// Create a host resource using `VIRTIO_GPU_CMD_RESOURCE_CREATE_2D`.
let mut request = Dma::new(ResourceCreate2d::default())?;
request.set_width(self.width);
request.set_height(self.height);
request.set_width(self.displays[screen].width);
request.set_height(self.displays[screen].height);
request.set_format(ResourceFormat::Bgrx);
request.set_resource_id(res_id);
@@ -135,8 +90,8 @@ impl<'a> Display<'a> {
// Use the allocated framebuffer from tthe guest ram, and attach it as backing
// storage to the resource just created, using `VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING`.
let mut mem_entries = unsafe { Dma::zeroed_slice(mapped.chunks().len())?.assume_init() };
for (entry, chunk) in mem_entries.iter_mut().zip(mapped.chunks().iter()) {
let mut mem_entries = unsafe { Dma::zeroed_slice(sgl.chunks().len())?.assume_init() };
for (entry, chunk) in mem_entries.iter_mut().zip(sgl.chunks().iter()) {
*entry = MemEntry {
address: chunk.phys as u64,
length: chunk.length.next_multiple_of(PAGE_SIZE) as u32,
@@ -155,43 +110,52 @@ impl<'a> Display<'a> {
self.control_queue.send(command).await;
assert_eq!(header.ty.get(), CommandTy::RespOkNodata);
let mut mapped_vts = self.vts_res.borrow_mut();
mapped_vts.insert(vt, res_id);
Ok(res_id)
let res = self
.vts
.entry(vt)
.or_default()
.entry(screen)
.or_insert(GpuResource { id: res_id, sgl });
Ok((res.id, res.sgl.as_ptr()))
}
async fn set_scanout(&self, vt: usize) -> Result<(), Error> {
let res_id = self.create_res_for_screen(vt).await?;
async fn set_scanout(&mut self, vt: usize, screen: usize) -> Result<(), Error> {
let (res_id, _) = self.res_for_screen(vt, screen).await?;
let scanout_request = Dma::new(SetScanout::new(
self.id as u32,
screen as u32,
res_id,
GpuRect::new(0, 0, self.width, self.height),
GpuRect::new(
0,
0,
self.displays[screen].width,
self.displays[screen].height,
),
))?;
let header = self.send_request(scanout_request).await?;
assert_eq!(header.ty.get(), CommandTy::RespOkNodata);
self.flush(vt, None).await?;
self.flush(vt, screen, None).await?;
Ok(())
}
/// If `damage` is `None`, the entire screen is flushed.
async fn flush(&self, vt: usize, damage: Option<&[Damage]>) -> Result<(), Error> {
let Some(&res_id) = self.vts_res.borrow().get(&vt) else {
// The resource is not yet created. Ignore the damage. We will write the entire backing
// storage to the resource once we create the resource, which is equivalent to damaging
// the entire resource.
return Ok(());
};
async fn flush(
&mut self,
vt: usize,
screen: usize,
damage: Option<&[Damage]>,
) -> Result<(), Error> {
let (res_id, _) = self.res_for_screen(vt, screen).await?;
let req = Dma::new(XferToHost2d::new(
res_id,
GpuRect {
x: 0,
y: 0,
width: self.width,
height: self.height,
width: self.displays[screen].width,
height: self.displays[screen].height,
},
0,
))?;
@@ -202,7 +166,12 @@ impl<'a> Display<'a> {
for damage in damage {
self.flush_resource(ResourceFlush::new(
res_id,
damage.clip(self.width as i32, self.height as i32).into(),
damage
.clip(
self.displays[screen].width as i32,
self.displays[screen].height as i32,
)
.into(),
))
.await?;
}
@@ -212,8 +181,8 @@ impl<'a> Display<'a> {
GpuRect {
x: 0,
y: 0,
width: self.width,
height: self.height,
width: self.displays[screen].width,
height: self.displays[screen].height,
},
))
.await?;
@@ -222,17 +191,24 @@ impl<'a> Display<'a> {
}
}
struct Handle<'a> {
display: Arc<Display<'a>>,
struct Handle {
screen: usize,
vt: usize,
}
pub struct GpuScheme<'a> {
handles: BTreeMap<usize /* file descriptor */, Handle<'a>>,
control_queue: Arc<Queue<'a>>,
cursor_queue: Arc<Queue<'a>>,
transport: Arc<dyn Transport>,
pub inputd_handle: inputd::DisplayHandle,
handles: BTreeMap<usize /* file descriptor */, Handle>,
/// Counter used for file descriptor allocation.
next_id: AtomicUsize,
displays: Vec<Arc<Display<'a>>>,
pub inputd_handle: inputd::DisplayHandle,
active_vt: usize,
vts: HashMap<usize, HashMap<usize, GpuResource>>,
displays: Vec<Display>,
}
impl<'a> GpuScheme<'a> {
@@ -242,50 +218,45 @@ impl<'a> GpuScheme<'a> {
cursor_queue: Arc<Queue<'a>>,
transport: Arc<dyn Transport>,
) -> Result<GpuScheme<'a>, Error> {
let displays = Self::probe(
control_queue.clone(),
cursor_queue.clone(),
transport.clone(),
config,
)
.await?;
let displays = Self::probe(control_queue.clone(), config).await?;
let inputd_handle = inputd::DisplayHandle::new("virtio-gpu").unwrap();
Ok(Self {
control_queue,
cursor_queue,
transport,
inputd_handle,
handles: BTreeMap::new(),
next_id: AtomicUsize::new(0),
active_vt: 0,
vts: HashMap::new(),
displays,
inputd_handle,
})
}
async fn probe(
control_queue: Arc<Queue<'a>>,
cursor_queue: Arc<Queue<'a>>,
transport: Arc<dyn Transport>,
config: &GpuConfig,
) -> Result<Vec<Arc<Display<'a>>>, Error> {
) -> Result<Vec<Display>, Error> {
let mut display_info = Self::get_display_info(control_queue.clone()).await?;
let displays = &mut display_info.display_info[..config.num_scanouts() as usize];
let mut result = vec![];
for (id, info) in displays.iter().enumerate() {
for info in displays.iter() {
log::info!(
"virtio-gpu: opening display ({}x{}px)",
info.rect().width,
info.rect().height
);
let display = Display::new(
control_queue.clone(),
cursor_queue.clone(),
transport.clone(),
id,
);
result.push(Arc::new(display));
result.push(Display {
width: info.rect().width,
height: info.rect().height,
});
}
Ok(result)
@@ -314,13 +285,13 @@ impl<'a> GpuScheme<'a> {
VtEventKind::Activate => {
log::info!("activate {}", vt_event.vt);
for display in &self.displays {
for id in 0..self.displays.len() {
log::warn!("virtio-gpu: activating");
futures::executor::block_on(display.set_scanout(vt_event.vt)).unwrap();
*display.active_vt.borrow_mut() = vt_event.vt;
futures::executor::block_on(self.set_scanout(vt_event.vt, id)).unwrap();
}
self.active_vt = vt_event.vt;
}
VtEventKind::Deactivate => {
@@ -349,46 +320,45 @@ impl<'a> Scheme for GpuScheme<'a> {
dbg!(vt, id);
let display = self.displays.get(id).ok_or(SysError::new(EINVAL))?;
if id >= self.displays.len() {
return Err(SysError::new(EINVAL));
};
let fd = self.next_id.fetch_add(1, Ordering::SeqCst);
self.handles.insert(
fd,
Handle {
display: display.clone(),
vt,
},
);
self.handles.insert(fd, Handle { screen: id, vt });
Ok(fd)
}
fn fpath(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result<usize> {
let handle = self.handles.get(&id).unwrap();
let bytes_copied = futures::executor::block_on(handle.display.get_fpath(buf)).unwrap();
Ok(bytes_copied)
let path = format!(
"display.virtio-gpu:3.0/{}/{}",
self.displays[handle.screen].width, self.displays[handle.screen].height
);
buf[..path.len()].copy_from_slice(path.as_bytes());
Ok(path.len())
}
fn fsync(&mut self, id: usize) -> syscall::Result<usize> {
let handle = self.handles.get(&id).ok_or(SysError::new(EINVAL))?;
if handle.vt != *handle.display.active_vt.borrow() {
let handle = self.handles.get(&id).ok_or(SysError::new(EBADF))?;
if handle.vt != self.active_vt {
// This is a protection against background VT's spamming us with flush requests. We will
// flush the resource on the next scanout anyway
return Ok(0);
}
futures::executor::block_on(handle.display.flush(handle.vt, None)).unwrap();
futures::executor::block_on(self.flush(handle.vt, handle.screen, None)).unwrap();
Ok(0)
}
fn read(
&mut self,
_id: usize,
id: usize,
_buf: &mut [u8],
_offset: u64,
_fcntl_flags: u32,
) -> syscall::Result<usize> {
// TODO: figure out how to get input lol
log::warn!("virtio_gpu::read is a stub!");
Ok(0)
let _handle = self.handles.get(&id).ok_or(SysError::new(EBADF))?;
Err(SysError::new(EINVAL))
}
fn write(
@@ -398,9 +368,9 @@ impl<'a> Scheme for GpuScheme<'a> {
_offset: u64,
_fcntl_flags: u32,
) -> syscall::Result<usize> {
let handle = self.handles.get(&id).ok_or(SysError::new(EINVAL))?;
let handle = self.handles.get(&id).ok_or(SysError::new(EBADF))?;
if handle.vt != *handle.display.active_vt.borrow() {
if handle.vt != self.active_vt {
// This is a protection against background VT's spamming us with flush requests. We will
// flush the resource on the next scanout anyway
return Ok(buf.len());
@@ -413,12 +383,13 @@ impl<'a> Scheme for GpuScheme<'a> {
)
};
futures::executor::block_on(handle.display.flush(handle.vt, Some(damage))).unwrap();
futures::executor::block_on(self.flush(handle.vt, handle.screen, Some(damage))).unwrap();
Ok(buf.len())
}
fn close(&mut self, _id: usize) -> syscall::Result<usize> {
fn close(&mut self, id: usize) -> syscall::Result<usize> {
self.handles.remove(&id).ok_or(SysError::new(EBADF))?;
Ok(0)
}
fn mmap_prep(
@@ -430,9 +401,8 @@ impl<'a> Scheme for GpuScheme<'a> {
) -> syscall::Result<usize> {
log::info!("KSMSG MMAP {} {:?} {} {}", id, flags, offset, size);
let handle = self.handles.get(&id).ok_or(SysError::new(EINVAL))?;
let ptr =
futures::executor::block_on(handle.display.mmap_screen(handle.vt, offset as usize))
.unwrap();
Ok(ptr as usize)
let (_, ptr) =
futures::executor::block_on(self.res_for_screen(handle.vt, handle.screen)).unwrap();
Ok(unsafe { ptr.offset(offset as isize) } as usize)
}
}