virtio::transport: setup queue

Signed-off-by: Anhad Singh <andypython@protonmail.com>
This commit is contained in:
Anhad Singh
2023-06-20 14:14:22 +10:00
parent 990a9c2716
commit 8fb5e353ea
4 changed files with 180 additions and 35 deletions
+60 -18
View File
@@ -1,9 +1,25 @@
//! https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.html
use core::cell::UnsafeCell;
use static_assertions::const_assert_eq;
pub mod transport;
pub mod utils;
use utils::{IncompleteArrayField, VolatileCell};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("capability {0:?} not found")]
InCapable(CfgType),
#[error("failed to map memory")]
Physmap,
#[error("failed to allocate an interrupt vector")]
ExhaustedInt,
#[error("syscall error")]
SyscallError(syscall::Error),
}
#[derive(Debug, Copy, Clone)]
#[repr(u8)]
@@ -124,24 +140,50 @@ const_assert_eq!(core::mem::size_of::<Descriptor>(), 16);
/// specification for more information.
pub const VIRTIO_F_VERSION_1: u32 = 32;
pub struct VirtQueue {}
#[derive(Debug)]
#[repr(transparent)]
pub struct VolatileCell<T> {
value: UnsafeCell<T>,
// ======== Available Ring ========
#[repr(C)]
pub struct AvailableRingElement {
pub table_index: VolatileCell<u16>,
}
impl<T: Copy> VolatileCell<T> {
/// Returns a copy of the contained value.
#[inline]
pub fn get(&self) -> T {
unsafe { core::ptr::read_volatile(self.value.get()) }
}
const_assert_eq!(core::mem::size_of::<AvailableRingElement>(), 2);
/// Sets the contained value.
#[inline]
pub fn set(&mut self, value: T) {
unsafe { core::ptr::write_volatile(self.value.get(), value) }
}
/// Virtqueue Available Ring
#[repr(C)]
pub struct AvailableRing {
pub flags: VolatileCell<u16>,
pub head_index: VolatileCell<u16>,
pub elements: IncompleteArrayField<AvailableRingElement>,
}
const_assert_eq!(core::mem::size_of::<AvailableRing>(), 4);
#[repr(C)]
pub struct AvailableRingExtra {
pub avail_event: VolatileCell<u16>, // Only if `VIRTIO_F_EVENT_IDX`
}
const_assert_eq!(core::mem::size_of::<AvailableRingExtra>(), 2);
// ======== Used Ring ========
#[repr(C)]
pub struct UsedRingElement {
pub table_index: VolatileCell<u32>,
pub written: VolatileCell<u32>,
}
const_assert_eq!(core::mem::size_of::<UsedRingElement>(), 8);
#[repr(C)]
pub struct UsedRing {
pub flags: VolatileCell<u16>,
pub head_index: VolatileCell<u16>,
pub elements: IncompleteArrayField<UsedRingElement>,
}
const_assert_eq!(core::mem::size_of::<UsedRing>(), 4);
#[repr(C)]
pub struct UsedRingExtra {
pub event_index: VolatileCell<u16>,
}
+8 -15
View File
@@ -1,7 +1,8 @@
#![deny(trivial_numeric_casts, unused_allocation)]
use core::ptr::NonNull;
use static_assertions::const_assert_eq;
use thiserror::Error;
use virtiod::transport::StandardTransport;
use virtiod::*;
@@ -13,6 +14,7 @@ use pcid_interface::msi::{MsixCapability, MsixTableEntry};
use pcid_interface::*;
use syscall::{Io, PHYSMAP_NO_CACHE, PHYSMAP_WRITE};
use virtiod::utils::VolatileCell;
use std::ops::{Add, Div, Rem};
@@ -95,17 +97,7 @@ impl MsixInfo {
const_assert_eq!(std::mem::size_of::<MsixTableEntry>(), 16);
#[derive(Debug, Copy, Clone, Error)]
enum Error {
#[error("capability {0:?} not found")]
InCapable(CfgType),
#[error("failed to map memory")]
Physmap,
#[error("failed to allocate an interrupt vector")]
ExhaustedInt,
}
fn enable_msix(pcid_handle: &mut PcidServerHandle) -> anyhow::Result<()> {
fn enable_msix(pcid_handle: &mut PcidServerHandle) -> anyhow::Result<u8> {
let pci_config = pcid_handle.fetch_config()?;
// Extended message signaled interrupts.
@@ -186,7 +178,7 @@ fn enable_msix(pcid_handle: &mut PcidServerHandle) -> anyhow::Result<()> {
pcid_handle.enable_feature(PciFeature::MsiX)?;
log::info!("virtio: using MSI-X (vector={vector}, interrupt_handle={interrupt_handle:?})");
Ok(())
Ok(vector)
}
fn deamon(_deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
@@ -282,7 +274,7 @@ fn deamon(_deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
log::info!("virtio: successfully reseted the device");
// XXX: According to the virtio specification v1.2, setting the ACKNOWLEDGE and DRIVER bits
// in `device_status` are required to be done in two steps.
// in `device_status` is required to be done in two steps.
common
.device_status
.set(common.device_status.get() | DeviceStatusFlags::ACKNOWLEDGE);
@@ -299,13 +291,14 @@ fn deamon(_deamon: redox_daemon::Daemon) -> anyhow::Result<()> {
// According to the virtio specification, the device REQUIRED to support MSI-X.
assert!(has_msix, "virtio: device does not support MSI-X");
enable_msix(&mut pcid_handle)?;
let msix_vector = enable_msix(&mut pcid_handle)?;
log::info!("virtio: using standard PCI transport");
let mut transport = StandardTransport::new(pci_header, common);
transport.finalize_features();
let queue = transport.setup_queue(msix_vector.into())?;
loop {}
}
+74 -2
View File
@@ -1,14 +1,26 @@
use crate::utils::align;
use crate::*;
use pcid_interface::PciHeader;
use syscall::{Dma, PhysBox};
use core::sync::atomic::{AtomicU16, Ordering};
pub struct StandardTransport<'a> {
header: PciHeader,
common: &'a mut CommonCfg,
queue_index: AtomicU16,
}
impl<'a> StandardTransport<'a> {
pub fn new(header: PciHeader, common: &'a mut CommonCfg) -> Self {
Self { header, common }
Self {
header,
common,
queue_index: AtomicU16::new(0),
}
}
pub fn check_device_feature(&mut self, feature: u32) -> bool {
@@ -34,9 +46,69 @@ impl<'a> StandardTransport<'a> {
.device_status
.set(self.common.device_status.get() | DeviceStatusFlags::FEATURES_OK);
// Re-read device status to ensure the `FEATURES_OK` bit is still set: otherwise,
// Re-read device status to ensure the `FEATURES_OK` bit is still set: otherwise,
// the device does not support our subset of features and the device is unusable.
let confirm = self.common.device_status.get();
assert!((confirm & DeviceStatusFlags::FEATURES_OK) == DeviceStatusFlags::FEATURES_OK);
}
pub fn setup_queue(&mut self, vector: u16) -> anyhow::Result<()> {
let queue_index = self.queue_index.fetch_add(1, Ordering::SeqCst);
self.common.queue_select.set(queue_index);
let queue_size = self.common.queue_size.get() as usize;
let queue_notify_idx = self.common.queue_notify_off.get();
assert!(queue_size != 0 && queue_size.is_power_of_two());
// Get the queue size in bytes.
//
// Section 2.7 Split Virtqueues of the specfication describe the alignment
// and size of the queues.
const AVAILABLE_ALIGN: usize = 2;
const USED_ALIGN: usize = 4;
let table_size: usize = align(
(queue_size as usize) * core::mem::size_of::<Descriptor>(),
AVAILABLE_ALIGN,
);
let available_size = align(
(queue_size as usize * core::mem::size_of::<AvailableRingElement>())
+ core::mem::size_of::<AvailableRingExtra>(),
USED_ALIGN,
);
let used_size = (queue_size as usize) * core::mem::size_of::<UsedRingElement>()
+ core::mem::size_of::<UsedRingExtra>();
// Allocate memory for the queue structues.
let table = unsafe {
Dma::<[Descriptor]>::zeroed_unsized(table_size)
.map_err(|err| Error::SyscallError(err))?
};
let avaliable = unsafe {
Dma::<[AvailableRing]>::zeroed_unsized(available_size)
.map_err(|err| Error::SyscallError(err))?
};
let used = unsafe {
Dma::<[UsedRing]>::zeroed_unsized(used_size).map_err(|err| Error::SyscallError(err))?
};
self.common.queue_desc.set(table.physical() as u64);
self.common.queue_driver.set(avaliable.physical() as u64);
self.common.queue_device.set(used.physical() as u64);
// Set the MSI-X vector.
self.common.queue_msix_vector.set(vector);
assert!(self.common.queue_msix_vector.get() != 0);
// Enable the queue.
self.common.queue_enable.set(1);
log::info!("virtio: enabled queue #{queue_index} (size={queue_size})");
Ok(())
}
}
+38
View File
@@ -0,0 +1,38 @@
use core::cell::UnsafeCell;
use core::marker::PhantomData;
use static_assertions::const_assert_eq;
#[derive(Debug)]
#[repr(transparent)]
pub struct VolatileCell<T> {
value: UnsafeCell<T>,
}
impl<T: Copy> VolatileCell<T> {
/// Returns a copy of the contained value.
#[inline]
pub fn get(&self) -> T {
unsafe { core::ptr::read_volatile(self.value.get()) }
}
/// Sets the contained value.
#[inline]
pub fn set(&mut self, value: T) {
unsafe { core::ptr::write_volatile(self.value.get(), value) }
}
}
#[repr(C)]
pub struct IncompleteArrayField<T>(PhantomData<T>, [T; 0]);
impl<T> IncompleteArrayField<T> {
#[inline]
pub const fn new() -> Self {
IncompleteArrayField(PhantomData, [])
}
}
pub const fn align(val: usize, align: usize) -> usize {
(val + align) & !align
}