virtio::transport: setup queue
Signed-off-by: Anhad Singh <andypython@protonmail.com>
This commit is contained in:
+60
-18
@@ -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
@@ -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 {}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user