virtio-core: add support for legacy transport
Signed-off-by: Anhad Singh <andypython@protonmail.com>
This commit is contained in:
Generated
+1
@@ -1864,6 +1864,7 @@ dependencies = [
|
||||
"redox-daemon",
|
||||
"redox-log",
|
||||
"redox_syscall 0.3.5",
|
||||
"spin",
|
||||
"static_assertions",
|
||||
"thiserror",
|
||||
"virtio-core",
|
||||
|
||||
+25
-12
@@ -483,20 +483,20 @@ pub fn main() {
|
||||
// provided by the firmware. The GPU devices are latter started by `pcid` (such as `virtio-gpu`).
|
||||
let mut devices = vec![];
|
||||
let schemes = std::fs::read_dir(":").unwrap();
|
||||
|
||||
|
||||
for entry in schemes {
|
||||
let path = entry.unwrap().path();
|
||||
let path_str = path
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.expect("inputd: failed to convert path to string");
|
||||
|
||||
|
||||
if path_str.contains("display") {
|
||||
println!("inputd: found display scheme {}", path_str);
|
||||
devices.push(path_str);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let device = devices
|
||||
.iter()
|
||||
.filter(|d| !d.contains("vesa"))
|
||||
@@ -507,10 +507,10 @@ pub fn main() {
|
||||
// TODO: What should we do when there are multiple display devices?
|
||||
device[0].split('/').nth(2).unwrap()
|
||||
};
|
||||
|
||||
|
||||
let mut handle =
|
||||
inputd::Handle::new(device).expect("inputd: failed to open display handle");
|
||||
|
||||
|
||||
handle
|
||||
.activate(vt, VtMode::Graphic)
|
||||
.expect("inputd: failed to activate VT in graphic mode");
|
||||
@@ -520,20 +520,33 @@ pub fn main() {
|
||||
"-A" => {
|
||||
let vt = args.next().unwrap().parse::<usize>().unwrap();
|
||||
|
||||
let handle = File::open(format!("input:consumer/{vt}")).expect("inputd: failed to open consumer handle");
|
||||
let handle = File::open(format!("input:consumer/{vt}"))
|
||||
.expect("inputd: failed to open consumer handle");
|
||||
let mut display_path = [0; 4096];
|
||||
|
||||
let written = syscall::fpath(handle.as_raw_fd() as usize, &mut display_path).expect("inputd: fpath() failed");
|
||||
let written = syscall::fpath(handle.as_raw_fd() as usize, &mut display_path)
|
||||
.expect("inputd: fpath() failed");
|
||||
|
||||
assert!(written <= display_path.len());
|
||||
drop(handle);
|
||||
|
||||
let display_path = std::str::from_utf8(&display_path[..written]).expect("inputd: display path UTF-8 validation failed");
|
||||
let display_name = display_path.split('/').skip(1).next().expect("inputd: invalid display path");
|
||||
let display_scheme = display_name.split(':').next().expect("inputd: invalid display path");
|
||||
let display_path = std::str::from_utf8(&display_path[..written])
|
||||
.expect("inputd: display path UTF-8 validation failed");
|
||||
let display_name = display_path
|
||||
.split('/')
|
||||
.skip(1)
|
||||
.next()
|
||||
.expect("inputd: invalid display path");
|
||||
let display_scheme = display_name
|
||||
.split(':')
|
||||
.next()
|
||||
.expect("inputd: invalid display path");
|
||||
|
||||
let mut handle = inputd::Handle::new(display_scheme).expect("inputd: failed to open display handle");
|
||||
handle.activate(vt, VtMode::Default).expect("inputd: failed to activate VT");
|
||||
let mut handle = inputd::Handle::new(display_scheme)
|
||||
.expect("inputd: failed to open display handle");
|
||||
handle
|
||||
.activate(vt, VtMode::Default)
|
||||
.expect("inputd: failed to activate VT");
|
||||
}
|
||||
|
||||
_ => panic!("inputd: invalid argument: {}", val),
|
||||
|
||||
@@ -10,6 +10,7 @@ log = "0.4"
|
||||
thiserror = "1.0.40"
|
||||
static_assertions = "1.1.0"
|
||||
futures = { version = "0.3.28", features = ["executor"] }
|
||||
spin = "*"
|
||||
|
||||
redox-daemon = "0.1"
|
||||
redox-log = "0.1"
|
||||
|
||||
+43
-14
@@ -1,9 +1,12 @@
|
||||
#![deny(trivial_numeric_casts, unused_allocation)]
|
||||
#![feature(int_roundings, async_fn_in_trait)]
|
||||
|
||||
// TODO(andypython): driver panic with an empty disk.
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::fd::{FromRawFd, RawFd};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use static_assertions::const_assert_eq;
|
||||
|
||||
@@ -12,6 +15,7 @@ use virtio_core::spec::*;
|
||||
|
||||
use syscall::{Packet, SchemeBlockMut};
|
||||
|
||||
use virtio_core::transport::Transport;
|
||||
use virtio_core::utils::VolatileCell;
|
||||
|
||||
mod scheme;
|
||||
@@ -43,23 +47,48 @@ pub struct BlockGeometry {
|
||||
pub sectors: VolatileCell<u8>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct BlockDeviceConfig {
|
||||
capacity: VolatileCell<u64>,
|
||||
pub size_max: VolatileCell<u32>,
|
||||
pub seq_max: VolatileCell<u32>,
|
||||
pub geometry: BlockGeometry,
|
||||
blk_size: VolatileCell<u32>,
|
||||
#[repr(u8)]
|
||||
pub enum DeviceConfigTy {
|
||||
Capacity = 0,
|
||||
SizeMax = 0x8,
|
||||
SeqMax = 0xc,
|
||||
Geometry = 0x10,
|
||||
BlkSize = 0x14,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct BlockDeviceConfig(Weak<dyn Transport>);
|
||||
|
||||
impl BlockDeviceConfig {
|
||||
/// Returns the capacity of the block device in bytes.
|
||||
pub fn capacity(&self) -> u64 {
|
||||
self.capacity.get()
|
||||
#[inline]
|
||||
fn new(tranport: &Arc<dyn Transport>) -> Self {
|
||||
Self(Arc::downgrade(&tranport))
|
||||
}
|
||||
|
||||
pub fn load_config<T>(&self, ty: DeviceConfigTy) -> T
|
||||
where
|
||||
T: Sized + TryFrom<u64>,
|
||||
<T as TryFrom<u64>>::Error: std::fmt::Debug,
|
||||
{
|
||||
let transport = self.0.upgrade().unwrap();
|
||||
|
||||
let size = core::mem::size_of::<T>()
|
||||
.try_into()
|
||||
.expect("load_config: invalid size");
|
||||
|
||||
let value = transport.load_config(ty as u8, size);
|
||||
T::try_from(value).unwrap()
|
||||
}
|
||||
|
||||
/// Returns the capacity of the block device in bytes.
|
||||
#[inline]
|
||||
pub fn capacity(&self) -> u64 {
|
||||
self.load_config(DeviceConfigTy::Capacity)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn block_size(&self) -> u32 {
|
||||
self.blk_size.get()
|
||||
self.load_config(DeviceConfigTy::BlkSize)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,15 +127,15 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
|
||||
.transport
|
||||
.setup_queue(virtio_core::MSIX_PRIMARY_VECTOR, &device.irq_handle)?;
|
||||
|
||||
let device_space = unsafe { &mut *(device.device_space as *mut BlockDeviceConfig) };
|
||||
let device_space = BlockDeviceConfig::new(&device.transport);
|
||||
|
||||
// At this point the device is alive!
|
||||
device.transport.run_device();
|
||||
|
||||
log::info!(
|
||||
"virtio-blk: disk size: {} sectors and block size of {} bytes",
|
||||
device_space.capacity.get(),
|
||||
device_space.blk_size.get()
|
||||
device_space.capacity(),
|
||||
device_space.block_size()
|
||||
);
|
||||
|
||||
let mut name = pci_config.func.name();
|
||||
|
||||
@@ -99,13 +99,13 @@ pub enum Handle {
|
||||
pub struct DiskScheme<'a> {
|
||||
queue: Arc<Queue<'a>>,
|
||||
next_id: usize,
|
||||
cfg: &'a mut BlockDeviceConfig,
|
||||
cfg: BlockDeviceConfig,
|
||||
handles: BTreeMap<usize, Handle>,
|
||||
part_table: Option<PartitionTable>,
|
||||
}
|
||||
|
||||
impl<'a> DiskScheme<'a> {
|
||||
pub fn new(queue: Arc<Queue<'a>>, cfg: &'a mut BlockDeviceConfig) -> Self {
|
||||
pub fn new(queue: Arc<Queue<'a>>, cfg: BlockDeviceConfig) -> Self {
|
||||
let mut this = Self {
|
||||
queue,
|
||||
next_id: 0,
|
||||
@@ -157,7 +157,7 @@ impl<'a> DiskScheme<'a> {
|
||||
|
||||
impl<'a, 'b> Seek for VirtioShim<'a, 'b> {
|
||||
fn seek(&mut self, from: std::io::SeekFrom) -> IoResult<u64> {
|
||||
let size_u = self.scheme.cfg.capacity.get() * self.scheme.cfg.blk_size.get() as u64;
|
||||
let size_u = self.scheme.cfg.capacity() * self.scheme.cfg.block_size() as u64;
|
||||
let size = i64::try_from(size_u).or(Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Disk larger than 2^63 - 1 bytes",
|
||||
|
||||
+77
-41
@@ -11,11 +11,11 @@ use pcid_interface::*;
|
||||
use syscall::Io;
|
||||
|
||||
use crate::spec::*;
|
||||
use crate::transport::{Error, StandardTransport};
|
||||
use crate::transport::{Error, LegacyTransport, StandardTransport, Transport};
|
||||
use crate::utils::{align_down, VolatileCell};
|
||||
|
||||
pub struct Device<'a> {
|
||||
pub transport: Arc<StandardTransport<'a>>,
|
||||
pub transport: Arc<dyn Transport>,
|
||||
pub device_space: *const u8,
|
||||
pub irq_handle: File,
|
||||
pub isr: &'a VolatileCell<u32>,
|
||||
@@ -233,59 +233,95 @@ pub fn probe_device<'a>(pcid_handle: &mut PcidServerHandle) -> Result<Device<'a>
|
||||
log::trace!("virtio-core::device-probe: {capability:?}");
|
||||
}
|
||||
|
||||
let common_addr = common_addr.ok_or(Error::InCapable(CfgType::Common))?;
|
||||
let (notify_addr, notify_multiplier) = notify_addr.ok_or(Error::InCapable(CfgType::Notify))?;
|
||||
let isr_addr = isr_addr.ok_or(Error::InCapable(CfgType::Isr))?;
|
||||
let device_addr = device_addr.ok_or(Error::InCapable(CfgType::Device))?;
|
||||
if let (
|
||||
Some(common_addr),
|
||||
Some(isr_addr),
|
||||
Some(device_addr),
|
||||
Some((notify_addr, notify_multiplier)),
|
||||
) = (common_addr, isr_addr, device_addr, notify_addr)
|
||||
{
|
||||
assert!(
|
||||
notify_multiplier != 0,
|
||||
"virtio-core::device_probe: device uses the same Queue Notify addresses for all queues"
|
||||
);
|
||||
|
||||
assert!(
|
||||
notify_multiplier != 0,
|
||||
"virtio-core::device_probe: device uses the same Queue Notify addresses for all queues"
|
||||
);
|
||||
let common = unsafe { &mut *(common_addr as *mut CommonCfg) };
|
||||
let device_space = unsafe { &mut *(device_addr as *mut u8) };
|
||||
let isr = unsafe { &*(isr_addr as *mut VolatileCell<u32>) };
|
||||
|
||||
let common = unsafe { &mut *(common_addr as *mut CommonCfg) };
|
||||
let device_space = unsafe { &mut *(device_addr as *mut u8) };
|
||||
let isr = unsafe { &*(isr_addr as *mut VolatileCell<u32>) };
|
||||
let transport = StandardTransport::new(
|
||||
common,
|
||||
notify_addr as *const u8,
|
||||
notify_multiplier,
|
||||
device_space,
|
||||
);
|
||||
|
||||
let transport = StandardTransport::new(common, notify_addr as *const u8, notify_multiplier);
|
||||
// Setup interrupts.
|
||||
let all_pci_features = pcid_handle.fetch_all_features()?;
|
||||
let has_msix = all_pci_features
|
||||
.iter()
|
||||
.any(|(feature, _)| feature.is_msix());
|
||||
|
||||
// Setup interrupts.
|
||||
let all_pci_features = pcid_handle.fetch_all_features()?;
|
||||
let has_msix = all_pci_features
|
||||
.iter()
|
||||
.any(|(feature, _)| feature.is_msix());
|
||||
// According to the virtio specification, the device REQUIRED to support MSI-X.
|
||||
assert!(has_msix, "virtio: device does not support MSI-X");
|
||||
let irq_handle = enable_msix(pcid_handle)?;
|
||||
|
||||
// According to the virtio specification, the device REQUIRED to support MSI-X.
|
||||
assert!(has_msix, "virtio: device does not support MSI-X");
|
||||
let irq_handle = enable_msix(pcid_handle)?;
|
||||
log::info!("virtio: using standard PCI transport");
|
||||
|
||||
log::info!("virtio: using standard PCI transport");
|
||||
let device = Device {
|
||||
transport,
|
||||
device_space,
|
||||
irq_handle,
|
||||
isr,
|
||||
};
|
||||
|
||||
let device = Device {
|
||||
transport,
|
||||
device_space,
|
||||
irq_handle,
|
||||
isr,
|
||||
};
|
||||
device.transport.reset();
|
||||
reinit(&device)?;
|
||||
|
||||
device.transport.reset();
|
||||
reinit(&device)?;
|
||||
Ok(device)
|
||||
} else {
|
||||
if let PciBar::Port(port) = pci_header.get_bar(0) {
|
||||
unsafe { syscall::iopl(3).expect("virtio: failed to set I/O privilege level") };
|
||||
log::warn!("virtio: using legacy transport");
|
||||
|
||||
Ok(device)
|
||||
static SHIM: VolatileCell<u32> = VolatileCell::new(0);
|
||||
|
||||
let transport = LegacyTransport::new(port);
|
||||
|
||||
// Setup interrupts.
|
||||
let all_pci_features = pcid_handle.fetch_all_features()?;
|
||||
let has_msix = all_pci_features
|
||||
.iter()
|
||||
.any(|(feature, _)| feature.is_msix());
|
||||
|
||||
// According to the virtio specification, the device REQUIRED to support MSI-X.
|
||||
assert!(has_msix, "virtio: device does not support MSI-X");
|
||||
let irq_handle = enable_msix(pcid_handle)?;
|
||||
|
||||
let device = Device {
|
||||
transport,
|
||||
irq_handle,
|
||||
isr: &SHIM,
|
||||
device_space: core::ptr::null_mut(),
|
||||
};
|
||||
|
||||
device.transport.reset();
|
||||
reinit(&device)?;
|
||||
|
||||
Ok(device)
|
||||
} else {
|
||||
unreachable!("virtio: legacy transport with non-port IO?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
device
|
||||
.transport
|
||||
.insert_status(DeviceStatusFlags::ACKNOWLEDGE);
|
||||
|
||||
device.transport.insert_status(DeviceStatusFlags::DRIVER);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+370
-79
@@ -1,8 +1,9 @@
|
||||
use crate::spec::*;
|
||||
use crate::utils::align;
|
||||
|
||||
use common::dma::Dma;
|
||||
use common::dma::{Dma, PhysBox};
|
||||
use event::EventQueue;
|
||||
use syscall::{Io, Pio};
|
||||
|
||||
use core::mem::size_of;
|
||||
use core::sync::atomic::{AtomicU16, Ordering};
|
||||
@@ -62,12 +63,43 @@ const fn queue_part_sizes(queue_size: usize) -> (usize, usize, usize) {
|
||||
let used = used_header + size_of::<UsedRingElement>() * queue_size;
|
||||
|
||||
(
|
||||
align(desc, DESCRIPTOR_ALIGN),
|
||||
align(avail, AVAILABLE_ALIGN),
|
||||
align(used, USED_ALIGN),
|
||||
align(desc, DESCRIPTOR_ALIGN).next_multiple_of(syscall::PAGE_SIZE),
|
||||
align(avail, AVAILABLE_ALIGN).next_multiple_of(syscall::PAGE_SIZE),
|
||||
align(used, USED_ALIGN).next_multiple_of(syscall::PAGE_SIZE),
|
||||
)
|
||||
}
|
||||
|
||||
fn spawn_irq_thread(irq_handle: &File, queue: &Arc<Queue<'static>>) -> Result<(), Error> {
|
||||
let irq_fd = irq_handle.as_raw_fd();
|
||||
let queue_copy = queue.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let mut event_queue = EventQueue::<usize>::new().unwrap();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// Wake up the tasks waiting on the queue.
|
||||
Ok(None)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
event_queue.run().unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub trait NotifyBell {
|
||||
fn ring(&self, queue_index: u16);
|
||||
}
|
||||
|
||||
pub struct PendingRequest<'a> {
|
||||
queue: Arc<Queue<'a>>,
|
||||
first_descriptor: u32,
|
||||
@@ -133,26 +165,29 @@ pub struct Queue<'a> {
|
||||
pub used_head: AtomicU16,
|
||||
vector: u16,
|
||||
|
||||
notification_bell: &'a mut AtomicU16,
|
||||
notification_bell: Box<dyn NotifyBell>,
|
||||
descriptor_stack: crossbeam_queue::SegQueue<u16>,
|
||||
sref: Weak<Self>,
|
||||
}
|
||||
|
||||
impl<'a> Queue<'a> {
|
||||
pub fn new(
|
||||
pub fn new<N>(
|
||||
descriptor: Dma<[Descriptor]>,
|
||||
available: Available<'a>,
|
||||
used: Used<'a>,
|
||||
|
||||
notification_bell: &'a mut AtomicU16,
|
||||
notification_bell: N,
|
||||
queue_index: u16,
|
||||
vector: u16,
|
||||
) -> Arc<Self> {
|
||||
) -> Arc<Self>
|
||||
where
|
||||
N: NotifyBell + 'static,
|
||||
{
|
||||
let descriptor_stack = crossbeam_queue::SegQueue::new();
|
||||
(0..descriptor.len() as u16).for_each(|i| descriptor_stack.push(i));
|
||||
|
||||
Arc::new_cyclic(|sref| Self {
|
||||
notification_bell,
|
||||
notification_bell: Box::new(notification_bell),
|
||||
available,
|
||||
descriptor,
|
||||
used,
|
||||
@@ -211,8 +246,7 @@ impl<'a> Queue<'a> {
|
||||
.set_table_index(first_descriptor as u16);
|
||||
|
||||
self.available.set_head_idx(index as u16 + 1);
|
||||
self.notification_bell
|
||||
.store(self.queue_index, Ordering::SeqCst);
|
||||
self.notification_bell.ring(self.queue_index);
|
||||
|
||||
PendingRequest {
|
||||
queue: self.sref.upgrade().unwrap(),
|
||||
@@ -240,22 +274,33 @@ pub struct Available<'a> {
|
||||
|
||||
impl<'a> Available<'a> {
|
||||
pub fn new(queue_size: usize) -> Result<Self, Error> {
|
||||
let (_, size, _) = queue_part_sizes(queue_size);
|
||||
let size = size.next_multiple_of(syscall::PAGE_SIZE); // align to page size
|
||||
|
||||
let (_, _, size) = queue_part_sizes(queue_size);
|
||||
let addr = unsafe { syscall::physalloc(size) }.map_err(Error::SyscallError)?;
|
||||
let virt =
|
||||
unsafe { common::physmap(addr, size, common::Prot::RW, common::MemoryType::default()) }
|
||||
.map_err(Error::SyscallError)?;
|
||||
|
||||
unsafe { Self::from_raw(addr, size, queue_size) }
|
||||
}
|
||||
|
||||
/// `addr` is the physical address of the ring.
|
||||
pub unsafe fn from_raw(addr: usize, size: usize, queue_size: usize) -> Result<Self, Error> {
|
||||
let virt = unsafe {
|
||||
common::physmap(addr, size, common::Prot::RW, common::MemoryType::default())
|
||||
}?;
|
||||
|
||||
let ring = unsafe { &mut *(virt as *mut AvailableRing) };
|
||||
|
||||
Ok(Self {
|
||||
let ring = Self {
|
||||
addr,
|
||||
size,
|
||||
ring,
|
||||
queue_size,
|
||||
})
|
||||
};
|
||||
|
||||
for i in 0..queue_size {
|
||||
// Setting them to `u16::MAX` helps with debugging since qemu reports them
|
||||
// as illegal values.
|
||||
ring.get_element_at(i).table_index.store(u16::MAX, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
Ok(ring)
|
||||
}
|
||||
|
||||
/// ## Panics
|
||||
@@ -308,21 +353,32 @@ pub struct Used<'a> {
|
||||
impl<'a> Used<'a> {
|
||||
pub fn new(queue_size: usize) -> Result<Self, Error> {
|
||||
let (_, _, size) = queue_part_sizes(queue_size);
|
||||
let size = size.next_multiple_of(syscall::PAGE_SIZE); // align to page size
|
||||
|
||||
let addr = unsafe { syscall::physalloc(size) }.map_err(Error::SyscallError)?;
|
||||
let virt =
|
||||
unsafe { common::physmap(addr, size, common::Prot::RW, common::MemoryType::default()) }
|
||||
.map_err(Error::SyscallError)?;
|
||||
|
||||
unsafe { Self::from_raw(addr, size, queue_size) }
|
||||
}
|
||||
|
||||
/// `addr` is the physical address of the ring.
|
||||
pub unsafe fn from_raw(addr: usize, size: usize, queue_size: usize) -> Result<Self, Error> {
|
||||
let virt = unsafe {
|
||||
common::physmap(addr, size, common::Prot::RW, common::MemoryType::default())
|
||||
}?;
|
||||
|
||||
let ring = unsafe { &mut *(virt as *mut UsedRing) };
|
||||
|
||||
Ok(Self {
|
||||
let mut ring = Self {
|
||||
addr,
|
||||
size,
|
||||
ring,
|
||||
queue_size,
|
||||
})
|
||||
};
|
||||
|
||||
for i in 0..queue_size {
|
||||
// Setting them to `u32::MAX` helps with debugging since qemu reports them
|
||||
// as illegal values.
|
||||
ring.get_mut_element_at(i).table_index.set(u32::MAX);
|
||||
}
|
||||
|
||||
Ok(ring)
|
||||
}
|
||||
|
||||
/// ## Panics
|
||||
@@ -377,43 +433,307 @@ impl Drop for Used<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Transport: Sync + Send {
|
||||
/// `size` specifies the size of the read in bytes.
|
||||
///
|
||||
/// ## Panics
|
||||
/// This function panics if the provided `size` is more then `size_of::<u64>()`.
|
||||
fn load_config(&self, offset: u8, size: u8) -> u64;
|
||||
|
||||
/// Resets the device.
|
||||
fn reset(&self);
|
||||
|
||||
/// Returns whether the device supports the specified feature.
|
||||
fn check_device_feature(&self, feature: u32) -> bool;
|
||||
|
||||
/// Acknowledges the specified feature.
|
||||
///
|
||||
/// **Note**: [`Transport::check_device_feature`] must be used to check whether
|
||||
/// the device supports the feature before acknowledging it.
|
||||
fn ack_driver_feature(&self, feature: u32);
|
||||
|
||||
/// Finalizes the acknowledged features by setting the `FEATURES_OK` bit in the
|
||||
/// device status flags. No-op on a legacy device.
|
||||
fn finalize_features(&self);
|
||||
|
||||
/// Runs the device.
|
||||
///
|
||||
/// At this point, all of the queues must be created and the features must be
|
||||
/// finalized.
|
||||
///
|
||||
/// ## Panics
|
||||
/// This function panics if the device is already running.
|
||||
fn run_device(&self) {
|
||||
self.insert_status(DeviceStatusFlags::DRIVER_OK);
|
||||
}
|
||||
|
||||
/// Creates a new queue.
|
||||
///
|
||||
/// ## Panics
|
||||
/// This function panics if the device is running.
|
||||
fn setup_queue(&self, vector: u16, irq_handle: &File) -> Result<Arc<Queue>, Error>;
|
||||
|
||||
// TODO(andypython): Should this function be unsafe?
|
||||
fn reinit_queue(&self, queue: Arc<Queue>);
|
||||
fn insert_status(&self, status: DeviceStatusFlags);
|
||||
}
|
||||
|
||||
pub enum LegacyRegister {
|
||||
DeviceFeatures = 0, // u32
|
||||
|
||||
QueueAddress = 8, // u32
|
||||
QueueSize = 12, // u16
|
||||
QueueSelect = 14, // u16
|
||||
QueueNotify = 16, // u16
|
||||
|
||||
DeviceStatus = 18, // u8
|
||||
|
||||
ConfigMsixVector = 20, // u16
|
||||
QueueMsixVector = 22, // u16
|
||||
}
|
||||
|
||||
struct LegacyBell(Weak<LegacyTransport>);
|
||||
|
||||
impl NotifyBell for LegacyBell {
|
||||
#[inline]
|
||||
fn ring(&self, queue_index: u16) {
|
||||
let transport = self.0.upgrade().expect("bell: transport dropped");
|
||||
transport.write::<u16>(LegacyRegister::QueueNotify, queue_index)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LegacyTransport(u16, AtomicU16, Weak<Self>);
|
||||
|
||||
impl LegacyTransport {
|
||||
pub(super) fn new(port: u16) -> Arc<Self> {
|
||||
Arc::new_cyclic(|sref| Self(port, AtomicU16::new(0), sref.clone()))
|
||||
}
|
||||
|
||||
unsafe fn read_raw<V>(&self, offset: usize) -> V
|
||||
where
|
||||
V: Sized + TryFrom<u64>,
|
||||
<V as TryFrom<u64>>::Error: std::fmt::Debug,
|
||||
{
|
||||
let port = self.0 + offset as u16;
|
||||
|
||||
if size_of::<V>() == size_of::<u8>() {
|
||||
V::try_from(Pio::<u8>::new(port).read() as u64).unwrap()
|
||||
} else if size_of::<V>() == size_of::<u16>() {
|
||||
V::try_from(Pio::<u16>::new(port).read() as u64).unwrap()
|
||||
} else if size_of::<V>() == size_of::<u32>() {
|
||||
V::try_from(Pio::<u32>::new(port).read() as u64).unwrap()
|
||||
} else if size_of::<V>() == size_of::<u64>() {
|
||||
let lower = Pio::<u32>::new(port).read() as u64;
|
||||
let upper = Pio::<u32>::new(port + size_of::<u32>() as u16).read() as u64;
|
||||
|
||||
V::try_from(lower | (upper << 32)).unwrap()
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn read<V>(&self, register: LegacyRegister) -> V
|
||||
where
|
||||
V: Sized + TryFrom<u64>,
|
||||
<V as TryFrom<u64>>::Error: std::fmt::Debug,
|
||||
{
|
||||
unsafe { self.read_raw(register as usize) }
|
||||
}
|
||||
|
||||
fn write<V>(&self, register: LegacyRegister, value: V)
|
||||
where
|
||||
V: Sized + TryInto<usize>,
|
||||
<V as TryInto<usize>>::Error: std::fmt::Debug,
|
||||
{
|
||||
if size_of::<V>() == size_of::<u8>() {
|
||||
Pio::<u8>::new(self.0 + register as u16).write(value.try_into().unwrap() as u8);
|
||||
} else if size_of::<V>() == size_of::<u16>() {
|
||||
Pio::<u16>::new(self.0 + register as u16).write(value.try_into().unwrap() as u16);
|
||||
} else if size_of::<V>() == size_of::<u32>() {
|
||||
Pio::<u32>::new(self.0 + register as u16).write(value.try_into().unwrap() as u32);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Transport for LegacyTransport {
|
||||
fn reset(&self) {
|
||||
self.write(LegacyRegister::DeviceStatus, 0u8);
|
||||
|
||||
let status = self.read::<u8>(LegacyRegister::DeviceStatus);
|
||||
assert_eq!(status, 0);
|
||||
}
|
||||
|
||||
fn check_device_feature(&self, feature: u32) -> bool {
|
||||
assert!(
|
||||
feature < 32,
|
||||
"virtio: cannot query feature {feature} on a legacy device"
|
||||
);
|
||||
self.read::<u32>(LegacyRegister::DeviceFeatures) & (1 << feature) == (1 << feature)
|
||||
}
|
||||
|
||||
fn ack_driver_feature(&self, feature: u32) {
|
||||
assert!(
|
||||
feature < 32,
|
||||
"virtio: cannot ack feature {feature} on a legacy device"
|
||||
);
|
||||
|
||||
let current = self.read::<u32>(LegacyRegister::DeviceFeatures);
|
||||
self.write::<u32>(LegacyRegister::DeviceFeatures, current | (1 << feature));
|
||||
}
|
||||
|
||||
fn setup_queue(&self, vector: u16, irq_handle: &File) -> Result<Arc<Queue>, Error> {
|
||||
let queue_index = self.1.fetch_add(1, Ordering::SeqCst);
|
||||
self.write(LegacyRegister::QueueSelect, queue_index);
|
||||
|
||||
let queue_size = self.read::<u16>(LegacyRegister::QueueSize) as usize;
|
||||
let (desc_size, avail_size, used_size) = queue_part_sizes(queue_size);
|
||||
|
||||
let size_bytes = desc_size + avail_size + used_size;
|
||||
let addr = unsafe { syscall::physalloc(size_bytes).map_err(Error::SyscallError)? };
|
||||
|
||||
let descriptor = unsafe {
|
||||
let physbox = PhysBox::from_raw_parts(addr, desc_size);
|
||||
let table = Dma::<[Descriptor]>::from_physbox_uninit_unsized(physbox, queue_size)?;
|
||||
|
||||
table.assume_init()
|
||||
};
|
||||
|
||||
let avail_addr = addr + desc_size;
|
||||
let avail = unsafe { Available::from_raw(avail_addr, avail_size, queue_size)? };
|
||||
|
||||
let used_addr = avail_addr + avail_size;
|
||||
let used = unsafe { Used::from_raw(used_addr, used_size, queue_size)? };
|
||||
|
||||
self.write::<u16>(LegacyRegister::QueueMsixVector, vector);
|
||||
self.write::<u32>(LegacyRegister::QueueAddress, (addr as u32) >> 12);
|
||||
|
||||
log::info!("virtio-core: enabled queue #{queue_index} (size={queue_size})");
|
||||
|
||||
let queue = Queue::new(
|
||||
descriptor,
|
||||
avail,
|
||||
used,
|
||||
LegacyBell(self.2.clone()),
|
||||
queue_index,
|
||||
vector,
|
||||
);
|
||||
|
||||
spawn_irq_thread(irq_handle, &queue)?;
|
||||
Ok(queue)
|
||||
}
|
||||
|
||||
fn load_config(&self, offset: u8, size: u8) -> u64 {
|
||||
// We always enable MSI-X. So, the device configuration space offset will
|
||||
// always be 0x18.
|
||||
//
|
||||
// Checkout 4.1.4.8 Legacy Interfaces: A Note on PCI Device Layout
|
||||
const DEVICE_SPACE_OFFSET: usize = 0x18;
|
||||
|
||||
let size = size as usize;
|
||||
let offset = DEVICE_SPACE_OFFSET + offset as usize;
|
||||
|
||||
unsafe {
|
||||
if size == size_of::<u8>() {
|
||||
self.read_raw::<u8>(offset) as u64
|
||||
} else if size == size_of::<u16>() {
|
||||
self.read_raw::<u16>(offset) as u64
|
||||
} else if size == size_of::<u32>() {
|
||||
self.read_raw::<u32>(offset) as u64
|
||||
} else if size == size_of::<u64>() {
|
||||
self.read_raw::<u64>(offset) as u64
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_status(&self, status: DeviceStatusFlags) {
|
||||
let old = self.read::<u8>(LegacyRegister::DeviceStatus);
|
||||
self.write(LegacyRegister::DeviceStatus, old | status.bits());
|
||||
}
|
||||
|
||||
fn reinit_queue(&self, _queue: Arc<Queue>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
// Legacy devices do not have the `FEATURES_OK` bit.
|
||||
fn finalize_features(&self) {}
|
||||
}
|
||||
|
||||
struct StandardBell<'a>(&'a mut AtomicU16);
|
||||
|
||||
impl NotifyBell for StandardBell<'_> {
|
||||
#[inline]
|
||||
fn ring(&self, queue_index: u16) {
|
||||
self.0.store(queue_index, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StandardTransport<'a> {
|
||||
pub(crate) common: Mutex<&'a mut CommonCfg>,
|
||||
notify: *const u8,
|
||||
notify_mul: u32,
|
||||
device_space: *const u8,
|
||||
|
||||
queue_index: AtomicU16,
|
||||
}
|
||||
|
||||
impl<'a> StandardTransport<'a> {
|
||||
pub fn new(common: &'a mut CommonCfg, notify: *const u8, notify_mul: u32) -> Arc<Self> {
|
||||
pub fn new(
|
||||
common: &'a mut CommonCfg,
|
||||
notify: *const u8,
|
||||
notify_mul: u32,
|
||||
device_space: *const u8,
|
||||
) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
common: Mutex::new(common),
|
||||
notify,
|
||||
notify_mul,
|
||||
|
||||
queue_index: AtomicU16::new(0),
|
||||
device_space,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&self) {
|
||||
impl Transport for StandardTransport<'_> {
|
||||
fn load_config(&self, offset: u8, size: u8) -> u64 {
|
||||
unsafe {
|
||||
let ptr = self.device_space.add(offset as usize);
|
||||
let size = size as usize;
|
||||
|
||||
if size == size_of::<u8>() {
|
||||
ptr.cast::<u8>().read() as u64
|
||||
} else if size == size_of::<u16>() {
|
||||
ptr.cast::<u16>().read() as u64
|
||||
} else if size == size_of::<u32>() {
|
||||
ptr.cast::<u32>().read() as u64
|
||||
} else if size == size_of::<u64>() {
|
||||
ptr.cast::<u64>().read() as u64
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
fn check_device_feature(&self, feature: u32) -> bool {
|
||||
let mut common = self.common.lock().unwrap();
|
||||
|
||||
common.device_feature_select.set(feature >> 5);
|
||||
(common.device_feature.get() & (1 << (feature & 31))) != 0
|
||||
}
|
||||
|
||||
pub fn ack_driver_feature(&self, feature: u32) {
|
||||
fn ack_driver_feature(&self, feature: u32) {
|
||||
let mut common = self.common.lock().unwrap();
|
||||
|
||||
common.driver_feature_select.set(feature >> 5);
|
||||
@@ -422,7 +742,7 @@ impl<'a> StandardTransport<'a> {
|
||||
common.driver_feature.set(current | (1 << (feature & 31)));
|
||||
}
|
||||
|
||||
pub fn finalize_features(&self) {
|
||||
fn finalize_features(&self) {
|
||||
// Check VirtIO version 1 compliance.
|
||||
assert!(self.check_device_feature(VIRTIO_F_VERSION_1));
|
||||
self.ack_driver_feature(VIRTIO_F_VERSION_1);
|
||||
@@ -440,16 +760,7 @@ impl<'a> StandardTransport<'a> {
|
||||
assert!((confirm & DeviceStatusFlags::FEATURES_OK) == DeviceStatusFlags::FEATURES_OK);
|
||||
}
|
||||
|
||||
pub fn run_device(&self) {
|
||||
let mut common = self.common.lock().unwrap();
|
||||
|
||||
let status = common.device_status.get();
|
||||
common
|
||||
.device_status
|
||||
.set(status | DeviceStatusFlags::DRIVER_OK);
|
||||
}
|
||||
|
||||
pub fn setup_queue(&self, vector: u16, irq_handle: &File) -> Result<Arc<Queue<'a>>, Error> {
|
||||
fn setup_queue(&self, vector: u16, irq_handle: &File) -> Result<Arc<Queue>, Error> {
|
||||
let mut common = self.common.lock().unwrap();
|
||||
|
||||
let queue_index = self.queue_index.fetch_add(1, Ordering::SeqCst);
|
||||
@@ -464,17 +775,7 @@ impl<'a> StandardTransport<'a> {
|
||||
};
|
||||
|
||||
let avail = Available::new(queue_size)?;
|
||||
let mut used = Used::new(queue_size)?;
|
||||
|
||||
for i in 0..queue_size {
|
||||
// XXX: Fill the `table_index` of the elements with `T::MAX` to help with
|
||||
// debugging since qemu reports them as illegal values.
|
||||
avail
|
||||
.get_element_at(i)
|
||||
.table_index
|
||||
.store(u16::MAX, Ordering::Relaxed);
|
||||
used.get_mut_element_at(i).table_index.set(u32::MAX);
|
||||
}
|
||||
let used = Used::new(queue_size)?;
|
||||
|
||||
common.queue_desc.set(descriptor.physical() as u64);
|
||||
common.queue_driver.set(avail.phys_addr() as u64);
|
||||
@@ -498,37 +799,24 @@ impl<'a> StandardTransport<'a> {
|
||||
descriptor,
|
||||
avail,
|
||||
used,
|
||||
notification_bell,
|
||||
StandardBell(notification_bell),
|
||||
queue_index,
|
||||
vector,
|
||||
);
|
||||
|
||||
let queue_copy = queue.clone();
|
||||
let irq_fd = irq_handle.as_raw_fd();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let mut event_queue = EventQueue::<usize>::new().unwrap();
|
||||
|
||||
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();
|
||||
}
|
||||
Ok(None)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
event_queue.run().unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
spawn_irq_thread(irq_handle, &queue)?;
|
||||
Ok(queue)
|
||||
}
|
||||
|
||||
fn insert_status(&self, status: DeviceStatusFlags) {
|
||||
let mut common = self.common.lock().unwrap();
|
||||
let old = common.device_status.get();
|
||||
|
||||
common.device_status.set(old | status);
|
||||
}
|
||||
|
||||
/// Re-initializes a queue; usually done after a device reset.
|
||||
pub fn reinit_queue(&self, queue: Arc<Queue>) {
|
||||
fn reinit_queue(&self, queue: Arc<Queue>) {
|
||||
let mut common = self.common.lock().unwrap();
|
||||
queue.reinit();
|
||||
|
||||
@@ -546,3 +834,6 @@ impl<'a> StandardTransport<'a> {
|
||||
common.queue_enable.set(1);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for StandardTransport<'_> {}
|
||||
unsafe impl Sync for StandardTransport<'_> {}
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct VolatileCell<T> {
|
||||
|
||||
impl<T: Copy> VolatileCell<T> {
|
||||
#[inline]
|
||||
pub fn new(value: T) -> Self {
|
||||
pub const fn new(value: T) -> Self {
|
||||
Self {
|
||||
value: UnsafeCell::new(value),
|
||||
}
|
||||
|
||||
+22
-13
@@ -9,7 +9,7 @@ use common::dma::Dma;
|
||||
use syscall::{Error as SysError, SchemeMut, EAGAIN, EINVAL};
|
||||
|
||||
use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags};
|
||||
use virtio_core::transport::{Error, Queue, StandardTransport};
|
||||
use virtio_core::transport::{Error, Queue, Transport};
|
||||
use virtio_core::utils::VolatileCell;
|
||||
|
||||
use crate::*;
|
||||
@@ -30,7 +30,7 @@ impl Into<GpuRect> for &Damage {
|
||||
pub struct Display<'a> {
|
||||
control_queue: Arc<Queue<'a>>,
|
||||
cursor_queue: Arc<Queue<'a>>,
|
||||
transport: Arc<StandardTransport<'a>>,
|
||||
transport: Arc<dyn Transport>,
|
||||
|
||||
// TODO(andypython): Remove the need for the spin crate after the `once_cell`
|
||||
// API is stabilized.
|
||||
@@ -49,7 +49,7 @@ impl<'a> Display<'a> {
|
||||
pub fn new(
|
||||
control_queue: Arc<Queue<'a>>,
|
||||
cursor_queue: Arc<Queue<'a>>,
|
||||
transport: Arc<StandardTransport<'a>>,
|
||||
transport: Arc<dyn Transport>,
|
||||
id: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -250,7 +250,10 @@ impl<'a> Display<'a> {
|
||||
}
|
||||
|
||||
enum Handle<'a> {
|
||||
Vt { display: Arc<Display<'a>>, vt: usize },
|
||||
Vt {
|
||||
display: Arc<Display<'a>>,
|
||||
vt: usize,
|
||||
},
|
||||
Input,
|
||||
}
|
||||
|
||||
@@ -266,7 +269,7 @@ impl<'a> Scheme<'a> {
|
||||
config: &'a mut GpuConfig,
|
||||
control_queue: Arc<Queue<'a>>,
|
||||
cursor_queue: Arc<Queue<'a>>,
|
||||
transport: Arc<StandardTransport<'a>>,
|
||||
transport: Arc<dyn Transport>,
|
||||
) -> Result<Scheme<'a>, Error> {
|
||||
let displays = Self::probe(
|
||||
control_queue.clone(),
|
||||
@@ -286,7 +289,7 @@ impl<'a> Scheme<'a> {
|
||||
async fn probe(
|
||||
control_queue: Arc<Queue<'a>>,
|
||||
cursor_queue: Arc<Queue<'a>>,
|
||||
transport: Arc<StandardTransport<'a>>,
|
||||
transport: Arc<dyn Transport>,
|
||||
config: &GpuConfig,
|
||||
) -> Result<Vec<Arc<Display<'a>>>, Error> {
|
||||
let mut display_info = Self::get_display_info(control_queue.clone()).await?;
|
||||
@@ -353,7 +356,13 @@ impl<'a> SchemeMut for Scheme<'a> {
|
||||
let display = self.displays.get(id).ok_or(SysError::new(EINVAL))?;
|
||||
|
||||
let fd = self.next_id.fetch_add(1, Ordering::SeqCst);
|
||||
self.handles.insert(fd, Handle::Vt {display: display.clone(), vt });
|
||||
self.handles.insert(
|
||||
fd,
|
||||
Handle::Vt {
|
||||
display: display.clone(),
|
||||
vt,
|
||||
},
|
||||
);
|
||||
Ok(fd)
|
||||
}
|
||||
|
||||
@@ -440,9 +449,9 @@ impl<'a> SchemeMut for Scheme<'a> {
|
||||
let target_vt = vt;
|
||||
|
||||
for handle in self.handles.values() {
|
||||
if let Handle::Vt { display , vt } = handle {
|
||||
if *vt != target_vt {
|
||||
continue;
|
||||
if let Handle::Vt { display, vt } = handle {
|
||||
if *vt != target_vt {
|
||||
continue;
|
||||
}
|
||||
|
||||
futures::executor::block_on(display.init()).unwrap();
|
||||
@@ -452,9 +461,9 @@ impl<'a> SchemeMut for Scheme<'a> {
|
||||
|
||||
DisplayCommand::Deactivate(target_vt) => {
|
||||
for handle in self.handles.values() {
|
||||
if let Handle::Vt { display , vt } = handle {
|
||||
if *vt != target_vt {
|
||||
continue;
|
||||
if let Handle::Vt { display, vt } = handle {
|
||||
if *vt != target_vt {
|
||||
continue;
|
||||
}
|
||||
|
||||
futures::executor::block_on(display.detach()).unwrap();
|
||||
|
||||
@@ -9,7 +9,7 @@ use pcid_interface::PcidServerHandle;
|
||||
use syscall::{Packet, SchemeBlockMut};
|
||||
|
||||
use virtio_core::spec::VIRTIO_NET_F_MAC;
|
||||
use virtio_core::transport::Error;
|
||||
use virtio_core::transport::{Error, Transport};
|
||||
|
||||
use scheme::NetworkScheme;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user