Merge branch 'core' into 'master'

drivers: add `virtio-gpu`!

See merge request redox-os/drivers!96
This commit is contained in:
Jeremy Soller
2023-07-07 14:08:58 +00:00
21 changed files with 1310 additions and 56 deletions
Generated
+33 -9
View File
@@ -674,6 +674,18 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "inputd"
version = "0.1.0"
dependencies = [
"anyhow",
"log",
"orbclient",
"redox-daemon",
"redox-log",
"redox_syscall 0.3.5",
]
[[package]]
name = "instant"
version = "0.1.12"
@@ -742,9 +754,9 @@ checksum = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef"
[[package]]
name = "libc"
version = "0.2.141"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "link-cplusplus"
@@ -773,12 +785,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.17"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if 1.0.0",
]
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
[[package]]
name = "matches"
@@ -1014,6 +1023,12 @@ dependencies = [
"uuid",
]
[[package]]
name = "paste"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35"
[[package]]
name = "paw"
version = "1.0.0"
@@ -1131,9 +1146,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.56"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
dependencies = [
"unicode-ident",
]
@@ -1921,6 +1936,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
name = "vesad"
version = "0.1.0"
dependencies = [
"inputd",
"orbclient",
"ransid",
"redox-daemon",
@@ -1966,9 +1982,17 @@ dependencies = [
name = "virtio-gpud"
version = "0.1.0"
dependencies = [
"anyhow",
"futures",
"inputd",
"log",
"orbclient",
"paste",
"pcid",
"redox-daemon",
"redox_syscall 0.3.5",
"spin",
"static_assertions",
"virtio-core",
]
+1
View File
@@ -23,6 +23,7 @@ members = [
"usbctl",
"usbhidd",
"usbscsid",
"inputd",
"virtio-blkd",
"virtio-netd",
+1 -1
View File
@@ -38,7 +38,7 @@ fn main() {
let mut scheme = BgaScheme {
bga: bga,
display: File::open("display:input").ok()
display: File::open("display/vesa:input").ok()
};
scheme.update_size();
+14 -10
View File
@@ -1,13 +1,17 @@
#!/usr/bin/bash
pushd virtio-core
cargo fmt
popd
function fmt() {
for dir in "$@"
do
pushd $dir
printf "\e[1;32mFormatting\e[0m $dir\n"
cargo fmt
popd
done
}
pushd virtio-netd
cargo fmt
popd
pushd virtio-blkd
cargo fmt
popd
fmt virtio-core \
virtio-netd \
virtio-blkd \
virtio-gpud \
inputd
-11
View File
@@ -23,7 +23,6 @@ subclass = 8
command = ["nvmed"]
use_channel = true
# -------------- virtio -------------- #
[[drivers]]
name = "virtio-blk"
class = 1
@@ -33,19 +32,9 @@ device = 4097
command = ["virtio-blkd"]
use_channel = true
[[drivers]]
name = "virtio-net"
class = 2
subclass = 0
vendor = 6900
device = 4096
command = ["virtio-netd"]
use_channel = true
[[drivers]]
name = "virtio-gpu"
class = 3
subclass = 0
vendor = 6900
device = 4176
command = ["virtio-gpud"]
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "inputd"
version = "0.1.0"
edition = "2021"
authors = ["Anhad Singh <andypython@protonmail.com>"]
[dependencies]
anyhow = "1.0.71"
log = "0.4.19"
redox-daemon = "0.1.0"
redox-log = "0.1.1"
redox_syscall = "0.3.5"
orbclient = "0.3.27"
+25
View File
@@ -0,0 +1,25 @@
use std::fs::File;
use std::io::{Error, Read};
pub struct Handle(File);
impl Handle {
pub fn new<S: Into<String>>(device_name: S) -> Result<Self, Error> {
let path = format!("input:handle/{}", device_name.into());
Ok(Self(File::open(path)?))
}
// The return value is the display identifier. It will be used to uniquely
// identify the display on activation events.
pub fn register(&mut self) -> Result<usize, Error> {
Ok(dbg!(self.0.read(&mut [])?))
}
}
#[repr(packed)]
pub struct Damage {
pub x: i32,
pub y: i32,
pub width: i32,
pub height: i32,
}
+339
View File
@@ -0,0 +1,339 @@
//! `:input`
//!
//! A seperate scheme is required since all of the input from different input devices is required
//! to be combined into a single stream which is later going to be processed by the "consumer"
//! which usually is Orbital.
//!
//! ## Input Device ("producer")
//! Write events to `input:producer`.
//!
//! ## Input Consumer ("consumer")
//! Read events from `input:consumer`. Optionally, set the `EVENT_READ` flag to be notified when
//! events are available.
use std::collections::BTreeMap;
use std::fs::File;
use std::io::{Read, Write};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use orbclient::{Event, EventOption};
use syscall::{Error as SysError, EventFlags, Packet, SchemeMut, EINVAL};
enum Handle {
Producer,
Consumer {
events: EventFlags,
pending: Vec<u8>,
notified: bool,
},
Device(Arc<String>),
}
impl Handle {
pub fn is_producer(&self) -> bool {
matches!(self, Handle::Producer)
}
}
struct InputScheme {
handles: BTreeMap<usize, Handle>,
next_id: AtomicUsize,
next_vt_id: AtomicUsize,
vts: BTreeMap<usize, Arc<String>>,
super_key: bool,
active_vt: Option<(Arc<String>, File)>,
}
impl InputScheme {
pub fn new() -> Self {
Self {
next_id: AtomicUsize::new(0),
next_vt_id: AtomicUsize::new(1),
handles: BTreeMap::new(),
vts: BTreeMap::new(),
super_key: false,
active_vt: None,
}
}
}
impl SchemeMut for InputScheme {
fn open(&mut self, path: &str, _flags: usize, _uid: u32, _gid: u32) -> syscall::Result<usize> {
let mut path_parts = path.split('/');
let command = path_parts.next().ok_or(SysError::new(EINVAL))?;
let handle_ty = match command {
"producer" => Handle::Producer,
"consumer" => Handle::Consumer {
events: EventFlags::empty(),
pending: Vec::new(),
notified: false,
},
"handle" => {
let value = path_parts.next().ok_or(SysError::new(EINVAL))?;
Handle::Device(Arc::new(value.to_string()))
}
_ => unreachable!("inputd: invalid path {path}"),
};
log::info!("inputd: {path} channel has been opened");
let fd = self.next_id.fetch_add(1, Ordering::SeqCst);
self.handles.insert(fd, handle_ty);
Ok(fd)
}
fn read(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result<usize> {
let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?;
match handle {
Handle::Consumer { pending, .. } => {
let copy = core::cmp::min(pending.len(), buf.len());
for (i, byte) in pending.drain(..copy).enumerate() {
buf[i] = byte;
}
Ok(copy)
}
Handle::Device(device) => {
let vt = self.next_vt_id.fetch_add(1, Ordering::SeqCst);
self.vts.insert(vt, device.clone());
Ok(vt)
}
_ => unreachable!(),
}
}
fn write(&mut self, id: usize, buf: &[u8]) -> syscall::Result<usize> {
if buf.len() == 1 && buf[0] > 0xf4 {
return Ok(1);
}
let events = unsafe {
core::slice::from_raw_parts(
buf.as_ptr() as *const Event,
buf.len() / core::mem::size_of::<Event>(),
)
};
for event in events.iter() {
let mut new_active_opt = None;
match event.to_option() {
EventOption::Key(key_event) => match key_event.scancode {
f @ 0x3B..=0x44 if self.super_key => {
// F1 through F10
new_active_opt = Some((f - 0x3A) as usize);
}
0x57 if self.super_key => {
// F11
new_active_opt = Some(11);
}
0x58 if self.super_key => {
// F12
new_active_opt = Some(12);
}
0x5B => {
// Super
self.super_key = key_event.pressed;
}
_ => (),
},
_ => continue,
}
if let Some(new_active) = new_active_opt {
if let Some(vt) = self.vts.get(&new_active) {
// If the VT is already active, don't do anything.
if let Some(current) = self.active_vt.as_ref() {
if vt == &current.0 {
continue;
}
}
// Result: display/virtio-gpu:3/acvtivate
let activate = format!("display/{vt}:{new_active}/activate");
log::info!("inputd: switching to VT #{new_active} ({activate})");
// Drop the old active VT first.
//
// TODO(andypython): This can be drastically improved by introducting something
// like ioctl() to the display scheme.
for (vt, device) in self.vts.iter() {
if vt == &new_active {
continue;
}
let deactivate = format!("display/{device}:{}/deactivate", vt);
let _ = File::open(&deactivate);
if device.contains("virtio") {
break;
}
}
let file = File::open(&activate).unwrap();
self.active_vt = Some((vt.clone(), file));
} else {
log::warn!("inputd: switch to non-existent VT #{new_active} was requested");
}
}
}
let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?;
assert!(handle.is_producer());
for handle in self.handles.values_mut() {
match handle {
Handle::Consumer {
ref mut pending,
ref mut notified,
..
} => {
pending.extend_from_slice(buf);
*notified = false;
}
_ => continue,
}
}
Ok(buf.len())
}
fn fevent(
&mut self,
id: usize,
flags: syscall::EventFlags,
) -> syscall::Result<syscall::EventFlags> {
let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?;
match handle {
Handle::Consumer {
ref mut events,
ref mut notified,
..
} => {
*events = flags;
*notified = false;
}
_ => unreachable!(),
}
Ok(EventFlags::empty())
}
fn close(&mut self, _id: usize) -> syscall::Result<usize> {
todo!()
}
}
fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
// Create the ":input" scheme.
let mut socket_file = File::create(":input")?;
let mut scheme = InputScheme::new();
deamon.ready().unwrap();
loop {
let mut should_handle = false;
let mut packet = Packet::default();
socket_file.read(&mut packet)?;
// The producer has written to the channel; the consumers should be notified.
if packet.a == syscall::SYS_WRITE {
should_handle = true;
}
scheme.handle(&mut packet);
socket_file.write(&packet)?;
if !should_handle {
continue;
}
for (id, handle) in scheme.handles.iter_mut() {
if let Handle::Consumer {
events,
pending,
ref mut notified,
} = handle
{
if pending.is_empty() || *notified || !events.contains(EventFlags::EVENT_READ) {
continue;
}
// Notify the consumer that we have some events to read. Yum yum.
let mut event_packet = Packet::default();
event_packet.a = syscall::SYS_FEVENT;
event_packet.b = *id;
event_packet.c = EventFlags::EVENT_READ.bits();
// Specifies the number of bytes that can be read non-blocking.
event_packet.d = pending.len();
socket_file.write(&event_packet)?;
*notified = true;
}
}
}
}
fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! {
deamon(redox_daemon).unwrap();
unreachable!();
}
#[cfg(target_os = "redox")]
pub fn setup_logging(level: log::LevelFilter, name: &str) {
use redox_log::{OutputBuilder, RedoxLogger};
let mut logger = RedoxLogger::new().with_output(
OutputBuilder::stderr()
.with_filter(level)
.with_ansi_escape_codes()
.flush_on_newline(true)
.build(),
);
match OutputBuilder::in_redox_logging_scheme("disk", "pcie", format!("{name}.log")) {
Ok(builder) => {
logger = logger.with_output(builder.with_filter(level).flush_on_newline(true).build())
}
Err(err) => eprintln!("inputd: failed to create log: {}", err),
}
match OutputBuilder::in_redox_logging_scheme("disk", "pcie", format!("{name}.ansi.log")) {
Ok(builder) => {
logger = logger.with_output(
builder
.with_filter(level)
.with_ansi_escape_codes()
.flush_on_newline(true)
.build(),
)
}
Err(err) => eprintln!("inputd: failed to create ANSI log: {}", err),
}
logger.enable().unwrap();
log::info!("inputd: enabled logger");
}
pub fn main() {
#[cfg(target_os = "redox")]
setup_logging(log::LevelFilter::Trace, "inputd");
redox_daemon::Daemon::new(daemon_runner).expect("virtio-core: failed to daemonize");
}
+2 -2
View File
@@ -45,8 +45,8 @@ fn daemon(daemon: redox_daemon::Daemon) -> ! {
let input = OpenOptions::new()
.write(true)
.open("display:input")
.expect("ps2d: failed to open display:input");
.open("input:producer")
.expect("ps2d: failed to open input:producer");
let mut event_file = OpenOptions::new()
.read(true)
+1 -1
View File
@@ -382,7 +382,7 @@ fn main() {
let report_ty = ReportTy::Input;
let report_id = 0;
let mut display = File::open("display:input").expect("Failed to open orbital input socket");
let mut display = File::open("display/vesa:input").expect("Failed to open orbital input socket");
//TODO: get dynamically
let mut display_width = 0;
+1 -1
View File
@@ -206,7 +206,7 @@ fn main() {
let mut width = 0;
let mut height = 0;
let mut display_opt = File::open("display:input").ok();
let mut display_opt = File::open("display/vesa:input").ok();
if let Some(ref display) = display_opt {
let mut buf: [u8; 4096] = [0; 4096];
if let Ok(count) = syscall::fpath(display.as_raw_fd() as usize, &mut buf) {
+1
View File
@@ -9,6 +9,7 @@ ransid = "0.4"
rusttype = { version = "0.2", optional = true }
redox_syscall = "0.3"
redox-daemon = "0.1"
inputd = { path = "../inputd" }
[features]
default = []
+1 -1
View File
@@ -90,7 +90,7 @@ fn main() {
redox_daemon::Daemon::new(|daemon| inner(daemon, framebuffers, &spec)).expect("failed to create daemon");
}
fn inner(daemon: redox_daemon::Daemon, framebuffers: Vec<FrameBuffer>, spec: &[bool]) -> ! {
let mut socket = File::create(":display").expect("vesad: failed to create display scheme");
let mut socket = File::create(":display/vesa").expect("vesad: failed to create display scheme");
let mut scheme = DisplayScheme::new(framebuffers, &spec);
+11 -5
View File
@@ -38,10 +38,13 @@ pub struct DisplayScheme {
next_id: usize,
pub handles: BTreeMap<usize, Handle>,
super_key: bool,
inputd_handle: inputd::Handle,
}
impl DisplayScheme {
pub fn new(mut framebuffers: Vec<FrameBuffer>, spec: &[bool]) -> DisplayScheme {
let mut inputd_handle = inputd::Handle::new("vesa").unwrap();
let mut onscreens = Vec::new();
for fb in framebuffers.iter_mut() {
onscreens.push(unsafe {
@@ -51,7 +54,6 @@ impl DisplayScheme {
let mut vts = BTreeMap::<VtIndex, BTreeMap<ScreenIndex, Box<dyn Screen>>>::new();
let mut vt_i = 1;
for &vt_type in spec.iter() {
let mut screens = BTreeMap::<ScreenIndex, Box<dyn Screen>>::new();
for fb_i in 0..framebuffers.len() {
@@ -62,8 +64,7 @@ impl DisplayScheme {
Box::new(TextScreen::new(Display::new(fb.width, fb.height)))
});
}
vts.insert(VtIndex(vt_i), screens);
vt_i += 1;
vts.insert(VtIndex(inputd_handle.register().unwrap()), screens);
}
DisplayScheme {
@@ -74,6 +75,7 @@ impl DisplayScheme {
next_id: 0,
handles: BTreeMap::new(),
super_key: false,
inputd_handle
}
}
@@ -168,11 +170,15 @@ impl SchemeMut for DisplayScheme {
let screen_i = ScreenIndex(
vt_screen.next().unwrap_or("").parse::<usize>().unwrap_or(0)
);
if let Some(screens) = self.vts.get(&vt_i) {
if screens.contains_key(&screen_i) {
if let Some(screens) = self.vts.get_mut(&vt_i) {
if let Some(screen) = screens.get_mut(&screen_i) {
for cmd in parts {
if cmd == "activate" {
self.active = vt_i;
screen.redraw(
self.onscreens[screen_i.0],
self.framebuffers[screen_i.0].stride
);
}
}
+2 -6
View File
@@ -12,7 +12,7 @@ use syscall::{Io, PHYSMAP_NO_CACHE, PHYSMAP_WRITE};
use crate::spec::*;
use crate::transport::{Error, StandardTransport};
use crate::utils::{VolatileCell, align_down};
use crate::utils::{align_down, VolatileCell};
pub struct Device<'a> {
pub transport: Arc<StandardTransport<'a>>,
@@ -186,11 +186,7 @@ pub fn probe_device<'a>(pcid_handle: &mut PcidServerHandle) -> Result<Device<'a>
let size = offset + capability.length as usize;
let addr = syscall::physmap(
aligned_addr,
size,
PHYSMAP_WRITE | PHYSMAP_NO_CACHE,
)?;
let addr = syscall::physmap(aligned_addr, size, PHYSMAP_WRITE | PHYSMAP_NO_CACHE)?;
addr + offset
};
+18 -4
View File
@@ -86,6 +86,12 @@ impl<'a> Future for PendingRequest<'a> {
.insert(self.first_descriptor, cx.waker().clone());
let used_head = self.queue.used.head_index();
if used_head == self.queue.used_head.load(Ordering::SeqCst) {
// No new requests have been completed.
return Poll::Pending;
}
let used_element = self.queue.used.get_element_at((used_head - 1) as usize);
let written = used_element.written.get();
@@ -109,6 +115,8 @@ impl<'a> Future for PendingRequest<'a> {
.lock()
.unwrap()
.remove(&self.first_descriptor);
self.queue.used_head.store(used_head, Ordering::SeqCst);
return Poll::Ready(written);
} else {
return Poll::Pending;
@@ -122,6 +130,7 @@ pub struct Queue<'a> {
pub used: Used<'a>,
pub descriptor: Dma<[Descriptor]>,
pub available: Available<'a>,
pub used_head: AtomicU16,
notification_bell: &'a mut AtomicU16,
descriptor_stack: crossbeam_queue::SegQueue<u16>,
@@ -148,6 +157,7 @@ impl<'a> Queue<'a> {
waker: Mutex::new(std::collections::HashMap::new()),
queue_index,
descriptor_stack,
used_head: AtomicU16::new(0),
sref: sref.clone(),
})
}
@@ -190,8 +200,6 @@ impl<'a> Queue<'a> {
self.notification_bell
.store(self.queue_index, Ordering::SeqCst);
assert_eq!(self.used.flags(), 0);
PendingRequest {
queue: self.sref.upgrade().unwrap(),
first_descriptor: first_descriptor as u32,
@@ -244,7 +252,7 @@ impl<'a> Available<'a> {
self.ring
.elements
.as_slice(self.queue_size)
.get(index % 256)
.get(index % self.queue_size)
.expect("virtio-core::available: index out of bounds")
}
}
@@ -310,7 +318,7 @@ impl<'a> Used<'a> {
self.ring
.elements
.as_slice(self.queue_size)
.get(index % 256)
.get(index % self.queue_size)
.expect("virtio-core::used: index out of bounds")
}
}
@@ -372,6 +380,11 @@ impl<'a> StandardTransport<'a> {
})
}
pub fn reset(&self) {
let mut common = self.common.lock().unwrap();
common.device_status.set(DeviceStatusFlags::empty());
}
pub fn check_device_feature(&self, feature: u32) -> bool {
let mut common = self.common.lock().unwrap();
@@ -470,6 +483,7 @@ impl<'a> StandardTransport<'a> {
event_queue
.add(irq_fd, move |_| -> Result<Option<usize>, std::io::Error> {
// Wake up the tasks waiting on the queue.
for (_, task) in queue_copy.waker.lock().unwrap().iter() {
task.wake_by_ref();
}
+12 -1
View File
@@ -1,7 +1,7 @@
use core::cell::UnsafeCell;
use core::fmt::Debug;
use core::marker::PhantomData;
#[derive(Debug)]
#[repr(C)]
pub struct VolatileCell<T> {
value: UnsafeCell<T>,
@@ -28,6 +28,17 @@ impl<T: Copy> VolatileCell<T> {
}
}
impl<T> Debug for VolatileCell<T>
where
T: Debug + Copy,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VolatileCell")
.field("value", &self.get())
.finish()
}
}
unsafe impl<T> Sync for VolatileCell<T> {}
#[repr(C)]
+8
View File
@@ -6,8 +6,16 @@ authors = ["Anhad Singh <andypython@protonmail.com>"]
[dependencies]
log = "0.4"
static_assertions = "1.1.0"
futures = { version = "0.3.28", features = ["executor"] }
anyhow = "1.0.71"
paste = "1.0.13"
virtio-core = { path = "../virtio-core" }
pcid = { path = "../pcid" }
inputd = { path = "../inputd" }
redox-daemon = "0.1"
redox_syscall = "0.3"
orbclient = "0.3.27"
spin = "0.9.8"
+432 -4
View File
@@ -1,7 +1,400 @@
use pcid_interface::PcidServerHandle;
use virtio_core::transport::Error;
//! `virtio-gpu` is a virtio based graphics adapter. It can operate in 2D mode and in 3D mode.
//!
//! XXX: 3D mode will offload rendering ops to the host gpu and therefore requires a GPU with 3D support
//! on the host machine.
fn deamon(deamon: redox_daemon::Daemon) -> Result<(), Error> {
// Notes for the future:
//
// `virtio-gpu` 2D acceleration is just blitting. 3D acceleration has 2 kinds:
// - virgl - OpenGL
// - venus - Vulkan
//
// The Venus driver requires support for the following from the `virtio-gpu` kernel driver:
// - VIRTGPU_PARAM_3D_FEATURES
// - VIRTGPU_PARAM_CAPSET_QUERY_FIX
// - VIRTGPU_PARAM_RESOURCE_BLOB
// - VIRTGPU_PARAM_HOST_VISIBLE
// - VIRTGPU_PARAM_CROSS_DEVICE
// - VIRTGPU_PARAM_CONTEXT_INIT
//
// cc https://docs.mesa3d.org/drivers/venus.html
// cc https://docs.mesa3d.org/drivers/virgl.html
#![feature(int_roundings)]
use std::cell::UnsafeCell;
use std::fs::File;
use std::io::{Read, Write};
use pcid_interface::PcidServerHandle;
use syscall::{Packet, SchemeMut};
use virtio_core::utils::VolatileCell;
use virtio_core::MSIX_PRIMARY_VECTOR;
mod scheme;
// const VIRTIO_GPU_EVENT_DISPLAY: u32 = 1 << 0;
const VIRTIO_GPU_MAX_SCANOUTS: usize = 16;
macro_rules! make_getter_setter {
($($field:ident: $return_ty:ty),*) => {
$(
pub fn $field(&self) -> $return_ty {
self.$field.get()
}
paste::item! {
pub fn [<set_ $field>](&mut self, value: $return_ty) {
self.$field.set(value)
}
}
)*
};
(@$field:ident: $return_ty:ty) => {
pub fn $field(&mut self, value: $return_ty) {
self.$field.set(value)
}
};
}
#[repr(C)]
pub struct GpuConfig {
/// Signals pending events to the driver.
pub events_read: VolatileCell<u32>, // read-only
/// Clears pending events in the device (write-to-clear).
pub events_clear: VolatileCell<u32>, // write-only
pub num_scanouts: VolatileCell<u32>,
pub num_capsets: VolatileCell<u32>,
}
impl GpuConfig {
#[inline]
pub fn num_scanouts(&self) -> u32 {
self.num_scanouts.get()
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(u32)]
pub enum CommandTy {
Undefined = 0,
// 2D commands
GetDisplayInfo = 0x0100,
ResourceCreate2d,
ResourceUnref,
SetScanout,
ResourceFlush,
TransferToHost2d,
ResourceAttachBacking,
ResourceDetachBacking,
GetCapsetInfo,
GetCapset,
GetEdid,
ResourceAssignUuid,
ResourceCreateBlob,
SetScanoutBlob,
// 3D commands
CtxCreate = 0x0200,
CtxDestroy,
CtxAttachResource,
CtxDetachResource,
ResourceCreate3d,
TransferToHost3d,
TransferFromHost3d,
Submit3d,
ResourceMapBlob,
ResourceUnmapBlob,
// cursor commands
UpdateCursor = 0x0300,
MoveCursor,
// success responses
RespOkNodata = 0x1100,
RespOkDisplayInfo,
RespOkCapsetInfo,
RespOkCapset,
RespOkEdid,
RespOkResourceUuid,
RespOkMapInfo,
// error responses
RespErrUnspec = 0x1200,
RespErrOutOfMemory,
RespErrInvalidScanoutId,
RespErrInvalidResourceId,
RespErrInvalidContextId,
RespErrInvalidParameter,
}
static_assertions::const_assert_eq!(core::mem::size_of::<CommandTy>(), 4);
#[derive(Debug)]
#[repr(C)]
pub struct ControlHeader {
pub ty: VolatileCell<CommandTy>,
pub flags: VolatileCell<u32>,
pub fence_id: VolatileCell<u64>,
pub ctx_id: VolatileCell<u32>,
pub ring_index: VolatileCell<u8>,
padding: [u8; 3],
}
impl ControlHeader {
pub fn with_ty(ty: CommandTy) -> Self {
Self {
ty: VolatileCell::new(ty),
..Default::default()
}
}
}
impl Default for ControlHeader {
fn default() -> Self {
Self {
ty: VolatileCell::new(CommandTy::Undefined),
flags: VolatileCell::new(0),
fence_id: VolatileCell::new(0),
ctx_id: VolatileCell::new(0),
ring_index: VolatileCell::new(0),
padding: [0; 3],
}
}
}
#[derive(Debug, Clone)]
#[repr(C)]
pub struct GpuRect {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
impl GpuRect {
pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
Self {
x,
y,
width,
height,
}
}
}
#[derive(Debug)]
#[repr(C)]
pub struct DisplayInfo {
rect: UnsafeCell<GpuRect>,
pub enabled: VolatileCell<u32>,
pub flags: VolatileCell<u32>,
}
impl DisplayInfo {
pub fn rect(&self) -> &GpuRect {
// SAFETY: We never give out mutable references to `self.rect`.
unsafe { &*self.rect.get() }
}
}
#[derive(Debug)]
#[repr(C)]
pub struct GetDisplayInfo {
pub header: ControlHeader,
pub display_info: [DisplayInfo; VIRTIO_GPU_MAX_SCANOUTS],
}
impl Default for GetDisplayInfo {
fn default() -> Self {
Self {
header: ControlHeader {
ty: VolatileCell::new(CommandTy::GetDisplayInfo),
..Default::default()
},
display_info: unsafe { core::mem::zeroed() },
}
}
}
#[derive(Debug, Copy, Clone)]
#[repr(u32)]
pub enum ResourceFormat {
Unknown = 0,
Bgrx = 2,
Xrgb = 4,
}
#[derive(Debug)]
#[repr(C)]
pub struct ResourceCreate2d {
pub header: ControlHeader,
resource_id: VolatileCell<u32>,
format: VolatileCell<ResourceFormat>,
width: VolatileCell<u32>,
height: VolatileCell<u32>,
}
impl ResourceCreate2d {
make_getter_setter!(resource_id: u32, format: ResourceFormat, width: u32, height: u32);
}
impl Default for ResourceCreate2d {
fn default() -> Self {
Self {
header: ControlHeader {
ty: VolatileCell::new(CommandTy::ResourceCreate2d),
..Default::default()
},
resource_id: VolatileCell::new(0),
format: VolatileCell::new(ResourceFormat::Unknown),
width: VolatileCell::new(0),
height: VolatileCell::new(0),
}
}
}
#[derive(Debug)]
#[repr(C)]
pub struct MemEntry {
pub address: u64,
pub length: u32,
pub padding: u32,
}
#[derive(Debug)]
#[repr(C)]
pub struct AttachBacking {
pub header: ControlHeader,
pub resource_id: u32,
pub num_entries: u32,
}
impl AttachBacking {
pub fn new(resource_id: u32, num_entries: u32) -> Self {
Self {
header: ControlHeader::with_ty(CommandTy::ResourceAttachBacking),
resource_id,
num_entries,
}
}
}
#[derive(Debug)]
#[repr(C)]
pub struct DetachBacking {
pub header: ControlHeader,
pub resource_id: u32,
pub padding: u32,
}
impl DetachBacking {
pub fn new(resource_id: u32) -> Self {
Self {
header: ControlHeader::with_ty(CommandTy::ResourceDetachBacking),
resource_id,
padding: 0,
}
}
}
#[derive(Debug)]
#[repr(C)]
pub struct ResourceFlush {
pub header: ControlHeader,
pub rect: GpuRect,
pub resource_id: u32,
pub padding: u32,
}
impl ResourceFlush {
pub fn new(resource_id: u32, rect: GpuRect) -> Self {
Self {
header: ControlHeader::with_ty(CommandTy::ResourceFlush),
rect,
resource_id,
padding: 0,
}
}
}
#[derive(Debug)]
#[repr(C)]
pub struct ResourceUnref {
pub header: ControlHeader,
pub resource_id: u32,
pub padding: u32,
}
impl ResourceUnref {
pub fn new(resource_id: u32) -> Self {
Self {
header: ControlHeader::with_ty(CommandTy::ResourceUnref),
resource_id,
padding: 0,
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct SetScanout {
pub header: ControlHeader,
pub rect: GpuRect,
pub scanout_id: u32,
pub resource_id: u32,
}
impl SetScanout {
pub fn new(scanout_id: u32, resource_id: u32, rect: GpuRect) -> Self {
Self {
header: ControlHeader {
ty: VolatileCell::new(CommandTy::SetScanout),
..Default::default()
},
rect,
scanout_id,
resource_id,
}
}
}
#[derive(Debug)]
#[repr(C)]
pub struct XferToHost2d {
pub header: ControlHeader,
pub rect: GpuRect,
pub offset: u64,
pub resource_id: u32,
pub padding: u32,
}
impl XferToHost2d {
pub fn new(resource_id: u32, rect: GpuRect) -> Self {
Self {
header: ControlHeader {
ty: VolatileCell::new(CommandTy::TransferToHost2d),
..Default::default()
},
rect,
resource_id,
offset: 0,
padding: 0,
}
}
}
fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
let mut pcid_handle = PcidServerHandle::connect_default()?;
// Double check that we have the right device.
@@ -13,7 +406,42 @@ fn deamon(deamon: redox_daemon::Daemon) -> Result<(), Error> {
log::info!("virtio-gpu: initiating startup sequence :^)");
let device = virtio_core::probe_device(&mut pcid_handle)?;
loop {}
let config = unsafe { &mut *(device.device_space as *mut GpuConfig) };
// Negotiate features.
device.transport.finalize_features();
// Queue for sending control commands.
let control_queue = device
.transport
.setup_queue(MSIX_PRIMARY_VECTOR, &device.irq_handle)?;
// Queue for sending cursor updates.
let cursor_queue = device
.transport
.setup_queue(MSIX_PRIMARY_VECTOR, &device.irq_handle)?;
device.transport.run_device();
deamon.ready().unwrap();
let mut socket_file = File::create(":display/virtio-gpu")?;
let mut scheme = futures::executor::block_on(scheme::Scheme::new(
config,
control_queue.clone(),
cursor_queue.clone(),
device.transport.clone(),
))?;
loop {
let mut packet = Packet::default();
socket_file
.read(&mut packet)
.expect("virtio-gpud: failed to read disk scheme");
scheme.handle(&mut packet);
socket_file
.write(&packet)
.expect("virtio-gpud: failed to read disk scheme");
}
}
fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! {
+388
View File
@@ -0,0 +1,388 @@
use std::collections::BTreeMap;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use inputd::Damage;
use syscall::{Dma, Error as SysError, SchemeMut, EINVAL};
use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags};
use virtio_core::transport::{Error, Queue, StandardTransport};
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 {
x: self.x as u32,
y: self.y as u32,
width: self.width as u32,
height: self.height as u32,
}
}
}
pub struct Display<'a> {
control_queue: Arc<Queue<'a>>,
cursor_queue: Arc<Queue<'a>>,
transport: Arc<StandardTransport<'a>>,
// TODO(andypython): Remove the need for the spin crate after the `once_cell`
// API is stabilized.
mapped: spin::Once<usize>,
width: u32,
height: u32,
resource_id: u32,
id: usize,
}
impl<'a> Display<'a> {
pub fn new(
control_queue: Arc<Queue<'a>>,
cursor_queue: Arc<Queue<'a>>,
transport: Arc<StandardTransport<'a>>,
id: usize,
) -> Self {
Self {
control_queue,
cursor_queue,
mapped: spin::Once::new(),
width: 1920,
height: 1080,
transport,
id,
resource_id: RESOURCE_ALLOC.fetch_add(1, Ordering::SeqCst),
}
}
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())
}
async fn send_request<T>(&self, request: Dma<T>) -> Result<Dma<ControlHeader>, Error> {
let header = Dma::new(ControlHeader::default())?;
let command = ChainBuilder::new()
.chain(Buffer::new(&request))
.chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY))
.build();
self.control_queue.send(command).await;
Ok(header)
}
async fn flush_resource(&self, flush: ResourceFlush) -> Result<(), Error> {
let header = self.send_request(Dma::new(flush)?).await?;
assert_eq!(header.ty.get(), CommandTy::RespOkNodata);
Ok(())
}
async fn map_screen(&self, offset: usize) -> Result<usize, Error> {
if let Some(mapped) = self.mapped.get() {
return Ok(mapped + offset);
}
// 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);
self.send_request(request).await?;
// Allocate a framebuffer from guest ram, and attach it as backing storage to the
// resource just created, using `VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING`. Scatter
// lists are supported, so the framebuffer doesnt need to be contignous in guest
// physical memory.
let bpp = 32;
let fb_size = (self.width as usize * self.height as usize * bpp / 8)
.next_multiple_of(syscall::PAGE_SIZE);
let address = unsafe { syscall::physalloc(fb_size) }? as u64;
let mapped = unsafe {
syscall::physmap(
address as usize,
fb_size,
syscall::PhysmapFlags::PHYSMAP_WRITE,
)
}?;
unsafe {
core::ptr::write_bytes(mapped as *mut u8, 255, fb_size);
}
let entry = Dma::new(MemEntry {
address,
length: fb_size as u32,
padding: 0,
})?;
let attach_request = Dma::new(AttachBacking::new(self.resource_id, 1))?;
let header = Dma::new(ControlHeader::default())?;
let command = ChainBuilder::new()
.chain(Buffer::new(&attach_request))
.chain(Buffer::new(&entry))
.chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY))
.build();
self.control_queue.send(command).await;
assert_eq!(header.ty.get(), CommandTy::RespOkNodata);
let scanout_request = Dma::new(SetScanout::new(
self.id as u32,
self.resource_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.mapped.call_once(|| mapped);
Ok(mapped + offset)
}
/// If `damage` is `None`, the entire screen is flushed.
async fn flush(&self, damage: Option<&Damage>) -> Result<(), Error> {
let damage = if let Some(damage) = damage {
damage.into()
} else {
GpuRect {
x: 0,
y: 0,
width: self.width,
height: self.height,
}
};
let req = Dma::new(XferToHost2d::new(
self.resource_id,
GpuRect {
x: 0,
y: 0,
width: self.width,
height: self.height,
},
))?;
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?;
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);
let request = Dma::new(ResourceUnref::new(self.resource_id))?;
let header = self.send_request(request).await?;
assert_eq!(header.ty.get(), CommandTy::RespOkNodata);
// Go back to legacy mode.
self.transport.reset();
Ok(())
}
}
pub struct Scheme<'a> {
handles: BTreeMap<usize /* file descriptor */, Arc<Display<'a>>>,
inputd_handle: inputd::Handle,
displays: Vec<Arc<Display<'a>>>,
}
impl<'a> Scheme<'a> {
pub async fn new(
config: &'a mut GpuConfig,
control_queue: Arc<Queue<'a>>,
cursor_queue: Arc<Queue<'a>>,
transport: Arc<StandardTransport<'a>>,
) -> Result<Scheme<'a>, Error> {
let displays = Self::probe(
control_queue.clone(),
cursor_queue.clone(),
transport.clone(),
config,
)
.await?;
Ok(Self {
handles: BTreeMap::new(),
inputd_handle: inputd::Handle::new("virtio-gpu").unwrap(),
displays,
})
}
async fn probe(
control_queue: Arc<Queue<'a>>,
cursor_queue: Arc<Queue<'a>>,
transport: Arc<StandardTransport<'a>>,
config: &GpuConfig,
) -> Result<Vec<Arc<Display<'a>>>, 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() {
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));
}
Ok(result)
}
async fn get_display_info(control_queue: Arc<Queue<'a>>) -> Result<Dma<GetDisplayInfo>, Error> {
let header = Dma::new(ControlHeader {
ty: VolatileCell::new(CommandTy::GetDisplayInfo),
..Default::default()
})?;
let response = Dma::new(GetDisplayInfo::default())?;
let command = ChainBuilder::new()
.chain(Buffer::new(&header))
.chain(Buffer::new(&response).flags(DescriptorFlags::WRITE_ONLY))
.build();
control_queue.send(command).await;
assert!(response.header.ty.get() == CommandTy::RespOkDisplayInfo);
Ok(response)
}
}
impl<'a> SchemeMut for Scheme<'a> {
fn open(&mut self, path: &str, _flags: usize, _uid: u32, _gid: u32) -> syscall::Result<usize> {
dbg!(&path);
let mut parts = path.split('/');
let mut screen = parts.next().unwrap_or("").split('.');
static YES: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
if YES.load(Ordering::SeqCst) {
YES.store(false, Ordering::SeqCst);
}
let fd = screen.next().unwrap_or("").parse::<usize>().unwrap();
if path.contains("deactivate") {
log::info!("virtio-gpu: deactivating display at file #{}", fd);
let handle = self.handles.get(&fd).unwrap();
futures::executor::block_on(handle.detach()).unwrap();
return Ok(0);
}
let id = screen.next().unwrap_or("").parse::<usize>().unwrap_or(0);
dbg!(&id);
let display = self.displays.get(id).ok_or(SysError::new(EINVAL))?;
let fd = self.inputd_handle.register().unwrap();
self.handles.insert(fd, display.clone());
Ok(fd)
}
fn dup(&mut self, _old_id: usize, _buf: &[u8]) -> syscall::Result<usize> {
todo!()
}
fn fevent(
&mut self,
_id: usize,
_flags: syscall::EventFlags,
) -> syscall::Result<syscall::EventFlags> {
log::warn!("fevent is a stub!");
Ok(syscall::EventFlags::empty())
}
fn fpath(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result<usize> {
let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?;
let bytes_copied = futures::executor::block_on(handle.get_fpath(buf)).unwrap();
Ok(bytes_copied)
}
fn fmap_old(&mut self, id: usize, map: &syscall::OldMap) -> syscall::Result<usize> {
self.fmap(
id,
&syscall::Map {
offset: map.offset,
size: map.size,
flags: map.flags,
address: 0,
},
)
}
fn fmap(&mut self, id: usize, map: &syscall::Map) -> syscall::Result<usize> {
let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?;
Ok(futures::executor::block_on(handle.map_screen(map.offset)).unwrap())
}
fn fsync(&mut self, id: usize) -> syscall::Result<usize> {
let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?;
futures::executor::block_on(handle.flush(None)).unwrap();
Ok(0)
}
fn read(&mut self, _id: usize, _buf: &mut [u8]) -> syscall::Result<usize> {
// TODO: figure out how to get input lol
log::warn!("virtio_gpu::read is a stub!");
Ok(0)
}
fn write(&mut self, id: usize, buf: &[u8]) -> syscall::Result<usize> {
let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?;
let damages = unsafe {
core::slice::from_raw_parts(
buf.as_ptr() as *const Damage,
buf.len() / core::mem::size_of::<Damage>(),
)
};
for damage in damages {
futures::executor::block_on(handle.flush(Some(damage))).unwrap();
}
Ok(buf.len())
}
fn seek(&mut self, _id: usize, _pos: isize, _whence: usize) -> syscall::Result<isize> {
todo!()
}
fn close(&mut self, _id: usize) -> syscall::Result<usize> {
Ok(0)
}
}
+7
View File
@@ -0,0 +1,7 @@
[[drivers]]
name = "virtio-net"
class = 2
vendor = 6900
device = 4096
command = ["virtio-netd"]
use_channel = true