virtio-gpu: handling GPU resets
Signed-off-by: Anhad Singh <andypython@protonmail.com>
This commit is contained in:
Generated
+1
@@ -1908,6 +1908,7 @@ dependencies = [
|
||||
name = "virtio-netd"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"common",
|
||||
"futures",
|
||||
"log",
|
||||
"netutils",
|
||||
|
||||
+34
-1
@@ -1,11 +1,12 @@
|
||||
use std::fs::File;
|
||||
use std::io::{Error, Read};
|
||||
use std::mem;
|
||||
|
||||
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());
|
||||
let path = format!("input:handle/display/{}", device_name.into());
|
||||
Ok(Self(File::open(path)?))
|
||||
}
|
||||
|
||||
@@ -16,6 +17,38 @@ impl Handle {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum CommandTy {
|
||||
Unknown = 0,
|
||||
|
||||
Activate,
|
||||
Deactivate,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Command {
|
||||
pub ty: CommandTy,
|
||||
pub value: usize,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn new(ty: CommandTy, value: usize) -> Self {
|
||||
Self { ty, value }
|
||||
}
|
||||
|
||||
/// ## Panics
|
||||
/// This function panics if the buffer is not `sizeof::<Command>()` bytes wide.
|
||||
pub fn parse<'a>(buffer: &'a [u8]) -> &'a Command {
|
||||
assert_eq!(buffer.len(), core::mem::size_of::<Command>());
|
||||
unsafe { &*(buffer.as_ptr() as *const Command) }
|
||||
}
|
||||
|
||||
pub fn into_bytes(self) -> [u8; mem::size_of::<Command>()] {
|
||||
unsafe { core::mem::transmute(self) }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct Damage {
|
||||
pub x: i32,
|
||||
|
||||
+52
-27
@@ -13,10 +13,11 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use inputd::{Command, CommandTy};
|
||||
use orbclient::{Event, EventOption};
|
||||
use syscall::{Error as SysError, EventFlags, Packet, SchemeMut, EINVAL};
|
||||
|
||||
@@ -26,6 +27,7 @@ enum Handle {
|
||||
events: EventFlags,
|
||||
pending: Vec<u8>,
|
||||
notified: bool,
|
||||
vt: usize,
|
||||
},
|
||||
Device(Arc<String>),
|
||||
}
|
||||
@@ -66,17 +68,30 @@ impl SchemeMut for InputScheme {
|
||||
let mut path_parts = path.split('/');
|
||||
|
||||
let command = path_parts.next().ok_or(SysError::new(EINVAL))?;
|
||||
let fd = self.next_id.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
let handle_ty = match command {
|
||||
"producer" => Handle::Producer,
|
||||
"consumer" => Handle::Consumer {
|
||||
events: EventFlags::empty(),
|
||||
pending: Vec::new(),
|
||||
notified: false,
|
||||
},
|
||||
"consumer" => {
|
||||
let vt = if let Some((_, _, vt)) = self.active_vt {
|
||||
vt
|
||||
} else {
|
||||
// No one is currently set as active, so we assume that the last allocated
|
||||
// one will be willing to take this consumer.
|
||||
self.next_vt_id.load(Ordering::SeqCst) - 1
|
||||
};
|
||||
|
||||
dbg!(vt);
|
||||
Handle::Consumer {
|
||||
events: EventFlags::empty(),
|
||||
pending: Vec::new(),
|
||||
notified: false,
|
||||
vt,
|
||||
}
|
||||
}
|
||||
"handle" => {
|
||||
let value = path_parts.next().ok_or(SysError::new(EINVAL))?;
|
||||
Handle::Device(Arc::new(value.to_string()))
|
||||
let value = path_parts.collect::<Vec<_>>().join("/");
|
||||
Handle::Device(Arc::new(value))
|
||||
}
|
||||
|
||||
_ => unreachable!("inputd: invalid path {path}"),
|
||||
@@ -84,9 +99,7 @@ impl SchemeMut for InputScheme {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -156,36 +169,34 @@ impl SchemeMut for InputScheme {
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
let deactivate = |vt, current| {
|
||||
// Drop the old active VT first.
|
||||
//
|
||||
// TODO(andypython): This can be drastically improved by introducting something
|
||||
// like ioctl() to the display scheme.
|
||||
let deactivate = format!("display/{}:{current}/deactivate", vt);
|
||||
let _ = File::open(&deactivate);
|
||||
let deactivate = |file: &mut File, id: usize| -> io::Result<()> {
|
||||
let command = Command::new(CommandTy::Deactivate, id).into_bytes();
|
||||
file.write(&command)?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
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((vt, _, current)) = self.active_vt.as_ref() {
|
||||
if let Some(vt) = self.vts.get(&new_active).cloned() {
|
||||
if let Some((_, file, current)) = self.active_vt.as_mut() {
|
||||
// If the VT is already active, don't do anything.
|
||||
if new_active == *current {
|
||||
continue;
|
||||
}
|
||||
|
||||
deactivate(vt, *current);
|
||||
deactivate(file, *current).unwrap();
|
||||
} else {
|
||||
let id = self.next_vt_id.load(Ordering::SeqCst) - 1;
|
||||
let vt = self.vts.get(&id).unwrap();
|
||||
let mut vt = File::open(self.vts.get_mut(&id).unwrap().as_ref()).unwrap();
|
||||
|
||||
deactivate(vt, id);
|
||||
deactivate(&mut vt, id).unwrap();
|
||||
}
|
||||
|
||||
// Result: display/virtio-gpu:3/acvtivate
|
||||
let activate = format!("display/{vt}:{new_active}/activate");
|
||||
log::info!("inputd: switching to VT #{new_active} (`{activate}`)");
|
||||
log::info!("inputd: switching to VT #{new_active} (`{vt}`)");
|
||||
|
||||
let mut file = File::open(vt.as_ref()).unwrap();
|
||||
file.write(&Command::new(CommandTy::Activate, new_active).into_bytes())
|
||||
.unwrap();
|
||||
|
||||
let file = File::open(&activate).unwrap();
|
||||
self.active_vt = Some((vt.clone(), file, new_active));
|
||||
} else {
|
||||
log::warn!("inputd: switch to non-existent VT #{new_active} was requested");
|
||||
@@ -269,12 +280,26 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
|
||||
events,
|
||||
pending,
|
||||
ref mut notified,
|
||||
vt,
|
||||
} = handle
|
||||
{
|
||||
if pending.is_empty() || *notified || !events.contains(EventFlags::EVENT_READ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let should_notify = if let Some((_, _, active_vt)) = scheme.active_vt {
|
||||
active_vt == *vt
|
||||
} else {
|
||||
let last_allocated = scheme.next_vt_id.load(Ordering::SeqCst) - 1;
|
||||
last_allocated == *vt
|
||||
};
|
||||
|
||||
// The activate VT is not the same as the VT that the consumer is listening to
|
||||
// for events.
|
||||
if !should_notify {
|
||||
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;
|
||||
|
||||
+30
-63
@@ -43,7 +43,7 @@ pub struct DisplayScheme {
|
||||
|
||||
impl DisplayScheme {
|
||||
pub fn new(mut framebuffers: Vec<FrameBuffer>, spec: &[bool]) -> DisplayScheme {
|
||||
let mut inputd_handle = inputd::Handle::new("vesa").unwrap();
|
||||
let mut inputd_handle = inputd::Handle::new("vesa:handle").unwrap();
|
||||
|
||||
let mut onscreens = Vec::new();
|
||||
for fb in framebuffers.iter_mut() {
|
||||
@@ -146,6 +146,18 @@ impl DisplayScheme {
|
||||
|
||||
impl SchemeMut for DisplayScheme {
|
||||
fn open(&mut self, path_str: &str, flags: usize, _uid: u32, _gid: u32) -> Result<usize> {
|
||||
if path_str == "handle" {
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
self.handles.insert(id, Handle {
|
||||
kind: HandleKind::Input,
|
||||
flags,
|
||||
events: EventFlags::empty(),
|
||||
notified_read: false
|
||||
});
|
||||
return Ok(id);
|
||||
}
|
||||
|
||||
let mut parts = path_str.split('/');
|
||||
let mut vt_screen = parts.next().unwrap_or("").split('.');
|
||||
let vt_i = VtIndex(
|
||||
@@ -156,16 +168,6 @@ impl SchemeMut for DisplayScheme {
|
||||
);
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
|
||||
@@ -303,51 +305,14 @@ impl SchemeMut for DisplayScheme {
|
||||
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
||||
|
||||
match handle.kind {
|
||||
HandleKind::Input => if buf.len() == 1 && buf[0] >= 0xF4 {
|
||||
let new_active = VtIndex((buf[0] - 0xF4) as usize + 1);
|
||||
if let Some(screens) = self.vts.get_mut(&new_active) {
|
||||
self.active = new_active;
|
||||
for (screen_i, screen) in screens.iter_mut() {
|
||||
screen.redraw(
|
||||
self.onscreens[screen_i.0],
|
||||
self.framebuffers[screen_i.0].stride
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(1)
|
||||
} else {
|
||||
let events = unsafe { slice::from_raw_parts(buf.as_ptr() as *const Event, buf.len()/mem::size_of::<Event>()) };
|
||||
HandleKind::Input => {
|
||||
let command = inputd::Command::parse(buf);
|
||||
|
||||
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(VtIndex((f - 0x3A) as usize));
|
||||
},
|
||||
0x57 if self.super_key => { // F11
|
||||
new_active_opt = Some(VtIndex(11));
|
||||
},
|
||||
0x58 if self.super_key => { // F12
|
||||
new_active_opt = Some(VtIndex(12));
|
||||
},
|
||||
0x5B => { // Super
|
||||
self.super_key = key_event.pressed;
|
||||
},
|
||||
_ => ()
|
||||
},
|
||||
EventOption::Resize(resize_event) => {
|
||||
let width = resize_event.width as usize;
|
||||
let height = resize_event.height as usize;
|
||||
let stride = width; //TODO: get stride somehow
|
||||
self.resize(width, height, stride);
|
||||
},
|
||||
_ => ()
|
||||
};
|
||||
match command.ty {
|
||||
inputd::CommandTy::Activate => {
|
||||
let vt_i = VtIndex(command.value);
|
||||
|
||||
if let Some(new_active) = new_active_opt {
|
||||
if let Some(screens) = self.vts.get_mut(&new_active) {
|
||||
self.active = new_active;
|
||||
if let Some(screens) = self.vts.get_mut(&vt_i) {
|
||||
for (screen_i, screen) in screens.iter_mut() {
|
||||
screen.redraw(
|
||||
self.onscreens[screen_i.0],
|
||||
@@ -355,18 +320,20 @@ impl SchemeMut for DisplayScheme {
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Some(screens) = self.vts.get_mut(&self.active) {
|
||||
//TODO: send input to other screens? Don't want extra events
|
||||
if let Some(screen) = screens.get_mut(&ScreenIndex(0)) {
|
||||
screen.input(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.active = vt_i;
|
||||
},
|
||||
|
||||
inputd::CommandTy::Deactivate => {
|
||||
// Nothing :)
|
||||
},
|
||||
|
||||
_ => unreachable!()
|
||||
}
|
||||
|
||||
Ok(events.len() * mem::size_of::<Event>())
|
||||
Ok(buf.len())
|
||||
},
|
||||
|
||||
HandleKind::Screen(vt_i, screen_i) => if let Some(screens) = self.vts.get_mut(&vt_i) {
|
||||
if let Some(screen) = screens.get_mut(&screen_i) {
|
||||
let count = screen.write(buf)?;
|
||||
|
||||
@@ -6,4 +6,4 @@ pub mod utils;
|
||||
|
||||
mod probe;
|
||||
|
||||
pub use probe::{probe_device, Device, MSIX_PRIMARY_VECTOR};
|
||||
pub use probe::{probe_device, Device, MSIX_PRIMARY_VECTOR, reinit};
|
||||
|
||||
+30
-20
@@ -11,7 +11,7 @@ use pcid_interface::*;
|
||||
use syscall::Io;
|
||||
|
||||
use crate::spec::*;
|
||||
use crate::transport::{Error, StandardTransport};
|
||||
use crate::transport::{Error, StandardTransport, Queue};
|
||||
use crate::utils::{align_down, VolatileCell};
|
||||
|
||||
pub struct Device<'a> {
|
||||
@@ -21,6 +21,9 @@ pub struct Device<'a> {
|
||||
pub isr: &'a VolatileCell<u32>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Device<'_> {}
|
||||
unsafe impl Sync for Device<'_> {}
|
||||
|
||||
struct MsixInfo {
|
||||
pub virt_table_base: NonNull<MsixTableEntry>,
|
||||
pub capability: MsixCapability,
|
||||
@@ -237,21 +240,7 @@ pub fn probe_device<'a>(pcid_handle: &mut PcidServerHandle) -> Result<Device<'a>
|
||||
let device_space = unsafe { &mut *(device_addr as *mut u8) };
|
||||
let isr = unsafe { &*(isr_addr as *mut VolatileCell<u32>) };
|
||||
|
||||
// Reset the device.
|
||||
common.device_status.set(DeviceStatusFlags::empty());
|
||||
// Upon reset, the device must initialize device status to 0.
|
||||
assert_eq!(common.device_status.get(), DeviceStatusFlags::empty());
|
||||
log::info!("virtio: successfully reseted the device");
|
||||
|
||||
// XXX: According to the virtio specification v1.2, setting the ACKNOWLEDGE and DRIVER bits
|
||||
// in `device_status` is required to be done in two steps.
|
||||
common
|
||||
.device_status
|
||||
.set(common.device_status.get() | DeviceStatusFlags::ACKNOWLEDGE);
|
||||
|
||||
common
|
||||
.device_status
|
||||
.set(common.device_status.get() | DeviceStatusFlags::DRIVER);
|
||||
let transport = StandardTransport::new(common, notify_addr as *const u8, notify_multiplier);
|
||||
|
||||
// Setup interrupts.
|
||||
let all_pci_features = pcid_handle.fetch_all_features()?;
|
||||
@@ -265,12 +254,33 @@ pub fn probe_device<'a>(pcid_handle: &mut PcidServerHandle) -> Result<Device<'a>
|
||||
|
||||
log::info!("virtio: using standard PCI transport");
|
||||
|
||||
let transport = StandardTransport::new(common, notify_addr as *const u8, notify_multiplier);
|
||||
|
||||
Ok(Device {
|
||||
let device = Device {
|
||||
transport,
|
||||
device_space,
|
||||
irq_handle,
|
||||
isr,
|
||||
})
|
||||
};
|
||||
|
||||
device.transport.reset();
|
||||
reinit(& device)?;
|
||||
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
pub fn reinit<'a>(device: & Device<'a>) -> Result<(), Error> {
|
||||
// XXX: According to the virtio specification v1.2, setting the ACKNOWLEDGE and DRIVER bits
|
||||
// in `device_status` is required to be done in two steps.
|
||||
let mut common = device.transport.common.lock().unwrap();
|
||||
let old = common.device_status.get();
|
||||
|
||||
common
|
||||
.device_status
|
||||
.set(old | DeviceStatusFlags::ACKNOWLEDGE);
|
||||
|
||||
let old = common.device_status.get();
|
||||
common.device_status.set(old | DeviceStatusFlags::DRIVER);
|
||||
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -131,6 +131,7 @@ pub struct Queue<'a> {
|
||||
pub descriptor: Dma<[Descriptor]>,
|
||||
pub available: Available<'a>,
|
||||
pub used_head: AtomicU16,
|
||||
vector: u16,
|
||||
|
||||
notification_bell: &'a mut AtomicU16,
|
||||
descriptor_stack: crossbeam_queue::SegQueue<u16>,
|
||||
@@ -145,6 +146,7 @@ impl<'a> Queue<'a> {
|
||||
|
||||
notification_bell: &'a mut AtomicU16,
|
||||
queue_index: u16,
|
||||
vector: u16,
|
||||
) -> Arc<Self> {
|
||||
let descriptor_stack = crossbeam_queue::SegQueue::new();
|
||||
(0..descriptor.len() as u16).for_each(|i| descriptor_stack.push(i));
|
||||
@@ -159,9 +161,21 @@ impl<'a> Queue<'a> {
|
||||
descriptor_stack,
|
||||
used_head: AtomicU16::new(0),
|
||||
sref: sref.clone(),
|
||||
vector
|
||||
})
|
||||
}
|
||||
|
||||
fn reinit(&self) {
|
||||
self.used_head.store(0, Ordering::SeqCst);
|
||||
self.available.set_head_idx(0);
|
||||
|
||||
// Drain all of the available descriptors.
|
||||
while let Some(_) = self.descriptor_stack.pop() {}
|
||||
|
||||
// Refill the descriptor stack.
|
||||
(0..self.descriptor.len() as u16).for_each(|i| self.descriptor_stack.push(i));
|
||||
}
|
||||
|
||||
#[must_use = "The function returns a future that must be awaited to ensure the sent request is completed."]
|
||||
pub fn send(&self, chain: Vec<Buffer>) -> PendingRequest<'a> {
|
||||
let mut first_descriptor: Option<usize> = None;
|
||||
@@ -363,7 +377,7 @@ impl Drop for Used<'_> {
|
||||
}
|
||||
|
||||
pub struct StandardTransport<'a> {
|
||||
common: Mutex<&'a mut CommonCfg>,
|
||||
pub(crate) common: Mutex<&'a mut CommonCfg>,
|
||||
notify: *const u8,
|
||||
notify_mul: u32,
|
||||
|
||||
@@ -383,7 +397,12 @@ impl<'a> StandardTransport<'a> {
|
||||
|
||||
pub fn reset(&self) {
|
||||
let mut common = self.common.lock().unwrap();
|
||||
|
||||
common.device_status.set(DeviceStatusFlags::empty());
|
||||
// Upon reset, the device must initialize device status to 0.
|
||||
assert_eq!(common.device_status.get(), DeviceStatusFlags::empty());
|
||||
|
||||
log::debug!("virtio: successfully reseted device");
|
||||
}
|
||||
|
||||
pub fn check_device_feature(&self, feature: u32) -> bool {
|
||||
@@ -474,7 +493,7 @@ impl<'a> StandardTransport<'a> {
|
||||
|
||||
log::info!("virtio-core: enabled queue #{queue_index} (size={queue_size})");
|
||||
|
||||
let queue = Queue::new(descriptor, avail, used, notification_bell, queue_index);
|
||||
let queue = Queue::new(descriptor, avail, used, notification_bell, queue_index, vector);
|
||||
|
||||
let queue_copy = queue.clone();
|
||||
let irq_fd = irq_handle.as_raw_fd();
|
||||
@@ -499,4 +518,23 @@ impl<'a> StandardTransport<'a> {
|
||||
|
||||
Ok(queue)
|
||||
}
|
||||
|
||||
/// Re-initializes a queue; usually done after a device reset.
|
||||
pub fn reinit_queue(&self, queue: Arc<Queue>) {
|
||||
let mut common = self.common.lock().unwrap();
|
||||
queue.reinit();
|
||||
|
||||
common.queue_select.set(queue.queue_index);
|
||||
|
||||
common.queue_desc.set(queue.descriptor.physical() as u64);
|
||||
common.queue_driver.set(queue.available.phys_addr() as u64);
|
||||
common.queue_device.set(queue.used.phys_addr() as u64);
|
||||
|
||||
// Set the MSI-X vector.
|
||||
common.queue_msix_vector.set(queue.vector);
|
||||
assert!(common.queue_msix_vector.get() == queue.vector);
|
||||
|
||||
// Enable the queue.
|
||||
common.queue_enable.set(1);
|
||||
}
|
||||
}
|
||||
|
||||
+19
-5
@@ -25,10 +25,12 @@
|
||||
use std::cell::UnsafeCell;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
use pcid_interface::PcidServerHandle;
|
||||
|
||||
use syscall::{Packet, SchemeMut};
|
||||
use virtio_core::transport::{Queue, self};
|
||||
use virtio_core::utils::VolatileCell;
|
||||
use virtio_core::MSIX_PRIMARY_VECTOR;
|
||||
|
||||
@@ -356,10 +358,7 @@ pub struct SetScanout {
|
||||
impl SetScanout {
|
||||
pub fn new(scanout_id: u32, resource_id: u32, rect: GpuRect) -> Self {
|
||||
Self {
|
||||
header: ControlHeader {
|
||||
ty: VolatileCell::new(CommandTy::SetScanout),
|
||||
..Default::default()
|
||||
},
|
||||
header: ControlHeader::with_ty(CommandTy::SetScanout),
|
||||
|
||||
rect,
|
||||
scanout_id,
|
||||
@@ -394,6 +393,21 @@ impl XferToHost2d {
|
||||
}
|
||||
}
|
||||
|
||||
static DEVICE: spin::Once<virtio_core::Device> = spin::Once::new();
|
||||
|
||||
fn reinit(control_queue: Arc<Queue>, cursor_queue: Arc<Queue>) -> Result<(), transport::Error> {
|
||||
let device = DEVICE.get().unwrap();
|
||||
|
||||
virtio_core::reinit(device)?;
|
||||
device.transport.finalize_features();
|
||||
|
||||
device.transport.reinit_queue(control_queue.clone());
|
||||
device.transport.reinit_queue(cursor_queue.clone());
|
||||
|
||||
device.transport.run_device();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
|
||||
let mut pcid_handle = PcidServerHandle::connect_default()?;
|
||||
|
||||
@@ -405,7 +419,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
|
||||
assert_eq!(pci_config.func.devid, 0x1050);
|
||||
log::info!("virtio-gpu: initiating startup sequence :^)");
|
||||
|
||||
let device = virtio_core::probe_device(&mut pcid_handle)?;
|
||||
let device = DEVICE.try_call_once(|| virtio_core::probe_device(&mut pcid_handle))?;
|
||||
let config = unsafe { &mut *(device.device_space as *mut GpuConfig) };
|
||||
|
||||
// Negotiate features.
|
||||
|
||||
+161
-74
@@ -1,11 +1,12 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use common::dma::Dma;
|
||||
use inputd::Damage;
|
||||
use syscall::{Error as SysError, SchemeMut, EINVAL};
|
||||
|
||||
use common::dma::Dma;
|
||||
use syscall::{Error as SysError, SchemeMut, EINVAL, EAGAIN};
|
||||
|
||||
use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags};
|
||||
use virtio_core::transport::{Error, Queue, StandardTransport};
|
||||
@@ -40,6 +41,8 @@ pub struct Display<'a> {
|
||||
|
||||
resource_id: u32,
|
||||
id: usize,
|
||||
|
||||
is_reseted: AtomicBool,
|
||||
}
|
||||
|
||||
impl<'a> Display<'a> {
|
||||
@@ -61,9 +64,27 @@ impl<'a> Display<'a> {
|
||||
|
||||
id,
|
||||
resource_id: RESOURCE_ALLOC.fetch_add(1, Ordering::SeqCst),
|
||||
|
||||
is_reseted: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
async fn init(&self) -> Result<(), Error> {
|
||||
if !self.is_reseted.load(Ordering::SeqCst) {
|
||||
// The device is already initialized.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.is_reseted.store(false, Ordering::SeqCst);
|
||||
|
||||
log::info!("virtio-gpu: initializing GPU after a reset");
|
||||
|
||||
crate::reinit(self.control_queue.clone(), self.cursor_queue.clone())?;
|
||||
self.remap_screen().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_fpath(&self, buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
let path = format!("display/virtio-gpu:3.0/{}/{}", self.width, self.height);
|
||||
|
||||
@@ -90,29 +111,26 @@ impl<'a> Display<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remap_screen(&self) -> Result<usize, Error> {
|
||||
let bpp = 32;
|
||||
let fb_size = (self.width as usize * self.height as usize * bpp / 8)
|
||||
.next_multiple_of(syscall::PAGE_SIZE);
|
||||
|
||||
let mapped = *self.mapped.get().unwrap();
|
||||
let address = unsafe {syscall::virttophys(mapped)}?;
|
||||
|
||||
self.map_screen_with(0, address, fb_size, mapped).await
|
||||
}
|
||||
|
||||
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 address = unsafe { syscall::physalloc(fb_size) }? ;
|
||||
let mapped = unsafe {
|
||||
common::physmap(
|
||||
address as usize,
|
||||
@@ -126,9 +144,28 @@ impl<'a> Display<'a> {
|
||||
core::ptr::write_bytes(mapped as *mut u8, 255, fb_size);
|
||||
}
|
||||
|
||||
self.map_screen_with(offset, address, fb_size, mapped).await
|
||||
}
|
||||
|
||||
async fn map_screen_with(&self, offset: usize, address: usize, size: usize, mapped: usize) -> Result<usize, Error> {
|
||||
// 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?;
|
||||
|
||||
// Use the allocated framebuffer from tthe guest ram, and attach it as backing
|
||||
// storage to the resource just created, using `VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING`.
|
||||
//
|
||||
// TODO(andypython): Scatter lists are supported, so the framebuffer doesn’t need to be
|
||||
// contignous in guest physical memory.
|
||||
let entry = Dma::new(MemEntry {
|
||||
address,
|
||||
length: fb_size as u32,
|
||||
address: address as u64,
|
||||
length: size as u32,
|
||||
padding: 0,
|
||||
})?;
|
||||
|
||||
@@ -200,12 +237,22 @@ impl<'a> Display<'a> {
|
||||
|
||||
// Go back to legacy mode.
|
||||
self.transport.reset();
|
||||
self.is_reseted.store(true, Ordering::SeqCst);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
enum Handle {
|
||||
Vt(usize /* VT index */),
|
||||
Input,
|
||||
}
|
||||
|
||||
pub struct Scheme<'a> {
|
||||
handles: BTreeMap<usize /* file descriptor */, Arc<Display<'a>>>,
|
||||
vts: BTreeMap<usize /* VT index */, Arc<Display<'a>>>,
|
||||
handles: BTreeMap<usize /* file descriptor */, Handle>,
|
||||
/// Counter used for file descriptor allocation.
|
||||
next_id: AtomicUsize,
|
||||
inputd_handle: inputd::Handle,
|
||||
displays: Vec<Arc<Display<'a>>>,
|
||||
}
|
||||
@@ -225,9 +272,16 @@ impl<'a> Scheme<'a> {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut inputd_handle = inputd::Handle::new("virtio-gpu:handle").unwrap();
|
||||
|
||||
let mut vts = BTreeMap::new();
|
||||
vts.insert(inputd_handle.register().unwrap(), displays[0].clone());
|
||||
|
||||
Ok(Self {
|
||||
vts,
|
||||
handles: BTreeMap::new(),
|
||||
inputd_handle: inputd::Handle::new("virtio-gpu").unwrap(),
|
||||
next_id: AtomicUsize::new(0),
|
||||
inputd_handle,
|
||||
displays,
|
||||
})
|
||||
}
|
||||
@@ -284,56 +338,42 @@ impl<'a> Scheme<'a> {
|
||||
|
||||
impl<'a> SchemeMut for Scheme<'a> {
|
||||
fn open(&mut self, path: &str, _flags: usize, _uid: u32, _gid: u32) -> syscall::Result<usize> {
|
||||
dbg!(&path);
|
||||
if path == "handle" {
|
||||
let fd = self.next_id.fetch_add(1, Ordering::SeqCst);
|
||||
self.handles.insert(fd, Handle::Input);
|
||||
|
||||
return Ok(fd);
|
||||
}
|
||||
|
||||
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 vt = screen.next().unwrap_or("").parse::<usize>().unwrap();
|
||||
let id = screen.next().unwrap_or("").parse::<usize>().unwrap_or(0);
|
||||
|
||||
dbg!(&id);
|
||||
dbg!(vt, id);
|
||||
|
||||
let display = self.displays.get(id).ok_or(SysError::new(EINVAL))?;
|
||||
if self.displays.get(id).is_none() {
|
||||
return Err(SysError::new(EINVAL));
|
||||
}
|
||||
|
||||
let fd = self.inputd_handle.register().unwrap();
|
||||
self.handles.insert(fd, display.clone());
|
||||
let fd = self.next_id.fetch_add(1, Ordering::SeqCst);
|
||||
// FIXME: The +1 is a hack. vesad smh
|
||||
self.handles.insert(fd, Handle::Vt(vt + 1));
|
||||
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();
|
||||
match self.handles.get(&id).ok_or(SysError::new(EINVAL))? {
|
||||
Handle::Vt(id) => {
|
||||
let handle = self.vts.get_mut(&id).ok_or(SysError::new(EINVAL))?;
|
||||
let bytes_copied = futures::executor::block_on(handle.get_fpath(buf)).unwrap();
|
||||
|
||||
Ok(bytes_copied)
|
||||
Ok(bytes_copied)
|
||||
}
|
||||
|
||||
Handle::Input => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn fmap_old(&mut self, id: usize, map: &syscall::OldMap) -> syscall::Result<usize> {
|
||||
@@ -349,14 +389,25 @@ impl<'a> SchemeMut for Scheme<'a> {
|
||||
}
|
||||
|
||||
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())
|
||||
match self.handles.get(&id).ok_or(SysError::new(EINVAL))? {
|
||||
Handle::Vt(id) => {
|
||||
let handle = self.vts.get_mut(&id).ok_or(SysError::new(EINVAL))?;
|
||||
Ok(futures::executor::block_on(handle.map_screen(map.offset)).unwrap())
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
match self.handles.get(&id).ok_or(SysError::new(EINVAL))? {
|
||||
Handle::Vt(id) => {
|
||||
let handle = self.vts.get_mut(&id).ok_or(SysError::new(EINVAL))?;
|
||||
futures::executor::block_on(handle.flush(None)).unwrap();
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&mut self, _id: usize, _buf: &mut [u8]) -> syscall::Result<usize> {
|
||||
@@ -366,18 +417,54 @@ impl<'a> SchemeMut for Scheme<'a> {
|
||||
}
|
||||
|
||||
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>(),
|
||||
)
|
||||
};
|
||||
match self.handles.get(&id).ok_or(SysError::new(EINVAL))? {
|
||||
Handle::Vt(id) => {
|
||||
let handle = self.vts.get_mut(&id).ok_or(SysError::new(EINVAL))?;
|
||||
|
||||
for damage in damages {
|
||||
futures::executor::block_on(handle.flush(Some(damage))).unwrap();
|
||||
// The VT is not active and the device is reseted. Ask them to try
|
||||
// again later.
|
||||
if handle.is_reseted.load(Ordering::SeqCst) {
|
||||
return Err(SysError::new(EAGAIN));
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
Handle::Input => {
|
||||
let command = inputd::Command::parse(buf);
|
||||
|
||||
match command.ty {
|
||||
inputd::CommandTy::Activate => {
|
||||
let vt = command.value;
|
||||
|
||||
let display = self.vts.get(&vt).unwrap();
|
||||
futures::executor::block_on(display.init()).unwrap();
|
||||
},
|
||||
|
||||
inputd::CommandTy::Deactivate => {
|
||||
let vt = command.value;
|
||||
|
||||
let display = self.vts.get(&vt).ok_or(SysError::new(EINVAL))?;
|
||||
futures::executor::block_on(display.detach()).unwrap();
|
||||
}
|
||||
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn seek(&mut self, _id: usize, _pos: isize, _whence: usize) -> syscall::Result<isize> {
|
||||
|
||||
@@ -10,6 +10,7 @@ futures = { version = "0.3.28", features = ["executor"] }
|
||||
|
||||
virtio-core = { path = "../virtio-core" }
|
||||
pcid = { path = "../pcid" }
|
||||
common = { path = "../common" }
|
||||
|
||||
redox-daemon = "0.1"
|
||||
redox_syscall = "0.3"
|
||||
|
||||
@@ -5,6 +5,8 @@ use std::sync::Arc;
|
||||
use syscall::Error as SysError;
|
||||
use syscall::*;
|
||||
|
||||
use common::dma::Dma;
|
||||
|
||||
use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags};
|
||||
use virtio_core::transport::Queue;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user