virtio-gpud: Handle multiple VT's

The code will become simpler once handoff is implemented such that
virtio-gpud no longer needs to support reset and reinitialization of the
virtio gpu device.
This commit is contained in:
bjorn3
2024-12-23 11:09:28 +01:00
parent 31deddd0d5
commit b0f682c95d
2 changed files with 120 additions and 106 deletions
+35 -36
View File
@@ -24,6 +24,7 @@
use std::cell::UnsafeCell;
use std::os::fd::AsRawFd;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use event::{user_data, EventQueue};
@@ -225,6 +226,18 @@ impl Default for GetDisplayInfo {
}
}
static RESOURCE_ALLOC: AtomicU32 = AtomicU32::new(1); // XXX: 0 is reserved for whatever that takes `resource_id`.
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct ResourceId(u32);
impl ResourceId {
fn alloc() -> Self {
ResourceId(RESOURCE_ALLOC.fetch_add(1, Ordering::SeqCst))
}
}
#[derive(Debug, Copy, Clone)]
#[repr(u32)]
pub enum ResourceFormat {
@@ -239,14 +252,16 @@ pub enum ResourceFormat {
pub struct ResourceCreate2d {
pub header: ControlHeader,
resource_id: VolatileCell<u32>,
// FIXME we can likely use regular loads and stores as the ring buffer should provide the
// necessary synchronization.
resource_id: VolatileCell<ResourceId>,
format: VolatileCell<ResourceFormat>,
width: VolatileCell<u32>,
height: VolatileCell<u32>,
}
impl ResourceCreate2d {
make_getter_setter!(resource_id: u32, format: ResourceFormat, width: u32, height: u32);
make_getter_setter!(resource_id: ResourceId, format: ResourceFormat, width: u32, height: u32);
}
impl Default for ResourceCreate2d {
@@ -257,7 +272,7 @@ impl Default for ResourceCreate2d {
..Default::default()
},
resource_id: VolatileCell::new(0),
resource_id: VolatileCell::new(ResourceId(0)),
format: VolatileCell::new(ResourceFormat::Unknown),
width: VolatileCell::new(0),
height: VolatileCell::new(0),
@@ -277,12 +292,12 @@ pub struct MemEntry {
#[repr(C)]
pub struct AttachBacking {
pub header: ControlHeader,
pub resource_id: u32,
pub resource_id: ResourceId,
pub num_entries: u32,
}
impl AttachBacking {
pub fn new(resource_id: u32, num_entries: u32) -> Self {
pub fn new(resource_id: ResourceId, num_entries: u32) -> Self {
Self {
header: ControlHeader::with_ty(CommandTy::ResourceAttachBacking),
resource_id,
@@ -295,12 +310,12 @@ impl AttachBacking {
#[repr(C)]
pub struct DetachBacking {
pub header: ControlHeader,
pub resource_id: u32,
pub resource_id: ResourceId,
pub padding: u32,
}
impl DetachBacking {
pub fn new(resource_id: u32) -> Self {
pub fn new(resource_id: ResourceId) -> Self {
Self {
header: ControlHeader::with_ty(CommandTy::ResourceDetachBacking),
resource_id,
@@ -314,12 +329,12 @@ impl DetachBacking {
pub struct ResourceFlush {
pub header: ControlHeader,
pub rect: GpuRect,
pub resource_id: u32,
pub resource_id: ResourceId,
pub padding: u32,
}
impl ResourceFlush {
pub fn new(resource_id: u32, rect: GpuRect) -> Self {
pub fn new(resource_id: ResourceId, rect: GpuRect) -> Self {
Self {
header: ControlHeader::with_ty(CommandTy::ResourceFlush),
rect,
@@ -333,12 +348,12 @@ impl ResourceFlush {
#[repr(C)]
pub struct ResourceUnref {
pub header: ControlHeader,
pub resource_id: u32,
pub resource_id: ResourceId,
pub padding: u32,
}
impl ResourceUnref {
pub fn new(resource_id: u32) -> Self {
pub fn new(resource_id: ResourceId) -> Self {
Self {
header: ControlHeader::with_ty(CommandTy::ResourceUnref),
resource_id,
@@ -353,11 +368,11 @@ pub struct SetScanout {
pub header: ControlHeader,
pub rect: GpuRect,
pub scanout_id: u32,
pub resource_id: u32,
pub resource_id: ResourceId,
}
impl SetScanout {
pub fn new(scanout_id: u32, resource_id: u32, rect: GpuRect) -> Self {
pub fn new(scanout_id: u32, resource_id: ResourceId, rect: GpuRect) -> Self {
Self {
header: ControlHeader::with_ty(CommandTy::SetScanout),
@@ -374,12 +389,12 @@ pub struct XferToHost2d {
pub header: ControlHeader,
pub rect: GpuRect,
pub offset: u64,
pub resource_id: u32,
pub resource_id: ResourceId,
pub padding: u32,
}
impl XferToHost2d {
pub fn new(resource_id: u32, rect: GpuRect) -> Self {
pub fn new(resource_id: ResourceId, rect: GpuRect) -> Self {
Self {
header: ControlHeader {
ty: VolatileCell::new(CommandTy::TransferToHost2d),
@@ -455,7 +470,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
}
let event_queue: EventQueue<Source> =
EventQueue::new().expect("vesad: failed to create event queue");
EventQueue::new().expect("virtio-gpud: failed to create event queue");
event_queue
.subscribe(
scheme.inputd_handle.inner().as_raw_fd() as usize,
@@ -477,14 +492,14 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
let all = [Source::Input, Source::Scheme];
for event in all
.into_iter()
.chain(event_queue.map(|e| e.expect("vesad: failed to get next event").user_data))
.chain(event_queue.map(|e| e.expect("virtio-gpud: failed to get next event").user_data))
{
match event {
Source::Input => {
while let Some(vt_event) = scheme
.inputd_handle
.read_vt_event()
.expect("vesad: failed to read display handle")
.expect("virtio-gpud: failed to read display handle")
{
scheme.handle_vt_event(vt_event);
}
@@ -508,7 +523,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
call_request.handle_scheme_mut(&mut scheme),
SignalBehavior::Restart,
)
.expect("vesad: failed to write display scheme");
.expect("virtio-gpud: failed to write display scheme");
}
RequestKind::Cancellation(_cancellation_request) => {
// FIXME handle this
@@ -522,23 +537,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
}
}
loop {
let Some(request) = socket.next_request(SignalBehavior::Restart)? else {
// Scheme likely got unmounted
return Ok(());
};
match request.kind() {
RequestKind::Call(call_request) => {
socket.write_response(
call_request.handle_scheme_mut(&mut scheme),
SignalBehavior::Restart,
)?;
}
RequestKind::Cancellation(_cancellation_request) => {}
RequestKind::MsyncMsg | RequestKind::MunmapMsg | RequestKind::MmapMsg => unreachable!(),
}
}
std::process::exit(0);
}
fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! {
+85 -70
View File
@@ -1,14 +1,14 @@
use std::cell::OnceCell;
use std::collections::BTreeMap;
use std::cell::RefCell;
use std::collections::{BTreeMap, HashMap};
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use common::{dma::Dma, sgl};
use inputd::{Damage, VtEvent, VtEventKind};
use redox_scheme::SchemeMut;
use syscall::{Error as SysError, MapFlags, EAGAIN, EINVAL, PAGE_SIZE};
use syscall::{Error as SysError, MapFlags, EINVAL, PAGE_SIZE};
use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags};
use virtio_core::transport::{Error, Queue, Transport};
@@ -16,8 +16,6 @@ use virtio_core::utils::VolatileCell;
use crate::*;
static RESOURCE_ALLOC: AtomicU32 = AtomicU32::new(1); // XXX: 0 is reserved for whatever that takes `resource_id`.
impl Into<GpuRect> for &Damage {
fn into(self) -> GpuRect {
GpuRect {
@@ -34,12 +32,13 @@ pub struct Display<'a> {
cursor_queue: Arc<Queue<'a>>,
transport: Arc<dyn Transport>,
mapped: OnceCell<sgl::Sgl>,
active_vt: RefCell<usize>,
vts_map: RefCell<HashMap<usize, sgl::Sgl>>,
vts_res: RefCell<HashMap<usize, ResourceId>>,
width: u32,
height: u32,
resource_id: u32,
id: usize,
is_reseted: AtomicBool,
@@ -56,22 +55,24 @@ impl<'a> Display<'a> {
control_queue,
cursor_queue,
mapped: OnceCell::new(),
active_vt: RefCell::new(0),
vts_map: RefCell::new(HashMap::new()),
vts_res: RefCell::new(HashMap::new()),
width: 1920,
height: 1080,
transport,
id,
resource_id: RESOURCE_ALLOC.fetch_add(1, Ordering::SeqCst),
is_reseted: AtomicBool::new(false),
}
}
async fn init(&self) -> Result<(), Error> {
async fn init(&self, vt: usize) -> Result<(), Error> {
if !self.is_reseted.load(Ordering::SeqCst) {
// The device is already initialized.
self.set_scanout(vt).await?;
return Ok(());
}
@@ -80,7 +81,7 @@ impl<'a> Display<'a> {
log::info!("virtio-gpu: initializing GPU after a reset");
crate::reinit(self.control_queue.clone(), self.cursor_queue.clone())?;
self.remap_screen().await?;
self.set_scanout(vt).await?;
Ok(())
}
@@ -111,20 +112,9 @@ impl<'a> Display<'a> {
Ok(())
}
// 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;
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<*mut u8, Error> {
if let Some(mapped) = self.mapped.get() {
return Ok(mapped.as_ptr().wrapping_add(offset));
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));
}
let bpp = 32;
@@ -134,35 +124,40 @@ impl<'a> Display<'a> {
unsafe {
core::ptr::write_bytes(mapped.as_ptr() as *mut u8, 255, fb_size);
}
let _ = self.mapped.set(mapped);
let mapped = self.mapped.get().unwrap();
self.map_screen_with(offset, fb_size, mapped.as_ptr(), mapped.chunks())
.await
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 map_screen_with(
&self,
offset: usize,
_size: usize,
virt: *mut u8,
chunks: &[sgl::Chunk],
) -> Result<*mut u8, Error> {
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_format(ResourceFormat::Bgrx);
request.set_resource_id(self.resource_id);
request.set_resource_id(res_id);
self.send_request(request).await?;
let header = self.send_request(request).await?;
assert_eq!(header.ty.get(), CommandTy::RespOkNodata);
// 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(chunks.len())?.assume_init() };
for (entry, chunk) in mem_entries.iter_mut().zip(chunks.iter()) {
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()) {
*entry = MemEntry {
address: chunk.phys as u64,
length: chunk.length.next_multiple_of(PAGE_SIZE) as u32,
@@ -170,10 +165,7 @@ impl<'a> Display<'a> {
};
}
let attach_request = Dma::new(AttachBacking::new(
self.resource_id,
mem_entries.len() as u32,
))?;
let attach_request = Dma::new(AttachBacking::new(res_id, mem_entries.len() as u32))?;
let header = Dma::new(ControlHeader::default())?;
let command = ChainBuilder::new()
.chain(Buffer::new(&attach_request))
@@ -184,21 +176,33 @@ 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)
}
async fn set_scanout(&self, vt: usize) -> Result<(), Error> {
let res_id = self.create_res_for_screen(vt).await?;
let scanout_request = Dma::new(SetScanout::new(
self.id as u32,
self.resource_id,
res_id,
GpuRect::new(0, 0, self.width, self.height),
))?;
let header = self.send_request(scanout_request).await?;
assert_eq!(header.ty.get(), CommandTy::RespOkNodata);
self.flush(None).await?;
self.flush(vt, None).await?;
Ok(virt.wrapping_add(offset))
Ok(())
}
/// If `damage` is `None`, the entire screen is flushed.
async fn flush(&self, damage: Option<&Damage>) -> Result<(), Error> {
async fn flush(&self, vt: usize, damage: Option<&Damage>) -> Result<(), Error> {
if vt != *self.active_vt.borrow() {
return Ok(());
}
let damage = if let Some(damage) = damage {
damage.into()
} else {
@@ -211,7 +215,7 @@ impl<'a> Display<'a> {
};
let req = Dma::new(XferToHost2d::new(
self.resource_id,
self.vts_res.borrow()[&vt],
GpuRect {
x: 0,
y: 0,
@@ -222,21 +226,26 @@ impl<'a> Display<'a> {
let header = self.send_request(req).await?;
assert_eq!(header.ty.get(), CommandTy::RespOkNodata);
self.flush_resource(ResourceFlush::new(self.resource_id, damage.clone()))
.await?;
self.flush_resource(ResourceFlush::new(
self.vts_res.borrow()[&vt],
damage.clone(),
))
.await?;
Ok(())
}
/// This detaches any backing pages from the display and unrefs the resource. Also resets the
/// device, which is required to go back to legacy mode.
async fn detach(&self) -> Result<(), Error> {
let request = Dma::new(DetachBacking::new(self.resource_id))?;
let header = self.send_request(request).await?;
assert_eq!(header.ty.get(), CommandTy::RespOkNodata);
for (_vt, res_id) in self.vts_res.borrow_mut().drain() {
let request = Dma::new(DetachBacking::new(res_id))?;
let header = self.send_request(request).await?;
assert_eq!(header.ty.get(), CommandTy::RespOkNodata);
let request = Dma::new(ResourceUnref::new(self.resource_id))?;
let header = self.send_request(request).await?;
assert_eq!(header.ty.get(), CommandTy::RespOkNodata);
let request = Dma::new(ResourceUnref::new(res_id))?;
let header = self.send_request(request).await?;
assert_eq!(header.ty.get(), CommandTy::RespOkNodata);
}
// Go back to legacy mode.
self.transport.reset();
@@ -310,6 +319,11 @@ impl<'a> Scheme<'a> {
transport.clone(),
id,
);
// FIXME this is a hack to avoid breaking things while we need to co-exist with vesad
// Somehow necessary to ensure that creating a resource on the first reinitialization
// after this detach doesn't fail.
display.init(1).await?;
display.detach().await?;
result.push(Arc::new(display));
}
@@ -340,14 +354,12 @@ impl<'a> Scheme<'a> {
VtEventKind::Activate => {
log::info!("activate {}", vt_event.vt);
for handle in self.handles.values() {
if handle.vt != vt_event.vt {
continue;
}
for display in &self.displays {
log::warn!("virtio-gpu: activating");
futures::executor::block_on(handle.display.init()).unwrap();
futures::executor::block_on(display.init(vt_event.vt)).unwrap();
*display.active_vt.borrow_mut() = vt_event.vt;
}
}
@@ -408,7 +420,7 @@ impl<'a> SchemeMut for Scheme<'a> {
fn fsync(&mut self, id: usize) -> syscall::Result<usize> {
let handle = self.handles.get(&id).ok_or(SysError::new(EINVAL))?;
futures::executor::block_on(handle.display.flush(None)).unwrap();
futures::executor::block_on(handle.display.flush(handle.vt, None)).unwrap();
Ok(0)
}
@@ -433,10 +445,11 @@ impl<'a> SchemeMut for Scheme<'a> {
) -> syscall::Result<usize> {
let handle = self.handles.get(&id).ok_or(SysError::new(EINVAL))?;
// The VT is not active and the device is reseted. Ask them to try
// again later.
// The VT is not active and the device is reseted. Ignore the damage. We will recreate the
// backing storage from scratch next time we initialize, which is equivalent to damaging the
// entire buffer.
if handle.display.is_reseted.load(Ordering::SeqCst) {
return Err(SysError::new(EAGAIN));
return Ok(buf.len());
}
let damages = unsafe {
@@ -447,7 +460,7 @@ impl<'a> SchemeMut for Scheme<'a> {
};
for damage in damages {
futures::executor::block_on(handle.display.flush(Some(damage))).unwrap();
futures::executor::block_on(handle.display.flush(handle.vt, Some(damage))).unwrap();
}
Ok(buf.len())
@@ -465,7 +478,9 @@ impl<'a> SchemeMut for Scheme<'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.map_screen(offset as usize)).unwrap();
let ptr =
futures::executor::block_on(handle.display.mmap_screen(handle.vt, offset as usize))
.unwrap();
Ok(ptr as usize)
}
}