Merge branch 'virtio_gpu_resize' into 'master'
graphics: Handle virtio-gpu display resizing in fbbootlogd See merge request redox-os/drivers!273
This commit is contained in:
@@ -226,4 +226,37 @@ impl TextScreen {
|
||||
|
||||
damage
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, old_map: &mut DisplayMap, new_map: &mut DisplayMap) {
|
||||
// FIXME fold row when target is narrower and maybe unfold when it is wider
|
||||
fn copy_row(
|
||||
old_map: &mut DisplayMap,
|
||||
new_map: &mut DisplayMap,
|
||||
from_row: usize,
|
||||
to_row: usize,
|
||||
) {
|
||||
for x in 0..cmp::min(old_map.width, new_map.width) {
|
||||
let old_idx = from_row * old_map.width + x;
|
||||
let new_idx = to_row * new_map.width + x;
|
||||
unsafe {
|
||||
(*new_map.offscreen)[new_idx] = (*old_map.offscreen)[old_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if new_map.height >= old_map.height {
|
||||
for row in 0..old_map.height {
|
||||
copy_row(old_map, new_map, row, row);
|
||||
}
|
||||
} else {
|
||||
let deleted_rows = (old_map.height - new_map.height).div_ceil(16);
|
||||
for row in 0..new_map.height {
|
||||
if row + (deleted_rows + 1) * 16 >= old_map.height {
|
||||
break;
|
||||
}
|
||||
copy_row(old_map, new_map, row + deleted_rows * 16, row);
|
||||
}
|
||||
self.console.state.y = self.console.state.y.saturating_sub(deleted_rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#![feature(slice_as_array)]
|
||||
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::io;
|
||||
use std::mem::transmute;
|
||||
use std::sync::Arc;
|
||||
|
||||
use graphics_ipc::v1::{CursorDamage, Damage};
|
||||
@@ -8,7 +11,7 @@ use libredox::Fd;
|
||||
use redox_scheme::scheme::SchemeSync;
|
||||
use redox_scheme::{CallerCtx, OpenResult, RequestKind, SignalBehavior, Socket};
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
use syscall::{Error, MapFlags, Result, EAGAIN, EBADF, EINVAL};
|
||||
use syscall::{Error, MapFlags, Result, EAGAIN, EBADF, EINVAL, ENOENT, EOPNOTSUPP};
|
||||
|
||||
pub trait GraphicsAdapter {
|
||||
type Framebuffer: Framebuffer;
|
||||
@@ -52,7 +55,7 @@ pub struct GraphicsScheme<T: GraphicsAdapter> {
|
||||
scheme_name: String,
|
||||
socket: Socket,
|
||||
next_id: usize,
|
||||
handles: BTreeMap<usize, Handle>,
|
||||
handles: BTreeMap<usize, Handle<T>>,
|
||||
|
||||
active_vt: usize,
|
||||
vts: HashMap<usize, VtState<T>>,
|
||||
@@ -63,8 +66,16 @@ struct VtState<T: GraphicsAdapter> {
|
||||
cursor_plane: Option<CursorPlane<T::Cursor>>,
|
||||
}
|
||||
|
||||
enum Handle {
|
||||
V1Screen { vt: usize, screen: usize },
|
||||
enum Handle<T: GraphicsAdapter> {
|
||||
V1Screen {
|
||||
vt: usize,
|
||||
screen: usize,
|
||||
},
|
||||
V2 {
|
||||
vt: usize,
|
||||
next_id: usize,
|
||||
fbs: HashMap<usize, Arc<T::Framebuffer>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||
@@ -100,6 +111,8 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||
VtEventKind::Activate => {
|
||||
log::info!("activate {}", vt_event.vt);
|
||||
|
||||
self.active_vt = vt_event.vt;
|
||||
|
||||
let vt_state =
|
||||
Self::get_or_create_vt(&mut self.adapter, &mut self.vts, vt_event.vt);
|
||||
|
||||
@@ -110,8 +123,6 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||
if let Some(cursor_plane) = &vt_state.cursor_plane {
|
||||
self.adapter.handle_cursor(cursor_plane, true);
|
||||
}
|
||||
|
||||
self.active_vt = vt_event.vt;
|
||||
}
|
||||
|
||||
VtEventKind::Resize => {
|
||||
@@ -120,6 +131,10 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_displays_changed(&mut self) {
|
||||
// FIXME notify clients
|
||||
}
|
||||
|
||||
/// Process new scheme requests.
|
||||
///
|
||||
/// This needs to be called each time there is a new event on the scheme
|
||||
@@ -200,22 +215,40 @@ impl<T: GraphicsAdapter> SchemeSync for GraphicsScheme<T> {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
let mut parts = path.split('/');
|
||||
let mut screen = parts.next().unwrap_or("").split('.');
|
||||
let handle = if path.starts_with("v") {
|
||||
if !path.starts_with("v2/") {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
let vt = path["v2/".len()..]
|
||||
.parse::<usize>()
|
||||
.map_err(|_| Error::new(EINVAL))?;
|
||||
|
||||
let vt = screen.next().unwrap_or("").parse::<usize>().unwrap();
|
||||
let id = screen.next().unwrap_or("").parse::<usize>().unwrap_or(0);
|
||||
// Ensure the VT exists such that the rest of the methods can freely access it.
|
||||
Self::get_or_create_vt(&mut self.adapter, &mut self.vts, vt);
|
||||
|
||||
if id >= self.adapter.display_count() {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
Handle::V2 {
|
||||
vt,
|
||||
next_id: 0,
|
||||
fbs: HashMap::new(),
|
||||
}
|
||||
} else {
|
||||
let mut parts = path.split('/');
|
||||
let mut screen = parts.next().unwrap_or("").split('.');
|
||||
|
||||
// Ensure the VT exists such that the rest of the methods can freely access it.
|
||||
Self::get_or_create_vt(&mut self.adapter, &mut self.vts, vt);
|
||||
let vt = screen.next().unwrap_or("").parse::<usize>().unwrap();
|
||||
let id = screen.next().unwrap_or("").parse::<usize>().unwrap_or(0);
|
||||
|
||||
if id >= self.adapter.display_count() {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
// Ensure the VT exists such that the rest of the methods can freely access it.
|
||||
Self::get_or_create_vt(&mut self.adapter, &mut self.vts, vt);
|
||||
|
||||
Handle::V1Screen { vt, screen: id }
|
||||
};
|
||||
self.next_id += 1;
|
||||
self.handles
|
||||
.insert(self.next_id, Handle::V1Screen { vt, screen: id });
|
||||
self.handles.insert(self.next_id, handle);
|
||||
Ok(OpenResult::ThisScheme {
|
||||
number: self.next_id,
|
||||
flags: NewFdFlags::empty(),
|
||||
@@ -233,6 +266,11 @@ impl<T: GraphicsAdapter> SchemeSync for GraphicsScheme<T> {
|
||||
framebuffer.height()
|
||||
)
|
||||
}
|
||||
Handle::V2 {
|
||||
vt,
|
||||
next_id: _,
|
||||
fbs: _,
|
||||
} => format!("/scheme/{}/v2/{vt}", self.scheme_name),
|
||||
};
|
||||
buf[..path.len()].copy_from_slice(path.as_bytes());
|
||||
Ok(path.len())
|
||||
@@ -253,6 +291,7 @@ impl<T: GraphicsAdapter> SchemeSync for GraphicsScheme<T> {
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Handle::V2 { .. } => Err(Error::new(EOPNOTSUPP)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +315,7 @@ impl<T: GraphicsAdapter> SchemeSync for GraphicsScheme<T> {
|
||||
|
||||
Ok(1)
|
||||
}
|
||||
Handle::V2 { .. } => Err(Error::new(EOPNOTSUPP)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,6 +394,122 @@ impl<T: GraphicsAdapter> SchemeSync for GraphicsScheme<T> {
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
Handle::V2 { .. } => Err(Error::new(EOPNOTSUPP)),
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&mut self, id: usize, payload: &mut [u8], metadata: &[u64]) -> Result<usize> {
|
||||
use graphics_ipc::v2::ipc;
|
||||
|
||||
match self.handles.get_mut(&id).ok_or(Error::new(EBADF))? {
|
||||
Handle::V1Screen { .. } => {
|
||||
return Err(Error::new(EOPNOTSUPP));
|
||||
}
|
||||
Handle::V2 { vt, next_id, fbs } => match metadata[0] {
|
||||
ipc::DISPLAY_COUNT => {
|
||||
if payload.len() < size_of::<ipc::DisplayCount>() {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
let payload = unsafe {
|
||||
transmute::<&mut [u8; size_of::<ipc::DisplayCount>()], &mut ipc::DisplayCount>(
|
||||
payload.as_mut_array().unwrap(),
|
||||
)
|
||||
};
|
||||
payload.count = self.adapter.display_count();
|
||||
Ok(size_of::<ipc::DisplayCount>())
|
||||
}
|
||||
ipc::DISPLAY_SIZE => {
|
||||
if payload.len() < size_of::<ipc::DisplaySize>() {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
let payload = unsafe {
|
||||
transmute::<&mut [u8; size_of::<ipc::DisplaySize>()], &mut ipc::DisplaySize>(
|
||||
payload.as_mut_array().unwrap(),
|
||||
)
|
||||
};
|
||||
let display_id = payload.display_id;
|
||||
if display_id >= self.adapter.display_count() {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
let (width, height) = self.adapter.display_size(display_id);
|
||||
payload.width = width;
|
||||
payload.height = height;
|
||||
Ok(size_of::<ipc::DisplaySize>())
|
||||
}
|
||||
ipc::CREATE_DUMB_FRAMEBUFFER => {
|
||||
if payload.len() < size_of::<ipc::CreateDumbFramebuffer>() {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
let payload = unsafe {
|
||||
transmute::<
|
||||
&mut [u8; size_of::<ipc::CreateDumbFramebuffer>()],
|
||||
&mut ipc::CreateDumbFramebuffer,
|
||||
>(payload.as_mut_array().unwrap())
|
||||
};
|
||||
|
||||
let fb = self
|
||||
.adapter
|
||||
.create_dumb_framebuffer(payload.width, payload.height);
|
||||
|
||||
*next_id += 1;
|
||||
fbs.insert(*next_id, Arc::new(fb));
|
||||
payload.fb_id = *next_id;
|
||||
Ok(size_of::<ipc::CreateDumbFramebuffer>())
|
||||
}
|
||||
ipc::DUMB_FRAMEBUFFER_MAP_OFFSET => {
|
||||
if payload.len() < size_of::<ipc::DumbFramebufferMapOffset>() {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
let payload = unsafe {
|
||||
transmute::<
|
||||
&mut [u8; size_of::<ipc::DumbFramebufferMapOffset>()],
|
||||
&mut ipc::DumbFramebufferMapOffset,
|
||||
>(payload.as_mut_array().unwrap())
|
||||
};
|
||||
|
||||
let fb_id = payload.fb_id;
|
||||
|
||||
if !fbs.contains_key(&fb_id) {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
// FIXME use a better scheme for creating map offsets
|
||||
assert!(fbs[&fb_id].width() * fbs[&fb_id].height() * 4 < 0x1_000_000);
|
||||
|
||||
payload.offset = fb_id * 0x10_000_000;
|
||||
|
||||
Ok(size_of::<ipc::DumbFramebufferMapOffset>())
|
||||
}
|
||||
ipc::UPDATE_PLANE => {
|
||||
if payload.len() < size_of::<ipc::UpdatePlane>() {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
let payload = unsafe {
|
||||
transmute::<&mut [u8; size_of::<ipc::UpdatePlane>()], &mut ipc::UpdatePlane>(
|
||||
payload.as_mut_array().unwrap(),
|
||||
)
|
||||
};
|
||||
|
||||
let display_id = payload.display_id;
|
||||
if display_id >= self.adapter.display_count() {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
let Some(framebuffer) = fbs.get(&{ payload.fb_id }) else {
|
||||
return Err(Error::new(EINVAL));
|
||||
};
|
||||
|
||||
self.vts.get_mut(vt).unwrap().display_fbs[display_id] = framebuffer.clone();
|
||||
|
||||
if *vt == self.active_vt {
|
||||
self.adapter
|
||||
.update_plane(display_id, framebuffer, payload.damage);
|
||||
}
|
||||
|
||||
Ok(size_of::<ipc::UpdatePlane>())
|
||||
}
|
||||
_ => return Err(Error::new(EINVAL)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,6 +524,16 @@ impl<T: GraphicsAdapter> SchemeSync for GraphicsScheme<T> {
|
||||
// log::trace!("KSMSG MMAP {} {:?} {} {}", id, _flags, _offset, _size);
|
||||
let (framebuffer, offset) = match self.handles.get(&id).ok_or(Error::new(EINVAL))? {
|
||||
Handle::V1Screen { vt, screen } => (&self.vts[vt].display_fbs[*screen], offset),
|
||||
Handle::V2 {
|
||||
vt: _,
|
||||
next_id: _,
|
||||
fbs,
|
||||
} => (
|
||||
fbs.get(&(offset as usize / 0x10_000_000))
|
||||
.ok_or(Error::new(EINVAL))
|
||||
.unwrap(),
|
||||
offset & (0x10_000_000 - 1),
|
||||
),
|
||||
};
|
||||
let ptr = T::map_dumb_framebuffer(&mut self.adapter, framebuffer);
|
||||
Ok(unsafe { ptr.add(offset as usize) } as usize)
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::ptr;
|
||||
|
||||
use graphics_ipc::v1::V1GraphicsHandle;
|
||||
use console_draw::TextScreen;
|
||||
use graphics_ipc::v2::V2GraphicsHandle;
|
||||
use inputd::ConsumerHandle;
|
||||
use redox_scheme::Scheme;
|
||||
use syscall::{Error, Result, EINVAL, ENOENT};
|
||||
|
||||
pub struct DisplayMap {
|
||||
display_handle: V1GraphicsHandle,
|
||||
display_handle: V2GraphicsHandle,
|
||||
fb: usize,
|
||||
inner: graphics_ipc::v1::DisplayMap,
|
||||
}
|
||||
|
||||
@@ -30,18 +33,24 @@ impl FbbootlogScheme {
|
||||
}
|
||||
|
||||
pub fn handle_handoff(&mut self) {
|
||||
let new_display_handle = match self.input_handle.open_display() {
|
||||
Ok(display) => V1GraphicsHandle::from_file(display).unwrap(),
|
||||
let new_display_handle = match self.input_handle.open_display_v2() {
|
||||
Ok(display) => V2GraphicsHandle::from_file(display).unwrap(),
|
||||
Err(err) => {
|
||||
eprintln!("fbbootlogd: No display present yet: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match new_display_handle.map_display() {
|
||||
let (width, height) = new_display_handle.display_size(0).unwrap();
|
||||
let fb = new_display_handle
|
||||
.create_dumb_framebuffer(width, height)
|
||||
.unwrap();
|
||||
|
||||
match new_display_handle.map_dumb_framebuffer(fb, width, height) {
|
||||
Ok(display_map) => {
|
||||
self.display_map = Some(DisplayMap {
|
||||
display_handle: new_display_handle,
|
||||
fb,
|
||||
inner: display_map,
|
||||
});
|
||||
|
||||
@@ -52,6 +61,53 @@ impl FbbootlogScheme {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_resize(map: &mut DisplayMap, text_screen: &mut TextScreen) {
|
||||
let (width, height) = match map.display_handle.display_size(0) {
|
||||
Ok((width, height)) => (width, height),
|
||||
Err(err) => {
|
||||
eprintln!("fbbootlogd: failed to get display size: {}", err);
|
||||
(map.inner.width() as u32, map.inner.height() as u32)
|
||||
}
|
||||
};
|
||||
|
||||
if width as usize != map.inner.width() || height as usize != map.inner.height() {
|
||||
match map.display_handle.create_dumb_framebuffer(width, height) {
|
||||
Ok(fb) => match map.display_handle.map_dumb_framebuffer(fb, width, height) {
|
||||
Ok(mut new_map) => {
|
||||
let count = new_map.ptr().len();
|
||||
unsafe {
|
||||
ptr::write_bytes(new_map.ptr_mut() as *mut u32, 0, count);
|
||||
}
|
||||
|
||||
text_screen.resize(
|
||||
&mut console_draw::DisplayMap {
|
||||
offscreen: map.inner.ptr_mut(),
|
||||
width: map.inner.width(),
|
||||
height: map.inner.height(),
|
||||
},
|
||||
&mut console_draw::DisplayMap {
|
||||
offscreen: new_map.ptr_mut(),
|
||||
width: new_map.width(),
|
||||
height: new_map.height(),
|
||||
},
|
||||
);
|
||||
|
||||
map.fb = fb;
|
||||
map.inner = new_map;
|
||||
|
||||
eprintln!("fbbootlogd: mapped display");
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("fbbootlogd: failed to open display: {}", err);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
eprintln!("fbbootlogd: failed to create framebuffer: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Scheme for FbbootlogScheme {
|
||||
@@ -91,6 +147,8 @@ impl Scheme for FbbootlogScheme {
|
||||
|
||||
fn write(&mut self, _id: usize, buf: &[u8], _offset: u64, _fcntl_flags: u32) -> Result<usize> {
|
||||
if let Some(map) = &mut self.display_map {
|
||||
Self::handle_resize(map, &mut self.text_screen);
|
||||
|
||||
let damage = self.text_screen.write(
|
||||
&mut console_draw::DisplayMap {
|
||||
offscreen: map.inner.ptr_mut(),
|
||||
@@ -102,7 +160,7 @@ impl Scheme for FbbootlogScheme {
|
||||
);
|
||||
|
||||
if let Some(map) = &self.display_map {
|
||||
map.display_handle.sync_rect(damage).unwrap();
|
||||
map.display_handle.update_plane(0, map.fb, damage).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
mod common;
|
||||
pub mod v1;
|
||||
pub mod v2;
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::{io, mem, ptr};
|
||||
|
||||
use libredox::flag;
|
||||
|
||||
pub use crate::common::{Damage, DisplayMap};
|
||||
|
||||
extern "C" {
|
||||
fn redox_sys_call_v0(
|
||||
fd: usize,
|
||||
payload: *mut u8,
|
||||
payload_len: usize,
|
||||
flags: usize,
|
||||
metadata: *const u64,
|
||||
metadata_len: usize,
|
||||
) -> usize;
|
||||
}
|
||||
|
||||
unsafe fn sys_call<T>(
|
||||
fd: &impl AsRawFd,
|
||||
payload: &mut T,
|
||||
flags: usize,
|
||||
metadata: &[u64],
|
||||
) -> libredox::error::Result<usize> {
|
||||
libredox::error::Error::demux(redox_sys_call_v0(
|
||||
fd.as_raw_fd() as usize,
|
||||
payload as *mut T as *mut u8,
|
||||
mem::size_of::<T>(),
|
||||
flags,
|
||||
metadata.as_ptr(),
|
||||
metadata.len(),
|
||||
))
|
||||
}
|
||||
|
||||
/// A graphics handle using the (currently unstable) 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.
|
||||
pub struct V2GraphicsHandle {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl V2GraphicsHandle {
|
||||
pub fn from_file(file: File) -> io::Result<Self> {
|
||||
Ok(V2GraphicsHandle { file })
|
||||
}
|
||||
|
||||
pub fn display_count(&self) -> io::Result<usize> {
|
||||
let mut cmd = ipc::DisplayCount { count: 0 };
|
||||
unsafe {
|
||||
sys_call(&self.file, &mut cmd, 0, &[ipc::DISPLAY_COUNT, 0, 0])?;
|
||||
}
|
||||
Ok(cmd.count)
|
||||
}
|
||||
|
||||
pub fn display_size(&self, id: usize) -> io::Result<(u32, u32)> {
|
||||
let mut cmd = ipc::DisplaySize {
|
||||
display_id: id,
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
unsafe {
|
||||
sys_call(&self.file, &mut cmd, 0, &[ipc::DISPLAY_SIZE, 0, 0])?;
|
||||
}
|
||||
Ok((cmd.width, cmd.height))
|
||||
}
|
||||
|
||||
pub fn create_dumb_framebuffer(&self, width: u32, height: u32) -> io::Result<usize> {
|
||||
let mut cmd = ipc::CreateDumbFramebuffer {
|
||||
width,
|
||||
height,
|
||||
|
||||
fb_id: 0,
|
||||
};
|
||||
unsafe {
|
||||
sys_call(
|
||||
&self.file,
|
||||
&mut cmd,
|
||||
0,
|
||||
&[ipc::CREATE_DUMB_FRAMEBUFFER, 0, 0],
|
||||
)?;
|
||||
}
|
||||
Ok(cmd.fb_id)
|
||||
}
|
||||
|
||||
pub fn map_dumb_framebuffer(
|
||||
&self,
|
||||
id: usize,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> io::Result<DisplayMap> {
|
||||
let mut cmd = ipc::DumbFramebufferMapOffset {
|
||||
fb_id: id,
|
||||
offset: 0,
|
||||
};
|
||||
unsafe {
|
||||
sys_call(
|
||||
&self.file,
|
||||
&mut cmd,
|
||||
0,
|
||||
&[ipc::DUMB_FRAMEBUFFER_MAP_OFFSET, 0, 0],
|
||||
)?;
|
||||
}
|
||||
|
||||
let display_ptr = unsafe {
|
||||
libredox::call::mmap(libredox::call::MmapArgs {
|
||||
fd: self.file.as_raw_fd() as usize,
|
||||
offset: cmd.offset as u64,
|
||||
length: (width * height * 4) as usize,
|
||||
prot: flag::PROT_READ | flag::PROT_WRITE,
|
||||
flags: flag::MAP_SHARED,
|
||||
addr: core::ptr::null_mut(),
|
||||
})?
|
||||
};
|
||||
let offscreen = ptr::slice_from_raw_parts_mut(
|
||||
display_ptr as *mut u32,
|
||||
width as usize * height as usize,
|
||||
);
|
||||
|
||||
Ok(unsafe { DisplayMap::new(offscreen, width as usize, height as usize) })
|
||||
}
|
||||
|
||||
pub fn update_plane(&self, display_id: usize, fb_id: usize, damage: Damage) -> io::Result<()> {
|
||||
let mut cmd = ipc::UpdatePlane {
|
||||
display_id,
|
||||
fb_id,
|
||||
damage,
|
||||
};
|
||||
unsafe {
|
||||
sys_call(&self.file, &mut cmd, 0, &[ipc::UPDATE_PLANE, 0, 0])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod ipc {
|
||||
use crate::common::Damage;
|
||||
|
||||
pub const DISPLAY_COUNT: u64 = 1;
|
||||
#[repr(C, packed)]
|
||||
pub struct DisplayCount {
|
||||
pub count: usize,
|
||||
}
|
||||
|
||||
pub const DISPLAY_SIZE: u64 = 2;
|
||||
#[repr(C, packed)]
|
||||
pub struct DisplaySize {
|
||||
pub display_id: usize,
|
||||
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
pub const CREATE_DUMB_FRAMEBUFFER: u64 = 3;
|
||||
#[repr(C, packed)]
|
||||
pub struct CreateDumbFramebuffer {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
|
||||
pub fb_id: usize,
|
||||
}
|
||||
|
||||
pub const DUMB_FRAMEBUFFER_MAP_OFFSET: u64 = 4;
|
||||
#[repr(C, packed)]
|
||||
pub struct DumbFramebufferMapOffset {
|
||||
pub fb_id: usize,
|
||||
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
pub const UPDATE_PLANE: u64 = 5;
|
||||
#[repr(C, packed)]
|
||||
pub struct UpdatePlane {
|
||||
pub display_id: usize,
|
||||
pub fb_id: usize,
|
||||
pub damage: Damage,
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ use virtio_core::MSIX_PRIMARY_VECTOR;
|
||||
|
||||
mod scheme;
|
||||
|
||||
// const VIRTIO_GPU_EVENT_DISPLAY: u32 = 1 << 0;
|
||||
const VIRTIO_GPU_EVENT_DISPLAY: u32 = 1 << 0;
|
||||
const VIRTIO_GPU_MAX_SCANOUTS: usize = 16;
|
||||
|
||||
#[repr(C)]
|
||||
@@ -457,6 +457,8 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
|
||||
.transport
|
||||
.setup_queue(MSIX_PRIMARY_VECTOR, &device.irq_handle)?;
|
||||
|
||||
device.transport.setup_config_notify(MSIX_PRIMARY_VECTOR);
|
||||
|
||||
device.transport.run_device();
|
||||
deamon.ready().unwrap();
|
||||
|
||||
@@ -471,6 +473,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
|
||||
enum Source {
|
||||
Input,
|
||||
Scheme,
|
||||
Interrupt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,8 +493,15 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
event_queue
|
||||
.subscribe(
|
||||
device.irq_handle.as_raw_fd() as usize,
|
||||
Source::Interrupt,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let all = [Source::Input, Source::Scheme];
|
||||
let all = [Source::Input, Source::Scheme, Source::Interrupt];
|
||||
for event in all
|
||||
.into_iter()
|
||||
.chain(event_queue.map(|e| e.expect("virtio-gpud: failed to get next event").user_data))
|
||||
@@ -510,6 +520,23 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
|
||||
.tick()
|
||||
.expect("virtio-gpud: failed to process scheme events");
|
||||
}
|
||||
Source::Interrupt => loop {
|
||||
let before_gen = device.transport.config_generation();
|
||||
|
||||
let events = config.events_read.get();
|
||||
|
||||
if events & VIRTIO_GPU_EVENT_DISPLAY != 0 {
|
||||
futures::executor::block_on(scheme.adapter_mut().update_displays(config))
|
||||
.unwrap();
|
||||
scheme.notify_displays_changed();
|
||||
config.events_clear.set(VIRTIO_GPU_EVENT_DISPLAY);
|
||||
}
|
||||
|
||||
let after_gen = device.transport.config_generation();
|
||||
if before_gen == after_gen {
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,40 @@ pub struct VirtGpuAdapter<'a> {
|
||||
}
|
||||
|
||||
impl VirtGpuAdapter<'_> {
|
||||
pub async fn update_displays(&mut self, config: &mut GpuConfig) -> Result<(), Error> {
|
||||
let mut display_info = self.get_display_info().await?;
|
||||
let raw_displays = &mut display_info.display_info[..config.num_scanouts() as usize];
|
||||
|
||||
self.displays.resize(
|
||||
raw_displays.len(),
|
||||
Display {
|
||||
width: 0,
|
||||
height: 0,
|
||||
active_resource: None,
|
||||
},
|
||||
);
|
||||
for (i, info) in raw_displays.iter().enumerate() {
|
||||
log::info!(
|
||||
"virtio-gpu: display {i} ({}x{}px)",
|
||||
info.rect.width,
|
||||
info.rect.height
|
||||
);
|
||||
|
||||
if info.rect.width == 0 || info.rect.height == 0 {
|
||||
// QEMU gives all displays other than the first a zero width and height, but trying
|
||||
// to attach a zero sized framebuffer to the display will result an error, so
|
||||
// default to 640x480px.
|
||||
self.displays[i].width = 640;
|
||||
self.displays[i].height = 480;
|
||||
} else {
|
||||
self.displays[i].width = info.rect.width;
|
||||
self.displays[i].height = info.rect.height;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_request<T>(&self, request: Dma<T>) -> Result<Dma<ControlHeader>, Error> {
|
||||
let header = Dma::new(ControlHeader::default())?;
|
||||
let command = ChainBuilder::new()
|
||||
@@ -350,7 +384,7 @@ pub struct GpuScheme {}
|
||||
|
||||
impl<'a> GpuScheme {
|
||||
pub async fn new(
|
||||
config: &'a mut GpuConfig,
|
||||
config: &mut GpuConfig,
|
||||
control_queue: Arc<Queue<'a>>,
|
||||
cursor_queue: Arc<Queue<'a>>,
|
||||
transport: Arc<dyn Transport>,
|
||||
@@ -362,33 +396,7 @@ impl<'a> GpuScheme {
|
||||
displays: vec![],
|
||||
};
|
||||
|
||||
let mut display_info = adapter.get_display_info().await?;
|
||||
let raw_displays = &mut display_info.display_info[..config.num_scanouts() as usize];
|
||||
|
||||
for info in raw_displays.iter() {
|
||||
log::info!(
|
||||
"virtio-gpu: opening display ({}x{}px)",
|
||||
info.rect.width,
|
||||
info.rect.height
|
||||
);
|
||||
|
||||
if info.rect.width == 0 || info.rect.height == 0 {
|
||||
// QEMU gives all displays other than the first a zero width and height, but trying
|
||||
// to attach a zero sized framebuffer to the display will result an error, so
|
||||
// default to 640x480px.
|
||||
adapter.displays.push(Display {
|
||||
width: 640,
|
||||
height: 480,
|
||||
active_resource: None,
|
||||
});
|
||||
} else {
|
||||
adapter.displays.push(Display {
|
||||
width: info.rect.width,
|
||||
height: info.rect.height,
|
||||
active_resource: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
adapter.update_displays(config).await?;
|
||||
|
||||
let scheme = GraphicsScheme::new(adapter, "display.virtio-gpu".to_owned());
|
||||
let handle = DisplayHandle::new("virtio-gpu").unwrap();
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::io::{self, Read, Write};
|
||||
use std::mem::size_of;
|
||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, RawFd};
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::path::PathBuf;
|
||||
use std::slice;
|
||||
|
||||
use libredox::flag::{O_CLOEXEC, O_NONBLOCK, O_RDWR};
|
||||
@@ -71,6 +72,34 @@ impl ConsumerHandle {
|
||||
Ok(display_file)
|
||||
}
|
||||
|
||||
pub fn open_display_v2(&self) -> io::Result<File> {
|
||||
let mut buffer = [0; 1024];
|
||||
let fd = self.0.as_raw_fd();
|
||||
let written = libredox::call::fpath(fd as usize, &mut buffer)?;
|
||||
|
||||
assert!(written <= buffer.len());
|
||||
|
||||
let mut display_path = PathBuf::from(
|
||||
std::str::from_utf8(&buffer[..written])
|
||||
.expect("init: display path UTF-8 check failed")
|
||||
.to_owned(),
|
||||
);
|
||||
display_path.set_file_name(format!(
|
||||
"v2/{}",
|
||||
display_path.file_name().unwrap().to_str().unwrap()
|
||||
));
|
||||
let display_path = display_path.to_str().unwrap();
|
||||
|
||||
let display_file =
|
||||
libredox::call::open(&display_path, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0)
|
||||
.map(|socket| unsafe { File::from_raw_fd(socket as RawFd) })
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("failed to open display {}: {}", display_path, err);
|
||||
});
|
||||
|
||||
Ok(display_file)
|
||||
}
|
||||
|
||||
pub fn read_events<'a>(&self, events: &'a mut [Event]) -> io::Result<ConsumerHandleEvent<'a>> {
|
||||
match read_to_slice(self.0.as_fd(), events) {
|
||||
Ok(count) => Ok(ConsumerHandleEvent::Events(&events[..count])),
|
||||
|
||||
@@ -492,6 +492,12 @@ pub trait Transport: Sync + Send {
|
||||
self.insert_status(DeviceStatusFlags::DRIVER_OK);
|
||||
}
|
||||
|
||||
/// Request to be notified on configuration changes on the given MSI-X vector.
|
||||
fn setup_config_notify(&self, vector: u16);
|
||||
|
||||
/// Each time the device configuration changes this number will be updated.
|
||||
fn config_generation(&self) -> u32;
|
||||
|
||||
/// Creates a new queue.
|
||||
///
|
||||
/// ## Panics
|
||||
@@ -601,6 +607,14 @@ impl Transport for StandardTransport<'_> {
|
||||
assert!((confirm & DeviceStatusFlags::FEATURES_OK) == DeviceStatusFlags::FEATURES_OK);
|
||||
}
|
||||
|
||||
fn setup_config_notify(&self, vector: u16) {
|
||||
self.common.lock().unwrap().config_msix_vector.set(vector);
|
||||
}
|
||||
|
||||
fn config_generation(&self) -> u32 {
|
||||
u32::from(self.common.lock().unwrap().config_generation.get())
|
||||
}
|
||||
|
||||
fn setup_queue(&self, vector: u16, irq_handle: &File) -> Result<Arc<Queue>, Error> {
|
||||
let mut common = self.common.lock().unwrap();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user