diff --git a/Cargo.lock b/Cargo.lock index 3965b23e13..0a9ffa5805 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 5182ebc37b..c7c25c1e42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = [ "usbctl", "usbhidd", "usbscsid", + "inputd", "virtio-blkd", "virtio-netd", diff --git a/bgad/src/main.rs b/bgad/src/main.rs index 2d5387aeae..781e68ae8f 100644 --- a/bgad/src/main.rs +++ b/bgad/src/main.rs @@ -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(); diff --git a/fmt.sh b/fmt.sh index b0b137080b..c9f29df18f 100755 --- a/fmt.sh +++ b/fmt.sh @@ -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 diff --git a/initfs.toml b/initfs.toml index 631768911d..d648d466ad 100644 --- a/initfs.toml +++ b/initfs.toml @@ -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"] diff --git a/inputd/Cargo.toml b/inputd/Cargo.toml new file mode 100644 index 0000000000..dfc537236e --- /dev/null +++ b/inputd/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "inputd" +version = "0.1.0" +edition = "2021" +authors = ["Anhad Singh "] + +[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" diff --git a/inputd/src/lib.rs b/inputd/src/lib.rs new file mode 100644 index 0000000000..9ad4ae5876 --- /dev/null +++ b/inputd/src/lib.rs @@ -0,0 +1,25 @@ +use std::fs::File; +use std::io::{Error, Read}; + +pub struct Handle(File); + +impl Handle { + pub fn new>(device_name: S) -> Result { + 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 { + Ok(dbg!(self.0.read(&mut [])?)) + } +} + +#[repr(packed)] +pub struct Damage { + pub x: i32, + pub y: i32, + pub width: i32, + pub height: i32, +} diff --git a/inputd/src/main.rs b/inputd/src/main.rs new file mode 100644 index 0000000000..a963789c06 --- /dev/null +++ b/inputd/src/main.rs @@ -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, + notified: bool, + }, + Device(Arc), +} + +impl Handle { + pub fn is_producer(&self) -> bool { + matches!(self, Handle::Producer) + } +} + +struct InputScheme { + handles: BTreeMap, + + next_id: AtomicUsize, + next_vt_id: AtomicUsize, + + vts: BTreeMap>, + super_key: bool, + active_vt: Option<(Arc, 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 { + 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 { + 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 { + 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::(), + ) + }; + + 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 { + 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 { + 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"); +} diff --git a/ps2d/src/main.rs b/ps2d/src/main.rs index 5a845e415f..f9cdba3b6b 100644 --- a/ps2d/src/main.rs +++ b/ps2d/src/main.rs @@ -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) diff --git a/usbhidd/src/main.rs b/usbhidd/src/main.rs index df3f6d60ca..bfefd2f798 100644 --- a/usbhidd/src/main.rs +++ b/usbhidd/src/main.rs @@ -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; diff --git a/vboxd/src/main.rs b/vboxd/src/main.rs index 4ad50c5598..c42c8da424 100644 --- a/vboxd/src/main.rs +++ b/vboxd/src/main.rs @@ -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) { diff --git a/vesad/Cargo.toml b/vesad/Cargo.toml index a77d65c2e6..9b247aabe2 100644 --- a/vesad/Cargo.toml +++ b/vesad/Cargo.toml @@ -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 = [] diff --git a/vesad/src/main.rs b/vesad/src/main.rs index 10ae4df44a..3d7cf98f4c 100644 --- a/vesad/src/main.rs +++ b/vesad/src/main.rs @@ -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, 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); diff --git a/vesad/src/scheme.rs b/vesad/src/scheme.rs index d50c12902b..493eb7888e 100644 --- a/vesad/src/scheme.rs +++ b/vesad/src/scheme.rs @@ -38,10 +38,13 @@ pub struct DisplayScheme { next_id: usize, pub handles: BTreeMap, super_key: bool, + inputd_handle: inputd::Handle, } impl DisplayScheme { pub fn new(mut framebuffers: Vec, 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::>>::new(); - let mut vt_i = 1; for &vt_type in spec.iter() { let mut screens = BTreeMap::>::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::().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 + ); } } diff --git a/virtio-core/src/probe.rs b/virtio-core/src/probe.rs index f627de9e68..58e9466659 100644 --- a/virtio-core/src/probe.rs +++ b/virtio-core/src/probe.rs @@ -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>, @@ -186,11 +186,7 @@ pub fn probe_device<'a>(pcid_handle: &mut PcidServerHandle) -> Result 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 }; diff --git a/virtio-core/src/transport.rs b/virtio-core/src/transport.rs index ae94c70316..a1a913d3fd 100644 --- a/virtio-core/src/transport.rs +++ b/virtio-core/src/transport.rs @@ -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, @@ -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, std::io::Error> { + // Wake up the tasks waiting on the queue. for (_, task) in queue_copy.waker.lock().unwrap().iter() { task.wake_by_ref(); } diff --git a/virtio-core/src/utils.rs b/virtio-core/src/utils.rs index 85d39e54af..6f71dac4ea 100644 --- a/virtio-core/src/utils.rs +++ b/virtio-core/src/utils.rs @@ -1,7 +1,7 @@ use core::cell::UnsafeCell; +use core::fmt::Debug; use core::marker::PhantomData; -#[derive(Debug)] #[repr(C)] pub struct VolatileCell { value: UnsafeCell, @@ -28,6 +28,17 @@ impl VolatileCell { } } +impl Debug for VolatileCell +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 Sync for VolatileCell {} #[repr(C)] diff --git a/virtio-gpud/Cargo.toml b/virtio-gpud/Cargo.toml index 17fdf4384a..c06c18d87e 100644 --- a/virtio-gpud/Cargo.toml +++ b/virtio-gpud/Cargo.toml @@ -6,8 +6,16 @@ authors = ["Anhad Singh "] [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" diff --git a/virtio-gpud/src/main.rs b/virtio-gpud/src/main.rs index 140c73ac2c..bbe5675688 100644 --- a/virtio-gpud/src/main.rs +++ b/virtio-gpud/src/main.rs @@ -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 [](&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, // read-only + /// Clears pending events in the device (write-to-clear). + pub events_clear: VolatileCell, // write-only + + pub num_scanouts: VolatileCell, + pub num_capsets: VolatileCell, +} + +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::(), 4); + +#[derive(Debug)] +#[repr(C)] +pub struct ControlHeader { + pub ty: VolatileCell, + pub flags: VolatileCell, + pub fence_id: VolatileCell, + pub ctx_id: VolatileCell, + pub ring_index: VolatileCell, + 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, + pub enabled: VolatileCell, + pub flags: VolatileCell, +} + +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, + format: VolatileCell, + width: VolatileCell, + height: VolatileCell, +} + +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) -> ! { diff --git a/virtio-gpud/src/scheme.rs b/virtio-gpud/src/scheme.rs new file mode 100644 index 0000000000..b0490c8cce --- /dev/null +++ b/virtio-gpud/src/scheme.rs @@ -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 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>, + cursor_queue: Arc>, + transport: Arc>, + + // TODO(andypython): Remove the need for the spin crate after the `once_cell` + // API is stabilized. + mapped: spin::Once, + + width: u32, + height: u32, + + resource_id: u32, + id: usize, +} + +impl<'a> Display<'a> { + pub fn new( + control_queue: Arc>, + cursor_queue: Arc>, + transport: Arc>, + 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 { + 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(&self, request: Dma) -> Result, 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 { + 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>>, + inputd_handle: inputd::Handle, + displays: Vec>>, +} + +impl<'a> Scheme<'a> { + pub async fn new( + config: &'a mut GpuConfig, + control_queue: Arc>, + cursor_queue: Arc>, + transport: Arc>, + ) -> Result, 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>, + cursor_queue: Arc>, + transport: Arc>, + config: &GpuConfig, + ) -> Result>>, 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>) -> Result, 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 { + 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::().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::().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 { + todo!() + } + + fn fevent( + &mut self, + _id: usize, + _flags: syscall::EventFlags, + ) -> syscall::Result { + log::warn!("fevent is a stub!"); + Ok(syscall::EventFlags::empty()) + } + + fn fpath(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result { + 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 { + 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 { + 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 { + 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 { + // 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 { + 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::(), + ) + }; + + 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 { + todo!() + } + + fn close(&mut self, _id: usize) -> syscall::Result { + Ok(0) + } +} diff --git a/virtio-netd/config.toml b/virtio-netd/config.toml new file mode 100644 index 0000000000..93949ce905 --- /dev/null +++ b/virtio-netd/config.toml @@ -0,0 +1,7 @@ +[[drivers]] +name = "virtio-net" +class = 2 +vendor = 6900 +device = 4096 +command = ["virtio-netd"] +use_channel = true