From 8fb5e353eae02109ddd9789e27e5b69a367c4783 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Tue, 20 Jun 2023 14:14:22 +1000 Subject: [PATCH] virtio::transport: setup queue Signed-off-by: Anhad Singh --- virtiod/src/lib.rs | 78 ++++++++++++++++++++++++++++++---------- virtiod/src/main.rs | 23 +++++------- virtiod/src/transport.rs | 76 +++++++++++++++++++++++++++++++++++++-- virtiod/src/utils.rs | 38 ++++++++++++++++++++ 4 files changed, 180 insertions(+), 35 deletions(-) create mode 100644 virtiod/src/utils.rs diff --git a/virtiod/src/lib.rs b/virtiod/src/lib.rs index ee820f957a..b58688aa46 100644 --- a/virtiod/src/lib.rs +++ b/virtiod/src/lib.rs @@ -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::(), 16); /// specification for more information. pub const VIRTIO_F_VERSION_1: u32 = 32; -pub struct VirtQueue {} - -#[derive(Debug)] -#[repr(transparent)] -pub struct VolatileCell { - value: UnsafeCell, +// ======== Available Ring ======== +#[repr(C)] +pub struct AvailableRingElement { + pub table_index: VolatileCell, } -impl VolatileCell { - /// 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::(), 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, + pub head_index: VolatileCell, + pub elements: IncompleteArrayField, +} + +const_assert_eq!(core::mem::size_of::(), 4); + +#[repr(C)] +pub struct AvailableRingExtra { + pub avail_event: VolatileCell, // Only if `VIRTIO_F_EVENT_IDX` +} + +const_assert_eq!(core::mem::size_of::(), 2); + +// ======== Used Ring ======== +#[repr(C)] +pub struct UsedRingElement { + pub table_index: VolatileCell, + pub written: VolatileCell, +} + +const_assert_eq!(core::mem::size_of::(), 8); + +#[repr(C)] +pub struct UsedRing { + pub flags: VolatileCell, + pub head_index: VolatileCell, + pub elements: IncompleteArrayField, +} + +const_assert_eq!(core::mem::size_of::(), 4); + +#[repr(C)] +pub struct UsedRingExtra { + pub event_index: VolatileCell, } diff --git a/virtiod/src/main.rs b/virtiod/src/main.rs index c3f32b42c8..8d877206bc 100644 --- a/virtiod/src/main.rs +++ b/virtiod/src/main.rs @@ -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::(), 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 { 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 {} } diff --git a/virtiod/src/transport.rs b/virtiod/src/transport.rs index 4ee344dd25..f46c45aa7e 100644 --- a/virtiod/src/transport.rs +++ b/virtiod/src/transport.rs @@ -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::(), + AVAILABLE_ALIGN, + ); + + let available_size = align( + (queue_size as usize * core::mem::size_of::()) + + core::mem::size_of::(), + USED_ALIGN, + ); + + let used_size = (queue_size as usize) * core::mem::size_of::() + + core::mem::size_of::(); + + // 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(()) + } } diff --git a/virtiod/src/utils.rs b/virtiod/src/utils.rs new file mode 100644 index 0000000000..3686e77ed9 --- /dev/null +++ b/virtiod/src/utils.rs @@ -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 { + value: UnsafeCell, +} + +impl VolatileCell { + /// 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(PhantomData, [T; 0]); + +impl IncompleteArrayField { + #[inline] + pub const fn new() -> Self { + IncompleteArrayField(PhantomData, []) + } +} + +pub const fn align(val: usize, align: usize) -> usize { + (val + align) & !align +}