Merge branch 'core' into 'master'
drivers: add `virtio-gpu`! See merge request redox-os/drivers!96
This commit is contained in:
Generated
+33
-9
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ members = [
|
||||
"usbctl",
|
||||
"usbhidd",
|
||||
"usbscsid",
|
||||
"inputd",
|
||||
|
||||
"virtio-blkd",
|
||||
"virtio-netd",
|
||||
|
||||
+1
-1
@@ -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();
|
||||
|
||||
@@ -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
@@ -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"]
|
||||
|
||||
@@ -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"
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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 == ¤t.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
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
@@ -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) -> ! {
|
||||
|
||||
@@ -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 doesn’t 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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[[drivers]]
|
||||
name = "virtio-net"
|
||||
class = 2
|
||||
vendor = 6900
|
||||
device = 4096
|
||||
command = ["virtio-netd"]
|
||||
use_channel = true
|
||||
Reference in New Issue
Block a user