Use scatter-gather list for virtio framebuffer.

The current frame allocator limits requests to powers of two, between 4
KiB and 4 MiB. As such, a 8-bit color 1920x1080 framebuffer needs at
least two allocations.
This commit is contained in:
4lDO2
2024-06-14 08:18:45 +02:00
parent 8f98e28e97
commit 7b2f4dda90
4 changed files with 134 additions and 46 deletions
+9 -5
View File
@@ -20,14 +20,18 @@ const DMA_MEMTY: MemoryType = {
}
};
pub(crate) fn phys_contiguous_fd() -> Result<Fd> {
Fd::open(
&format!("memory:zeroed@{DMA_MEMTY}?phys_contiguous"),
flag::O_CLOEXEC,
0,
)
}
fn alloc_and_map(length: usize) -> Result<(usize, *mut ())> {
assert_eq!(length % PAGE_SIZE, 0);
unsafe {
let fd = Fd::open(
&format!("memory:zeroed@{DMA_MEMTY}?phys_contiguous"),
flag::O_CLOEXEC,
0,
)?;
let fd = phys_contiguous_fd()?;
let virt = libredox::call::mmap(MmapArgs {
fd: fd.raw(),
offset: 0, // ignored
+1
View File
@@ -6,6 +6,7 @@ use libredox::flag::{self, O_CLOEXEC, O_RDONLY, O_RDWR, O_WRONLY};
use syscall::PAGE_SIZE;
pub mod dma;
pub mod sgl;
#[derive(Clone, Copy, Debug)]
pub enum MemoryType {
+89
View File
@@ -0,0 +1,89 @@
use std::num::NonZeroUsize;
use libredox::call::MmapArgs;
use libredox::errno::EINVAL;
use libredox::error::{Error, Result};
use libredox::flag::{MAP_PRIVATE, PROT_READ, PROT_WRITE};
use syscall::{MAP_FIXED, PAGE_SIZE};
use crate::dma::phys_contiguous_fd;
#[derive(Debug)]
pub struct Sgl {
virt: *mut u8,
unaligned_length: NonZeroUsize,
chunks: Vec<Chunk>,
}
#[derive(Debug)]
pub struct Chunk {
pub offset: usize,
pub phys: usize,
pub virt: *mut u8,
pub length: usize,
}
impl Sgl {
pub fn new(unaligned_length: usize) -> Result<Self> {
let unaligned_length = NonZeroUsize::new(unaligned_length).ok_or(Error::new(EINVAL))?;
unsafe {
let virt = libredox::call::mmap(MmapArgs {
flags: MAP_PRIVATE,
prot: PROT_READ | PROT_WRITE,
length: unaligned_length.get(),
offset: 0,
fd: !0,
addr: core::ptr::null_mut(),
})?.cast::<u8>();
let mut this = Self {
virt,
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).min(MAX_ALLOC_SIZE).next_power_of_two();
libredox::call::mmap(MmapArgs {
addr: virt.add(offset).cast(),
flags: MAP_PRIVATE | (MAP_FIXED.bits() as u32),
prot: PROT_READ | PROT_WRITE,
length: chunk_length,
fd: phys_contiguous_fd.raw(),
offset: 0,
})?;
let phys = syscall::virttophys(virt as usize + offset)?;
this.chunks.push(Chunk { offset, phys, length: (unaligned_length.get() - offset).min(chunk_length), virt: virt.add(offset) });
offset += chunk_length;
}
Ok(this)
}
}
pub fn chunks(&self) -> &[Chunk] {
&self.chunks
}
pub fn as_ptr(&self) -> *mut u8 {
self.virt
}
pub fn len(&self) -> usize {
self.unaligned_length.get()
}
}
impl Drop for Sgl {
fn drop(&mut self) {
unsafe {
let _ = libredox::call::munmap(self.virt.cast(), self.unaligned_length.get());
}
}
}
+35 -41
View File
@@ -1,12 +1,13 @@
use std::cell::OnceCell;
use std::collections::BTreeMap;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
use std::sync::Arc;
use inputd::Damage;
use common::dma::Dma;
use common::{dma::Dma, sgl};
use syscall::{Error as SysError, SchemeMut, EAGAIN, EINVAL, MapFlags, PAGE_SIZE, KSMSG_MMAP, KSMSG_MMAP_PREP, KSMSG_MSYNC, KSMSG_MUNMAP};
use syscall::{Error as SysError, MapFlags, SchemeMut, EAGAIN, EINVAL, PAGE_SIZE};
use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags};
use virtio_core::transport::{Error, Queue, Transport};
@@ -32,9 +33,7 @@ pub struct Display<'a> {
cursor_queue: Arc<Queue<'a>>,
transport: Arc<dyn Transport>,
// TODO(andypython): Remove the need for the spin crate after the `once_cell`
// API is stabilized.
mapped: spin::Once<usize>,
mapped: OnceCell<sgl::Sgl>,
width: u32,
height: u32,
@@ -56,7 +55,7 @@ impl<'a> Display<'a> {
control_queue,
cursor_queue,
mapped: spin::Once::new(),
mapped: OnceCell::new(),
width: 1920,
height: 1080,
@@ -111,46 +110,41 @@ impl<'a> Display<'a> {
Ok(())
}
async fn remap_screen(&self) -> Result<usize, Error> {
// TODO: Is this a no-op?
async fn remap_screen(&self) -> Result<*mut u8, Error> {
let bpp = 32;
let fb_size = (self.width as usize * self.height as usize * bpp / 8)
.next_multiple_of(syscall::PAGE_SIZE);
let mapped = *self.mapped.get().unwrap();
let address = (unsafe { syscall::virttophys(mapped) }).map_err(libredox::error::Error::from)?;
let fb_size = self.width as usize * self.height as usize * bpp / 8;
self.map_screen_with(0, address, fb_size, mapped).await
let mapped = self.mapped.get().unwrap();
self.map_screen_with(0, fb_size, mapped.as_ptr(), mapped.chunks()).await
}
async fn map_screen(&self, offset: usize) -> Result<usize, Error> {
async fn map_screen(&self, offset: usize) -> Result<*mut u8, Error> {
if let Some(mapped) = self.mapped.get() {
return Ok(mapped + offset);
return Ok(mapped.as_ptr().wrapping_add(offset));
}
let bpp = 32;
let fb_size = (self.width as usize * self.height as usize * bpp / 8)
.next_multiple_of(syscall::PAGE_SIZE);
let mut mapped = unsafe { Dma::zeroed_slice(fb_size)?.assume_init() };
let fb_size = self.width as usize * self.height as usize * bpp / 8;
let mapped = sgl::Sgl::new(fb_size)?;
unsafe {
core::ptr::write_bytes(mapped.as_mut_ptr() as *mut u8, 255, fb_size);
core::ptr::write_bytes(mapped.as_ptr() as *mut u8, 255, fb_size);
}
let _ = self.mapped.set(mapped);
let mapped = self.mapped.get().unwrap();
let virt = mapped.as_mut_ptr() as usize;
let phys = mapped.physical();
core::mem::forget(mapped);
// TODO: Keep Dma
self.map_screen_with(offset, phys, fb_size, virt).await
self.map_screen_with(offset, fb_size, mapped.as_ptr(), mapped.chunks()).await
}
async fn map_screen_with(
&self,
offset: usize,
address: usize,
size: usize,
mapped: usize,
) -> Result<usize, Error> {
_size: usize,
virt: *mut u8,
chunks: &[sgl::Chunk],
) -> Result<*mut u8, Error> {
// Create a host resource using `VIRTIO_GPU_CMD_RESOURCE_CREATE_2D`.
let mut request = Dma::new(ResourceCreate2d::default())?;
@@ -163,20 +157,21 @@ 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`.
//
// TODO(andypython): Scatter lists are supported, so the framebuffer doesnt need to be
// contignous in guest physical memory.
let entry = Dma::new(MemEntry {
address: address as u64,
length: size as u32,
padding: 0,
})?;
let attach_request = Dma::new(AttachBacking::new(self.resource_id, 1))?;
let mut mem_entries = unsafe { Dma::zeroed_slice(chunks.len())?.assume_init() };
for (entry, chunk) in mem_entries.iter_mut().zip(chunks.iter()) {
*entry = MemEntry {
address: chunk.phys as u64,
length: chunk.length.next_multiple_of(PAGE_SIZE) as u32,
padding: 0,
};
}
let attach_request = Dma::new(AttachBacking::new(self.resource_id, mem_entries.len() as u32))?;
let header = Dma::new(ControlHeader::default())?;
let command = ChainBuilder::new()
.chain(Buffer::new(&attach_request))
.chain(Buffer::new(&entry))
.chain(Buffer::new_unsized(&mem_entries))
.chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY))
.build();
@@ -192,9 +187,8 @@ impl<'a> Display<'a> {
assert_eq!(header.ty.get(), CommandTy::RespOkNodata);
self.flush(None).await?;
self.mapped.call_once(|| mapped);
Ok(mapped + offset)
Ok(virt.wrapping_add(offset))
}
/// If `damage` is `None`, the entire screen is flushed.
@@ -473,7 +467,7 @@ impl<'a> SchemeMut for Scheme<'a> {
log::info!("KSMSG MMAP {} {:?} {} {}", id, flags, offset, size);
match self.handles.get(&id).ok_or(SysError::new(EINVAL))? {
Handle::Vt { display, .. } => {
Ok(futures::executor::block_on(display.map_screen(offset as usize)).unwrap())
Ok(futures::executor::block_on(display.map_screen(offset as usize)).unwrap() as usize)
}
_ => unreachable!(),
}