From eee780fa0fe692fdaa6f651bc15eb4e25148caad Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Tue, 27 Jun 2023 09:51:16 +1000 Subject: [PATCH 1/7] virtiod: split into virtio-core and virtiod Signed-off-by: Anhad Singh --- Cargo.lock | 14 +- virtio-core/Cargo.toml | 14 + virtio-core/src/lib.rs | 9 + virtio-core/src/probe.rs | 271 ++++++++++++++++ virtiod/src/lib.rs => virtio-core/src/spec.rs | 28 +- {virtiod => virtio-core}/src/transport.rs | 42 ++- {virtiod => virtio-core}/src/utils.rs | 0 virtiod/Cargo.toml | 4 +- virtiod/VIRTIO.md | 20 -- virtiod/src/main.rs | 299 ++---------------- virtiod/src/scheme.rs | 12 +- 11 files changed, 378 insertions(+), 335 deletions(-) create mode 100644 virtio-core/Cargo.toml create mode 100644 virtio-core/src/lib.rs create mode 100644 virtio-core/src/probe.rs rename virtiod/src/lib.rs => virtio-core/src/spec.rs (93%) rename {virtiod => virtio-core}/src/transport.rs (92%) rename {virtiod => virtio-core}/src/utils.rs (100%) delete mode 100644 virtiod/VIRTIO.md diff --git a/Cargo.lock b/Cargo.lock index b16ea5ef70..252bb500b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1904,12 +1904,23 @@ dependencies = [ "rusttype", ] +[[package]] +name = "virtio-core" +version = "0.1.0" +dependencies = [ + "bitflags 2.3.2", + "log", + "pcid", + "redox_syscall 0.3.5", + "static_assertions", + "thiserror", +] + [[package]] name = "virtiod" version = "0.1.0" dependencies = [ "anyhow", - "bitflags 2.3.2", "block-io-wrapper", "log", "partitionlib", @@ -1920,6 +1931,7 @@ dependencies = [ "redox_syscall 0.3.5", "static_assertions", "thiserror", + "virtio-core", ] [[package]] diff --git a/virtio-core/Cargo.toml b/virtio-core/Cargo.toml new file mode 100644 index 0000000000..1ffd4a4bb7 --- /dev/null +++ b/virtio-core/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "virtio-core" +version = "0.1.0" +edition = "2021" +authors = ["Anhad Singh "] + +[dependencies] +static_assertions = "1.1.0" +bitflags = "2.3.2" +redox_syscall = "0.3" +log = "0.4" +thiserror = "1.0.40" + +pcid = { path = "../pcid" } diff --git a/virtio-core/src/lib.rs b/virtio-core/src/lib.rs new file mode 100644 index 0000000000..626c2ecf21 --- /dev/null +++ b/virtio-core/src/lib.rs @@ -0,0 +1,9 @@ +#![feature(int_roundings)] + +pub mod spec; +pub mod transport; +pub mod utils; + +mod probe; + +pub use probe::{probe_device, MSIX_PRIMARY_VECTOR, Device}; diff --git a/virtio-core/src/probe.rs b/virtio-core/src/probe.rs new file mode 100644 index 0000000000..2f91eabb71 --- /dev/null +++ b/virtio-core/src/probe.rs @@ -0,0 +1,271 @@ +use std::fs::File; +use std::ptr::NonNull; +use std::sync::Arc; + +use pcid_interface::irq_helpers::{allocate_single_interrupt_vector, read_bsp_apic_id}; +use pcid_interface::msi::x86_64 as x86_64_msix; +use pcid_interface::msi::x86_64::DeliveryMode; +use pcid_interface::msi::{MsixCapability, MsixTableEntry}; +use pcid_interface::*; + +use syscall::{Io, PHYSMAP_NO_CACHE, PHYSMAP_WRITE}; + +use crate::spec::*; +use crate::transport::{Error, StandardTransport}; +use crate::utils::VolatileCell; + +pub struct Device<'a> { + pub transport: Arc>, + pub device_space: *const u8, + pub irq_handle: File, + pub isr: &'a VolatileCell, +} + +struct MsixInfo { + pub virt_table_base: NonNull, + pub virt_pba_base: NonNull, + pub capability: MsixCapability, +} + +impl MsixInfo { + pub unsafe fn table_entry_pointer_unchecked(&mut self, k: usize) -> &mut MsixTableEntry { + &mut *self.virt_table_base.as_ptr().add(k) + } + + pub fn table_entry_pointer(&mut self, k: usize) -> &mut MsixTableEntry { + assert!(k < self.capability.table_size() as usize); + unsafe { self.table_entry_pointer_unchecked(k) } + } +} + +static_assertions::const_assert_eq!(std::mem::size_of::(), 16); + +pub const MSIX_PRIMARY_VECTOR: u16 = 0; + +fn enable_msix(pcid_handle: &mut PcidServerHandle) -> Result { + let pci_config = pcid_handle.fetch_config()?; + + // Extended message signaled interrupts. + let capability = match pcid_handle.feature_info(PciFeature::MsiX)? { + PciFeatureInfo::MsiX(capability) => capability, + _ => unreachable!(), + }; + + let table_size = capability.table_size(); + let table_base = capability.table_base_pointer(pci_config.func.bars); + let table_min_length = table_size * 16; + let pba_min_length = table_size.div_ceil(8); + + let pba_base = capability.pba_base_pointer(pci_config.func.bars); + + let bir = capability.table_bir() as usize; + let bar = pci_config.func.bars[bir]; + let bar_size = pci_config.func.bar_sizes[bir] as u64; + + let bar_ptr = match bar { + PciBar::Memory32(ptr) => ptr.into(), + PciBar::Memory64(ptr) => ptr, + _ => unreachable!(), + }; + + let address = unsafe { + syscall::physmap( + bar_ptr as usize, + bar_size as usize, + PHYSMAP_WRITE | PHYSMAP_NO_CACHE, + )? + }; + + // Ensure that the table and PBA are be within the BAR. + { + let bar_range = bar_ptr..bar_ptr + bar_size; + assert!(bar_range.contains(&(table_base as u64 + table_min_length as u64))); + assert!(bar_range.contains(&(pba_base as u64 + pba_min_length as u64))); + } + + let virt_table_base = ((table_base - bar_ptr as usize) + address) as *mut MsixTableEntry; + let virt_pba_base = ((pba_base - bar_ptr as usize) + address) as *mut u64; + + let mut info = MsixInfo { + virt_table_base: NonNull::new(virt_table_base).unwrap(), + virt_pba_base: NonNull::new(virt_pba_base).unwrap(), + capability, + }; + + // Allocate the primary MSI vector. + let interrupt_handle = { + let table_entry_pointer = info.table_entry_pointer(MSIX_PRIMARY_VECTOR as usize); + + let destination_id = read_bsp_apic_id().expect("virtio_core: `read_bsp_apic_id()` failed"); + let lapic_id = u8::try_from(destination_id).unwrap(); + + let rh = false; + let dm = false; + let addr = x86_64_msix::message_address(lapic_id, rh, dm); + + let (vector, interrupt_handle) = allocate_single_interrupt_vector(destination_id) + .unwrap() + .expect("virtio_core: interrupt vector exhaustion"); + + let msg_data = x86_64_msix::message_data_edge_triggered(DeliveryMode::Fixed, vector); + + table_entry_pointer.addr_lo.write(addr); + table_entry_pointer.addr_hi.write(0); + table_entry_pointer.msg_data.write(msg_data); + table_entry_pointer + .vec_ctl + .writef(MsixTableEntry::VEC_CTL_MASK_BIT, false); + + interrupt_handle + }; + + pcid_handle.enable_feature(PciFeature::MsiX)?; + + log::info!("virtio: using MSI-X (interrupt_handle={interrupt_handle:?})"); + Ok(interrupt_handle) +} + +/// VirtIO Device Probe +/// +/// ## Device State +/// After this function, the device has been successfully reseted and is ready for use. +/// +/// The caller is required to do the following: +/// * Negotiate the device and driver supported features (finialize via [`StandardTransport::finalize_features`]) +/// * Create the device specific virtio queues (via [`StandardTransport::setup_queue`]) +/// * Finally start the device (via [`StandardTransport::run_device`]). At this point, the device +/// is alive. +/// +/// ## Panics +/// This function panics if the device is not a virtio device. +pub fn probe_device<'a>(pcid_handle: &mut PcidServerHandle) -> Result, Error> { + let pci_config = pcid_handle.fetch_config()?; + let pci_header = pcid_handle.fetch_header()?; + + assert_eq!( + pci_config.func.venid, 6900, + "virtio_core::probe_device: not a virtio device" + ); + + let mut common_addr = None; + let mut notify_addr = None; + let mut isr_addr = None; + let mut device_addr = None; + + for capability in pcid_handle + .get_capabilities()? + .iter() + .filter_map(|capability| { + if let Capability::Vendor(vendor) = capability { + Some(vendor) + } else { + None + } + }) + { + // SAFETY: We have verified that the length of the data is correct. + let capability = unsafe { &*(capability.data.as_ptr() as *const PciCapability) }; + + match capability.cfg_type { + CfgType::Common | CfgType::Notify | CfgType::Isr | CfgType::Device => {} + _ => continue, + } + + let bar = pci_header.get_bar(capability.bar as usize); + let addr = match bar { + PciBar::Memory32(addr) => addr as usize, + PciBar::Memory64(addr) => addr as usize, + + _ => unreachable!("virtio: unsupported bar type: {bar:?}"), + }; + + let address = unsafe { + syscall::physmap( + addr + capability.offset as usize, + capability.length as usize, + PHYSMAP_WRITE | PHYSMAP_NO_CACHE, + )? + }; + + match capability.cfg_type { + CfgType::Common => { + debug_assert!(common_addr.is_none()); + common_addr = Some(address); + } + + CfgType::Notify => { + debug_assert!(notify_addr.is_none()); + + // SAFETY: The capability type is `Notify`, so its safe to access + // the `notify_multiplier` field. + let multiplier = unsafe { capability.notify_multiplier() }; + notify_addr = Some((address, multiplier)); + } + + CfgType::Isr => { + debug_assert!(isr_addr.is_none()); + isr_addr = Some(address); + } + + CfgType::Device => { + debug_assert!(device_addr.is_none()); + device_addr = Some(address); + } + + _ => unreachable!(), + } + + log::info!("virtio: {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))?; + + 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) }; + + // 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); + + // 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)?; + + log::info!("virtio: using standard PCI transport"); + + let transport = StandardTransport::new(common, notify_addr as *const u8, notify_multiplier); + + Ok(Device { + transport, + device_space, + irq_handle, + isr, + }) +} diff --git a/virtiod/src/lib.rs b/virtio-core/src/spec.rs similarity index 93% rename from virtiod/src/lib.rs rename to virtio-core/src/spec.rs index 04f8e74492..f494714f14 100644 --- a/virtiod/src/lib.rs +++ b/virtio-core/src/spec.rs @@ -1,28 +1,8 @@ //! https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.html -#![feature(int_roundings)] - +use crate::utils::{IncompleteArrayField, VolatileCell}; 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)] pub enum CfgType { @@ -225,9 +205,9 @@ pub struct UsedRingExtra { // ======== Utils ======== pub struct Buffer { - buffer: usize, - size: usize, - flags: DescriptorFlags, + pub(crate) buffer: usize, + pub(crate) size: usize, + pub(crate) flags: DescriptorFlags, } impl Buffer { diff --git a/virtiod/src/transport.rs b/virtio-core/src/transport.rs similarity index 92% rename from virtiod/src/transport.rs rename to virtio-core/src/transport.rs index 4ee9753f26..529de32ab1 100644 --- a/virtiod/src/transport.rs +++ b/virtio-core/src/transport.rs @@ -1,7 +1,6 @@ -use crate::utils::align; -use crate::*; +use crate::spec::*; +use crate::utils::{align, VolatileCell}; -use pcid_interface::PciHeader; use syscall::{Dma, PHYSMAP_WRITE}; use core::mem::size_of; @@ -10,6 +9,28 @@ use core::sync::atomic::{fence, AtomicU16, Ordering}; use std::collections::VecDeque; use std::sync::{Arc, Mutex, Weak}; +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("syscall failed")] + SyscallError(syscall::Error), + #[error("pcid client handle error")] + PcidClientHandle(pcid_interface::PcidClientHandleError), + #[error("the device is incapable of {0:?}")] + InCapable(CfgType), +} + +impl From for Error { + fn from(value: pcid_interface::PcidClientHandleError) -> Self { + Self::PcidClientHandle(value) + } +} + +impl From for Error { + fn from(value: syscall::Error) -> Self { + Self::SyscallError(value) + } +} + /// Returns the queue part sizes in bytes. /// /// ## Reference @@ -167,7 +188,7 @@ pub struct Available<'a> { } impl<'a> Available<'a> { - pub fn new(queue_size: usize) -> anyhow::Result { + pub fn new(queue_size: usize) -> Result { let (_, size, _) = queue_part_sizes(queue_size); let size = size.next_multiple_of(syscall::PAGE_SIZE); // align to page size @@ -229,7 +250,7 @@ pub struct Used<'a> { } impl<'a> Used<'a> { - pub fn new(queue_size: usize) -> anyhow::Result { + pub fn new(queue_size: usize) -> Result { let (_, _, size) = queue_part_sizes(queue_size); let size = size.next_multiple_of(syscall::PAGE_SIZE); // align to page size @@ -286,7 +307,6 @@ impl Drop for Used<'_> { } pub struct StandardTransport<'a> { - header: PciHeader, common: Mutex<&'a mut CommonCfg>, notify: *const u8, notify_mul: u32, @@ -296,14 +316,8 @@ pub struct StandardTransport<'a> { } impl<'a> StandardTransport<'a> { - pub fn new( - header: PciHeader, - common: &'a mut CommonCfg, - notify: *const u8, - notify_mul: u32, - ) -> Arc { + pub fn new(common: &'a mut CommonCfg, notify: *const u8, notify_mul: u32) -> Arc { Arc::new_cyclic(|sref| Self { - header, common: Mutex::new(common), notify, notify_mul, @@ -362,7 +376,7 @@ impl<'a> StandardTransport<'a> { .set(status | DeviceStatusFlags::DRIVER_OK); } - pub fn setup_queue(&self, vector: u16) -> anyhow::Result>> { + pub fn setup_queue(&self, vector: u16) -> Result>, Error> { let mut common = self.common.lock().unwrap(); let queue_index = self.queue_index.fetch_add(1, Ordering::SeqCst); diff --git a/virtiod/src/utils.rs b/virtio-core/src/utils.rs similarity index 100% rename from virtiod/src/utils.rs rename to virtio-core/src/utils.rs diff --git a/virtiod/Cargo.toml b/virtiod/Cargo.toml index 2594590c4b..74d3adce40 100644 --- a/virtiod/Cargo.toml +++ b/virtiod/Cargo.toml @@ -5,11 +5,10 @@ edition = "2021" authors = ["Anhad Singh "] [dependencies] -static_assertions = "1.1.0" -bitflags = "2.3.2" anyhow = "1.0.71" log = "0.4" thiserror = "1.0.40" +static_assertions = "1.1.0" redox-daemon = "0.1" redox-log = "0.1" @@ -19,3 +18,4 @@ partitionlib = { git = "https://gitlab.redox-os.org/redox-os/partitionlib.git" } block-io-wrapper = { path = "../block-io-wrapper" } pcid = { path = "../pcid" } +virtio-core = { path = "../virtio-core" } diff --git a/virtiod/VIRTIO.md b/virtiod/VIRTIO.md deleted file mode 100644 index a3c31a10a9..0000000000 --- a/virtiod/VIRTIO.md +++ /dev/null @@ -1,20 +0,0 @@ -## Generic -- [x] Reset the device. -- [x] Set the ACKNOWLEDGE status bit: the guest OS has noticed the device. -- [x] Set the DRIVER status bit: the guest OS knows how to drive the device. -- [x] Setup Interrupts - -## Driver Specific -- [x] Read device feature bits, and write the subset of feature bits understood by the OS and driver to the device. During this step the driver MAY read (but MUST NOT write) the device-specific configuration fields to check that it can support the device before accepting it. -- [x] Set the FEATURES_OK status bit. The driver MUST NOT accept new feature bits after this step. -- [x] 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. -- [x] Perform device-specific setup, including discovery of virtqueues for the device, optional per-bus setup, reading and possibly writing the device’s virtio configuration space, and population of virtqueues. -- [x] Set the DRIVER_OK status bit. At this point the device is “live”. - -## XXX -- [ ] Mark the deamon as ready. - -## Drivers -- [ ] `virtio-blk` (in-progress) -- [ ] `virtio-net` -- [ ] `virtio-gpu` diff --git a/virtiod/src/main.rs b/virtiod/src/main.rs index 11e1c38543..731dc77b96 100644 --- a/virtiod/src/main.rs +++ b/virtiod/src/main.rs @@ -1,8 +1,6 @@ #![deny(trivial_numeric_casts, unused_allocation)] #![feature(int_roundings)] -use core::ptr::NonNull; - use std::fs::File; use std::io; use std::io::{Read, Write}; @@ -10,22 +8,30 @@ use std::os::fd::{AsRawFd, FromRawFd, RawFd}; use static_assertions::const_assert_eq; -use virtiod::transport::StandardTransport; -use virtiod::*; - -use pcid_interface::irq_helpers::{allocate_single_interrupt_vector, read_bsp_apic_id}; -use pcid_interface::msi::x86_64 as x86_64_msix; -use pcid_interface::msi::x86_64::DeliveryMode; -use pcid_interface::msi::{MsixCapability, MsixTableEntry}; use pcid_interface::*; +use virtio_core::spec::*; use event::EventQueue; -use syscall::{Io, Packet, SchemeBlockMut, PHYSMAP_NO_CACHE, PHYSMAP_WRITE}; +use syscall::{Packet, SchemeBlockMut}; -use virtiod::utils::VolatileCell; +use virtio_core::utils::VolatileCell; mod scheme; +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), +} + pub fn main() -> anyhow::Result<()> { #[cfg(target_os = "redox")] setup_logging(); @@ -74,110 +80,6 @@ fn setup_logging() { log::info!("virtiod: enabled logger"); } -struct MsixInfo { - pub virt_table_base: NonNull, - pub virt_pba_base: NonNull, - pub capability: MsixCapability, -} - -impl MsixInfo { - pub unsafe fn table_entry_pointer_unchecked(&mut self, k: usize) -> &mut MsixTableEntry { - &mut *self.virt_table_base.as_ptr().add(k) - } - - pub fn table_entry_pointer(&mut self, k: usize) -> &mut MsixTableEntry { - assert!(k < self.capability.table_size() as usize); - unsafe { self.table_entry_pointer_unchecked(k) } - } -} - -const_assert_eq!(std::mem::size_of::(), 16); - -const MSIX_PRIMARY_VECTOR: u16 = 0; - -fn enable_msix(pcid_handle: &mut PcidServerHandle) -> anyhow::Result { - let pci_config = pcid_handle.fetch_config()?; - - // Extended message signaled interrupts. - let capability = match pcid_handle.feature_info(PciFeature::MsiX)? { - PciFeatureInfo::MsiX(capability) => capability, - _ => unreachable!(), - }; - - let table_size = capability.table_size(); - let table_base = capability.table_base_pointer(pci_config.func.bars); - let table_min_length = table_size * 16; - let pba_min_length = table_size.div_ceil(8); - - let pba_base = capability.pba_base_pointer(pci_config.func.bars); - - let bir = capability.table_bir() as usize; - let bar = pci_config.func.bars[bir]; - let bar_size = pci_config.func.bar_sizes[bir] as u64; - - let bar_ptr = match bar { - PciBar::Memory32(ptr) => ptr.into(), - PciBar::Memory64(ptr) => ptr, - _ => unreachable!(), - }; - - let address = unsafe { - syscall::physmap( - bar_ptr as usize, - bar_size as usize, - PHYSMAP_WRITE | PHYSMAP_NO_CACHE, - ) - .map_err(|_| Error::Physmap)? - }; - - // Ensure that the table and PBA are be within the BAR. - { - let bar_range = bar_ptr..bar_ptr + bar_size; - assert!(bar_range.contains(&(table_base as u64 + table_min_length as u64))); - assert!(bar_range.contains(&(pba_base as u64 + pba_min_length as u64))); - } - - let virt_table_base = ((table_base - bar_ptr as usize) + address) as *mut MsixTableEntry; - let virt_pba_base = ((pba_base - bar_ptr as usize) + address) as *mut u64; - - let mut info = MsixInfo { - virt_table_base: NonNull::new(virt_table_base).unwrap(), - virt_pba_base: NonNull::new(virt_pba_base).unwrap(), - capability, - }; - - // Allocate the primary MSI vector. - let interrupt_handle = { - let table_entry_pointer = info.table_entry_pointer(MSIX_PRIMARY_VECTOR as usize); - - let destination_id = read_bsp_apic_id()?; - let lapic_id = u8::try_from(destination_id).unwrap(); - - let rh = false; - let dm = false; - let addr = x86_64_msix::message_address(lapic_id, rh, dm); - - let (vector, interrupt_handle) = - allocate_single_interrupt_vector(destination_id)?.ok_or(Error::ExhaustedInt)?; - - let msg_data = x86_64_msix::message_data_edge_triggered(DeliveryMode::Fixed, vector); - - table_entry_pointer.addr_lo.write(addr); - table_entry_pointer.addr_hi.write(0); - table_entry_pointer.msg_data.write(msg_data); - table_entry_pointer - .vec_ctl - .writef(MsixTableEntry::VEC_CTL_MASK_BIT, false); - - interrupt_handle - }; - - pcid_handle.enable_feature(PciFeature::MsiX)?; - - log::info!("virtio: using MSI-X (interrupt_handle={interrupt_handle:?})"); - Ok(interrupt_handle) -} - #[repr(C)] pub struct BlockGeometry { pub cylinders: VolatileCell, @@ -224,148 +126,35 @@ const_assert_eq!(core::mem::size_of::(), 16); fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { let mut pcid_handle = PcidServerHandle::connect_default()?; - let pci_config = pcid_handle.fetch_config()?; - let pci_header = pcid_handle.fetch_header()?; + // Double check that we have the right device. + // // 0x1001 - virtio-blk + let pci_config = pcid_handle.fetch_config()?; + assert_eq!(pci_config.func.devid, 0x1001); log::info!("virtiod: found `virtio-blk` device"); - let mut common_addr = None; - let mut notify_addr = None; - let mut isr_addr = None; - let mut device_addr = None; + let mut device = virtio_core::probe_device(&mut pcid_handle)?; + device.transport.finalize_features(); - for capability in pcid_handle - .get_capabilities()? - .iter() - .filter_map(|capability| { - if let Capability::Vendor(vendor) = capability { - Some(vendor) - } else { - None - } - }) - { - // SAFETY: We have verified that the length of the data is correct. - let capability = unsafe { &*(capability.data.as_ptr() as *const PciCapability) }; - - match capability.cfg_type { - CfgType::Common | CfgType::Notify | CfgType::Isr | CfgType::Device => {} - _ => continue, - } - - let bar = pci_header.get_bar(capability.bar as usize); - let addr = match bar { - PciBar::Memory32(addr) => addr as usize, - PciBar::Memory64(addr) => addr as usize, - - _ => unreachable!("virtio: unsupported bar type: {bar:?}"), - }; - - let address = unsafe { - syscall::physmap( - addr + capability.offset as usize, - capability.length as usize, - PHYSMAP_WRITE | PHYSMAP_NO_CACHE, - ) - .map_err(|_| Error::Physmap)? - }; - - match capability.cfg_type { - CfgType::Common => { - debug_assert!(common_addr.is_none()); - common_addr = Some(address); - } - - CfgType::Notify => { - debug_assert!(notify_addr.is_none()); - - // SAFETY: The capability type is `Notify`, so its safe to access - // the `notify_multiplier` field. - let multiplier = unsafe { capability.notify_multiplier() }; - notify_addr = Some((address, multiplier)); - } - - CfgType::Isr => { - debug_assert!(isr_addr.is_none()); - isr_addr = Some(address); - } - - CfgType::Device => { - debug_assert!(device_addr.is_none()); - device_addr = Some(address); - } - - _ => unreachable!(), - } - - log::info!("virtio: {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))?; - - assert!( - notify_multiplier != 0, - "virtio: 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 BlockDeviceConfig) }; - let isr = unsafe { &*(isr_addr as *mut VolatileCell) }; - - // 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); - - // 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 mut irq_handle = enable_msix(&mut pcid_handle)?; - - log::info!("virtio: using standard PCI transport"); - - let transport = StandardTransport::new( - pci_header, - common, - notify_addr as *const u8, - notify_multiplier, - ); - transport.finalize_features(); - - let queue = transport.setup_queue(MSIX_PRIMARY_VECTOR)?; + let queue = device + .transport + .setup_queue(virtio_core::MSIX_PRIMARY_VECTOR)?; let queue_copy = queue.clone(); + let device_space = unsafe { &mut *(device.device_space as *mut BlockDeviceConfig) }; + std::thread::spawn(move || { let mut event_queue = EventQueue::::new().unwrap(); let mut progress_head = 0; event_queue .add( - irq_handle.as_raw_fd(), + device.irq_handle.as_raw_fd(), move |_| -> Result, io::Error> { // Read from ISR to acknowledge the interrupt. - let _isr = isr.get() as usize; + let _isr = device.isr.get() as usize; let mut inner = queue_copy.inner.lock().unwrap(); let used_head = inner.used.head_index(); @@ -394,7 +183,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { drop(inner); let mut buf = [0u8; 8]; - irq_handle.read(&mut buf)?; + device.irq_handle.read(&mut buf)?; // Acknowledge the interrupt. // irq_handle.write(&buf)?; Ok(None) @@ -408,7 +197,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { }); // At this point the device is alive! - transport.run_device(); + device.transport.run_device(); log::info!( "virtio-blk: disk size: {} sectors and block size of {} bytes", @@ -444,30 +233,6 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { .write(&mut packet) .expect("ahcid: failed to read disk scheme"); } - - // for _ in 0..3 { - // let req = syscall::Dma::new(BlockVirtRequest { - // ty: BlockRequestTy::In, - // reserved: 0, - // sector: 0, - // }) - // .unwrap(); - - // let result = syscall::Dma::new([0u8; 512]).unwrap(); - // let status = syscall::Dma::new(u8::MAX).unwrap(); - - // let chain = ChainBuilder::new() - // .chain(Buffer::new(&req).flags(DescriptorFlags::NEXT)) - // .chain(Buffer::new(&result).flags(DescriptorFlags::WRITE_ONLY | DescriptorFlags::NEXT)) - // .chain(Buffer::new(&status).flags(DescriptorFlags::WRITE_ONLY)) - // .build(); - - // queue.send(chain); - - // log::info!("{}", event_queue.run()?); - // log::info!("command status: {}", *status); - // log::info!("data: {:?}", result.as_ref()); - // } } fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! { diff --git a/virtiod/src/scheme.rs b/virtiod/src/scheme.rs index ad8b410766..a6247e2c6b 100644 --- a/virtiod/src/scheme.rs +++ b/virtiod/src/scheme.rs @@ -10,10 +10,9 @@ use std::sync::Arc; use partitionlib::LogicalBlockSize; use partitionlib::PartitionTable; use syscall::*; -use virtiod::transport::Queue; -use virtiod::Buffer; -use virtiod::ChainBuilder; -use virtiod::DescriptorFlags; + +use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags}; +use virtio_core::transport::Queue; use crate::BlockDeviceConfig; use crate::BlockRequestTy; @@ -28,6 +27,8 @@ trait BlkExtension { /// XXX: Reads only one block despite the size of the output buffer. Use [`BlkExtension::write`] instead. fn write_block(&self, block: u64, block_bytes: &[u8]) -> usize; + // FIXME(andypython): The following two methods can be done asynchronously (i.e, send all of the commands at once + // and wait for the result in the end). fn read(&self, block: u64, block_bytes: &mut [u8]) -> usize { let sectors = block_bytes.len() / BLK_SIZE as usize; @@ -72,8 +73,6 @@ impl BlkExtension for Queue<'_> { let size = core::cmp::min(block_bytes.len(), result.len()); block_bytes[..size].copy_from_slice(&result.as_slice()[..size]); - std::thread::yield_now(); - size } @@ -103,7 +102,6 @@ impl BlkExtension for Queue<'_> { core::hint::spin_loop(); } - std::thread::yield_now(); block_bytes.len() } } From 6968a548d95258237ac51c42303a1d258a59a6ac Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Tue, 27 Jun 2023 10:26:04 +1000 Subject: [PATCH 2/7] virtiod: rename to `virtio-blkd` * Rename to `virtio-blkd` * Start working on `virtio-netd` Signed-off-by: Anhad Singh --- Cargo.lock | 37 +++++++++------ Cargo.toml | 4 +- initfs.toml | 17 +++++-- {virtiod => virtio-blkd}/Cargo.toml | 2 +- {virtiod => virtio-blkd}/src/main.rs | 46 +------------------ {virtiod => virtio-blkd}/src/scheme.rs | 0 virtio-core/Cargo.toml | 2 + virtio-core/src/lib.rs | 2 +- virtio-core/src/probe.rs | 7 +-- virtio-core/src/spec.rs | 1 + virtio-core/src/transport.rs | 27 +++++++---- virtio-core/src/utils.rs | 36 +++++++++++++++ virtio-netd/Cargo.toml | 12 +++++ virtio-netd/src/main.rs | 63 ++++++++++++++++++++++++++ 14 files changed, 179 insertions(+), 77 deletions(-) rename {virtiod => virtio-blkd}/Cargo.toml (96%) rename {virtiod => virtio-blkd}/src/main.rs (80%) rename {virtiod => virtio-blkd}/src/scheme.rs (100%) create mode 100644 virtio-netd/Cargo.toml create mode 100644 virtio-netd/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 252bb500b9..be9d66cebc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1905,19 +1905,7 @@ dependencies = [ ] [[package]] -name = "virtio-core" -version = "0.1.0" -dependencies = [ - "bitflags 2.3.2", - "log", - "pcid", - "redox_syscall 0.3.5", - "static_assertions", - "thiserror", -] - -[[package]] -name = "virtiod" +name = "virtio-blkd" version = "0.1.0" dependencies = [ "anyhow", @@ -1934,6 +1922,29 @@ dependencies = [ "virtio-core", ] +[[package]] +name = "virtio-core" +version = "0.1.0" +dependencies = [ + "bitflags 2.3.2", + "log", + "pcid", + "redox-log", + "redox_syscall 0.3.5", + "static_assertions", + "thiserror", +] + +[[package]] +name = "virtio-netd" +version = "0.1.0" +dependencies = [ + "log", + "pcid", + "redox-daemon", + "virtio-core", +] + [[package]] name = "vte" version = "0.3.3" diff --git a/Cargo.toml b/Cargo.toml index f1fe9a570b..717d492594 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,9 @@ members = [ "usbhidd", "usbscsid", - "virtiod", + "virtio-blkd", + "virtio-netd", + "virtio-core", ] [profile.release] diff --git a/initfs.toml b/initfs.toml index d8d3a62644..067001a19d 100644 --- a/initfs.toml +++ b/initfs.toml @@ -23,12 +23,21 @@ subclass = 8 command = ["nvmed"] use_channel = true -# virtiod +# -------------- virtio -------------- # [[drivers]] -name = "VirtIO block storage" +name = "virtio-blk" class = 1 subclass = 0 -# VirtIO PCI vendor identifier. vendor = 6900 -command = ["virtiod"] +device = 4097 +command = ["virtio-blkd"] +use_channel = true + +[[drivers]] +name = "virtio-net" +class = 2 +subclass = 0 +vendor = 6900 +device = 4096 +command = ["virtio-netd"] use_channel = true diff --git a/virtiod/Cargo.toml b/virtio-blkd/Cargo.toml similarity index 96% rename from virtiod/Cargo.toml rename to virtio-blkd/Cargo.toml index 74d3adce40..0d9e12e5d0 100644 --- a/virtiod/Cargo.toml +++ b/virtio-blkd/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "virtiod" +name = "virtio-blkd" version = "0.1.0" edition = "2021" authors = ["Anhad Singh "] diff --git a/virtiod/src/main.rs b/virtio-blkd/src/main.rs similarity index 80% rename from virtiod/src/main.rs rename to virtio-blkd/src/main.rs index 731dc77b96..7e21dd0ab0 100644 --- a/virtiod/src/main.rs +++ b/virtio-blkd/src/main.rs @@ -34,52 +34,10 @@ pub enum Error { pub fn main() -> anyhow::Result<()> { #[cfg(target_os = "redox")] - setup_logging(); - + virtio_core::utils::setup_logging(log::LevelFilter::Trace, "virtio-blkd"); redox_daemon::Daemon::new(daemon_runner).expect("virtio-core: failed to daemonize"); } -#[cfg(target_os = "redox")] -fn setup_logging() { - use redox_log::{OutputBuilder, RedoxLogger}; - - let mut logger = RedoxLogger::new().with_output( - OutputBuilder::stderr() - .with_filter(log::LevelFilter::Trace) - .with_ansi_escape_codes() - .flush_on_newline(true) - .build(), - ); - - match OutputBuilder::in_redox_logging_scheme("disk", "pcie", "virtiod.log") { - Ok(builder) => { - logger = logger.with_output( - builder - .with_filter(log::LevelFilter::Trace) - .flush_on_newline(true) - .build(), - ) - } - Err(err) => eprintln!("virtiod: failed to create log: {}", err), - } - - match OutputBuilder::in_redox_logging_scheme("disk", "pcie", "virtiod.ansi.log") { - Ok(builder) => { - logger = logger.with_output( - builder - .with_filter(log::LevelFilter::Trace) - .with_ansi_escape_codes() - .flush_on_newline(true) - .build(), - ) - } - Err(err) => eprintln!("virtiod: failed to create ansi log: {}", err), - } - - logger.enable().unwrap(); - log::info!("virtiod: enabled logger"); -} - #[repr(C)] pub struct BlockGeometry { pub cylinders: VolatileCell, @@ -133,7 +91,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { let pci_config = pcid_handle.fetch_config()?; assert_eq!(pci_config.func.devid, 0x1001); - log::info!("virtiod: found `virtio-blk` device"); + log::info!("virtio-blk: initiating startup sequence :^)"); let mut device = virtio_core::probe_device(&mut pcid_handle)?; device.transport.finalize_features(); diff --git a/virtiod/src/scheme.rs b/virtio-blkd/src/scheme.rs similarity index 100% rename from virtiod/src/scheme.rs rename to virtio-blkd/src/scheme.rs diff --git a/virtio-core/Cargo.toml b/virtio-core/Cargo.toml index 1ffd4a4bb7..87467d6bfb 100644 --- a/virtio-core/Cargo.toml +++ b/virtio-core/Cargo.toml @@ -11,4 +11,6 @@ redox_syscall = "0.3" log = "0.4" thiserror = "1.0.40" +redox-log = "0.1" + pcid = { path = "../pcid" } diff --git a/virtio-core/src/lib.rs b/virtio-core/src/lib.rs index 626c2ecf21..5d1d6d0436 100644 --- a/virtio-core/src/lib.rs +++ b/virtio-core/src/lib.rs @@ -6,4 +6,4 @@ pub mod utils; mod probe; -pub use probe::{probe_device, MSIX_PRIMARY_VECTOR, Device}; +pub use probe::{probe_device, Device, MSIX_PRIMARY_VECTOR}; diff --git a/virtio-core/src/probe.rs b/virtio-core/src/probe.rs index 2f91eabb71..34fbc00bed 100644 --- a/virtio-core/src/probe.rs +++ b/virtio-core/src/probe.rs @@ -128,11 +128,12 @@ fn enable_msix(pcid_handle: &mut PcidServerHandle) -> Result { /// VirtIO Device Probe /// /// ## Device State -/// After this function, the device has been successfully reseted and is ready for use. +/// After this function, the device will have been successfully reseted and is ready for use. /// /// The caller is required to do the following: /// * Negotiate the device and driver supported features (finialize via [`StandardTransport::finalize_features`]) -/// * Create the device specific virtio queues (via [`StandardTransport::setup_queue`]) +/// * Create the device specific virtio queues (via [`StandardTransport::setup_queue`]). This is *required* to be done +/// before starting the device. /// * Finally start the device (via [`StandardTransport::run_device`]). At this point, the device /// is alive. /// @@ -215,7 +216,7 @@ pub fn probe_device<'a>(pcid_handle: &mut PcidServerHandle) -> Result _ => unreachable!(), } - log::info!("virtio: {capability:?}"); + log::trace!("virtio-core::device-probe: {capability:?}"); } let common_addr = common_addr.ok_or(Error::InCapable(CfgType::Common))?; diff --git a/virtio-core/src/spec.rs b/virtio-core/src/spec.rs index f494714f14..6c525c466e 100644 --- a/virtio-core/src/spec.rs +++ b/virtio-core/src/spec.rs @@ -131,6 +131,7 @@ const_assert_eq!(core::mem::size_of::(), 16); /// See `6.1 Driver Requirements: Reserved Feature Bits` section of the VirtIO /// specification for more information. pub const VIRTIO_F_VERSION_1: u32 = 32; +pub const VIRTIO_NET_F_MAC: u32 = 5; // ======== Available Ring ======== // diff --git a/virtio-core/src/transport.rs b/virtio-core/src/transport.rs index 529de32ab1..d4bcfdfd64 100644 --- a/virtio-core/src/transport.rs +++ b/virtio-core/src/transport.rs @@ -81,6 +81,7 @@ unsafe impl Send for QueueInner<'_> {} pub struct Queue<'a> { pub inner: Mutex>, + pub queue_index: u16, sref: Weak, } @@ -91,6 +92,7 @@ impl<'a> Queue<'a> { used: Used<'a>, notification_bell: &'a mut VolatileCell, + queue_index: u16, ) -> Arc { Arc::new_cyclic(|sref| Self { inner: Mutex::new(QueueInner { @@ -104,6 +106,7 @@ impl<'a> Queue<'a> { notification_bell, }), + queue_index, sref: sref.clone(), }) } @@ -153,7 +156,7 @@ impl<'a> Queue<'a> { inner.head_index += 1; assert_eq!(inner.used.flags(), 0); - inner.notification_bell.set(0); // FIXME: This corresponds to the queue index. + inner.notification_bell.set(self.queue_index); } fn alloc_descriptor(&self) -> usize { @@ -162,7 +165,7 @@ impl<'a> Queue<'a> { if let Some(index) = inner.descriptor_stack.pop_front() { index as usize } else { - log::warn!("virtiod: descriptors exhausted, waiting on garabage collector"); + log::warn!("virtiod-core: descriptors exhausted, waiting on garabage collector"); drop(inner); // Wait for the garbage collector thread to release some descriptors. @@ -216,7 +219,7 @@ impl<'a> Available<'a> { .elements .as_mut_slice(self.queue_size) .get_mut(index % 256) - .expect("virtio::available: index out of bounds") + .expect("virtio-core::available: index out of bounds") } } @@ -231,7 +234,7 @@ impl<'a> Available<'a> { impl Drop for Available<'_> { fn drop(&mut self) { - log::warn!("virtio: dropping 'available' ring at {:#x}", self.addr); + log::warn!("virtio-core: dropping 'available' ring at {:#x}", self.addr); unsafe { syscall::physunmap(self.addr).unwrap(); @@ -278,7 +281,7 @@ impl<'a> Used<'a> { .elements .as_mut_slice(self.queue_size) .get_mut(index % 256) - .expect("virtio::used: index out of bounds") + .expect("virtio-core::used: index out of bounds") } } @@ -297,7 +300,7 @@ impl<'a> Used<'a> { impl Drop for Used<'_> { fn drop(&mut self) { - log::warn!("virtio: dropping 'used' ring at {:#x}", self.addr); + log::warn!("virtio-core: dropping 'used' ring at {:#x}", self.addr); unsafe { syscall::physunmap(self.addr).unwrap(); @@ -385,8 +388,6 @@ impl<'a> StandardTransport<'a> { let queue_size = common.queue_size.get() as usize; let queue_notify_idx = common.queue_notify_off.get(); - log::info!("notify_idx: {}", queue_notify_idx); - // Allocate memory for the queue structues. let descriptor = unsafe { Dma::<[Descriptor]>::zeroed_unsized(queue_size).map_err(Error::SyscallError)? @@ -418,7 +419,13 @@ impl<'a> StandardTransport<'a> { &mut *(self.notify.add(offset as usize) as *mut VolatileCell) }; - log::info!("virtio: enabled queue #{queue_index} (size={queue_size})"); - Ok(Queue::new(descriptor, avail, used, notification_bell)) + log::info!("virtio-core: enabled queue #{queue_index} (size={queue_size})"); + Ok(Queue::new( + descriptor, + avail, + used, + notification_bell, + queue_index, + )) } } diff --git a/virtio-core/src/utils.rs b/virtio-core/src/utils.rs index be9ccbe876..d05b74c41e 100644 --- a/virtio-core/src/utils.rs +++ b/virtio-core/src/utils.rs @@ -63,3 +63,39 @@ impl IncompleteArrayField { pub const fn align(val: usize, align: usize) -> usize { (val + align) & !align } + +#[cfg(target_os = "redox")] +pub fn setup_logging(level: log::LevelFilter, name: &str) { + use redox_log::{OutputBuilder, RedoxLogger}; + + let mut logger = RedoxLogger::new().with_output( + OutputBuilder::stderr() + .with_filter(level) + .with_ansi_escape_codes() + .flush_on_newline(true) + .build(), + ); + + match OutputBuilder::in_redox_logging_scheme("disk", "pcie", format!("{name}.log")) { + Ok(builder) => { + logger = logger.with_output(builder.with_filter(level).flush_on_newline(true).build()) + } + Err(err) => eprintln!("virtio-core::utils: failed to create log: {}", err), + } + + match OutputBuilder::in_redox_logging_scheme("disk", "pcie", format!("{name}.ansi.log")) { + Ok(builder) => { + logger = logger.with_output( + builder + .with_filter(level) + .with_ansi_escape_codes() + .flush_on_newline(true) + .build(), + ) + } + Err(err) => eprintln!("virtio-core::utils: failed to create ANSI log: {}", err), + } + + logger.enable().unwrap(); + log::info!("virtio-core::utils: enabled logger"); +} diff --git a/virtio-netd/Cargo.toml b/virtio-netd/Cargo.toml new file mode 100644 index 0000000000..1be3271d90 --- /dev/null +++ b/virtio-netd/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "virtio-netd" +version = "0.1.0" +edition = "2021" + +[dependencies] +log = "0.4" + +virtio-core = { path = "../virtio-core" } +pcid = { path = "../pcid" } + +redox-daemon = "0.1" diff --git a/virtio-netd/src/main.rs b/virtio-netd/src/main.rs new file mode 100644 index 0000000000..b0fa791e36 --- /dev/null +++ b/virtio-netd/src/main.rs @@ -0,0 +1,63 @@ +use pcid_interface::PcidServerHandle; + +use virtio_core::spec::VIRTIO_NET_F_MAC; +use virtio_core::transport::Error; + +fn deamon(_deamon: redox_daemon::Daemon) -> Result<(), Error> { + let mut pcid_handle = PcidServerHandle::connect_default()?; + + // Double check that we have the right device. + // + // 0x1000 - virtio-net + let pci_config = pcid_handle.fetch_config()?; + + assert_eq!(pci_config.func.devid, 0x1000); + log::info!("virtio-net: initiating startup sequence :^)"); + + let device = virtio_core::probe_device(&mut pcid_handle)?; + let device_space = device.device_space; + + // Negotiate device features: + if device.transport.check_device_feature(VIRTIO_NET_F_MAC) { + let mac = (0..6) + .map(|i| unsafe { core::ptr::read_volatile(device_space.add(i)) }) + .collect::>(); + + log::info!( + "virtio-net: device MAC is {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", + mac[0], + mac[1], + mac[2], + mac[3], + mac[4], + mac[5] + ); + + device.transport.ack_driver_feature(VIRTIO_NET_F_MAC); + } + + // Allocate the recieve and transmit queues: + // + // TODO(andypython): Should we use the same IRQ vector for both? + let rx_queue = device + .transport + .setup_queue(virtio_core::MSIX_PRIMARY_VECTOR)?; + + let tx_queue = device + .transport + .setup_queue(virtio_core::MSIX_PRIMARY_VECTOR)?; + + device.transport.finalize_features(); + loop {} +} + +fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! { + deamon(redox_daemon).unwrap(); + unreachable!(); +} + +pub fn main() { + #[cfg(target_os = "redox")] + virtio_core::utils::setup_logging(log::LevelFilter::Trace, "virtio-netd"); + redox_daemon::Daemon::new(daemon_runner).expect("virtio-core: failed to daemonize"); +} From 97e77e21b6f5e612d17a52f3a0f646c27522e6f1 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Wed, 28 Jun 2023 12:58:51 +1000 Subject: [PATCH 3/7] virtio-net: scheme :^) Signed-off-by: Anhad Singh --- Cargo.lock | 3 + virtio-blkd/src/main.rs | 7 +- virtio-core/src/spec.rs | 8 ++ virtio-core/src/transport.rs | 7 +- virtio-netd/Cargo.toml | 3 + virtio-netd/src/main.rs | 91 ++++++++++++++++--- virtio-netd/src/scheme.rs | 165 +++++++++++++++++++++++++++++++++++ 7 files changed, 266 insertions(+), 18 deletions(-) create mode 100644 virtio-netd/src/scheme.rs diff --git a/Cargo.lock b/Cargo.lock index be9d66cebc..0ed00573d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1940,8 +1940,11 @@ name = "virtio-netd" version = "0.1.0" dependencies = [ "log", + "netutils", "pcid", "redox-daemon", + "redox_syscall 0.3.5", + "static_assertions", "virtio-core", ] diff --git a/virtio-blkd/src/main.rs b/virtio-blkd/src/main.rs index 7e21dd0ab0..64497da127 100644 --- a/virtio-blkd/src/main.rs +++ b/virtio-blkd/src/main.rs @@ -175,21 +175,20 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { .map_err(Error::SyscallError)?; let mut socket_file = unsafe { File::from_raw_fd(socket_fd as RawFd) }; - let mut scheme = scheme::DiskScheme::new(queue, device_space); - deamon.ready().expect("virtio: failed to deamonize"); + deamon.ready().expect("virtio-blkd: failed to deamonize"); loop { let mut packet = Packet::default(); socket_file .read(&mut packet) - .expect("ahcid: failed to read disk scheme"); + .expect("virtio-blkd: failed to read disk scheme"); let packey = scheme.handle(&mut packet); packet.a = packey.unwrap(); socket_file .write(&mut packet) - .expect("ahcid: failed to read disk scheme"); + .expect("virtio-blkd: failed to read disk scheme"); } } diff --git a/virtio-core/src/spec.rs b/virtio-core/src/spec.rs index 6c525c466e..37dd2b4fad 100644 --- a/virtio-core/src/spec.rs +++ b/virtio-core/src/spec.rs @@ -220,6 +220,14 @@ impl Buffer { } } + pub fn new_unsized(val: &syscall::Dma<[T]>) -> Self { + Self { + buffer: val.physical(), + size: core::mem::size_of::() * val.len(), + flags: DescriptorFlags::empty(), + } + } + pub fn flags(mut self, flags: DescriptorFlags) -> Self { self.flags = flags; self diff --git a/virtio-core/src/transport.rs b/virtio-core/src/transport.rs index d4bcfdfd64..cbcb7f860d 100644 --- a/virtio-core/src/transport.rs +++ b/virtio-core/src/transport.rs @@ -97,7 +97,7 @@ impl<'a> Queue<'a> { Arc::new_cyclic(|sref| Self { inner: Mutex::new(QueueInner { head_index: 0, - descriptor_stack: (0..(descriptor.len() - 1) as u16).rev().collect(), + descriptor_stack: (0..descriptor.len() as u16).collect(), descriptor, available, @@ -179,6 +179,11 @@ impl<'a> Queue<'a> { self.alloc_descriptor() } } + + /// Returns the number of descriptors in the descriptor table of this queue. + pub fn descriptor_len(&self) -> usize { + self.inner.lock().unwrap().descriptor.len() + } } pub struct Available<'a> { diff --git a/virtio-netd/Cargo.toml b/virtio-netd/Cargo.toml index 1be3271d90..8a905d18e8 100644 --- a/virtio-netd/Cargo.toml +++ b/virtio-netd/Cargo.toml @@ -5,8 +5,11 @@ edition = "2021" [dependencies] log = "0.4" +static_assertions = "1.1.0" virtio-core = { path = "../virtio-core" } pcid = { path = "../pcid" } redox-daemon = "0.1" +redox_syscall = "0.3" +netutils = { git = "https://gitlab.redox-os.org/redox-os/netutils.git", branch = "redox-unix" } diff --git a/virtio-netd/src/main.rs b/virtio-netd/src/main.rs index b0fa791e36..172034c2e2 100644 --- a/virtio-netd/src/main.rs +++ b/virtio-netd/src/main.rs @@ -1,9 +1,35 @@ +mod scheme; + +use std::fs::File; +use std::io::{Read, Write}; +use std::os::fd::{FromRawFd, RawFd}; + use pcid_interface::PcidServerHandle; +use syscall::{Packet, SchemeBlockMut}; + use virtio_core::spec::VIRTIO_NET_F_MAC; use virtio_core::transport::Error; -fn deamon(_deamon: redox_daemon::Daemon) -> Result<(), Error> { +use scheme::NetworkScheme; + +#[derive(Debug)] +#[repr(C)] +pub struct VirtHeader { + pub flags: u8, + pub gso_type: u8, + pub hdr_len: u16, + pub gso_size: u16, + pub csum_start: u16, + pub csum_offset: u16, + pub num_buffers: u16, +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 12); + +const MAX_BUFFER_LEN: usize = 65535; + +fn deamon(deamon: redox_daemon::Daemon) -> Result<(), Error> { let mut pcid_handle = PcidServerHandle::connect_default()?; // Double check that we have the right device. @@ -18,26 +44,32 @@ fn deamon(_deamon: redox_daemon::Daemon) -> Result<(), Error> { let device_space = device.device_space; // Negotiate device features: - if device.transport.check_device_feature(VIRTIO_NET_F_MAC) { + let mac_addr = if device.transport.check_device_feature(VIRTIO_NET_F_MAC) { let mac = (0..6) .map(|i| unsafe { core::ptr::read_volatile(device_space.add(i)) }) .collect::>(); - log::info!( - "virtio-net: device MAC is {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", - mac[0], - mac[1], - mac[2], - mac[3], - mac[4], - mac[5] + let mac_str = format!( + "{:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] ); + log::info!("virtio-net: device MAC is {mac_str}"); + device.transport.ack_driver_feature(VIRTIO_NET_F_MAC); - } + mac_str + } else { + unimplemented!() + }; + + device.transport.finalize_features(); // Allocate the recieve and transmit queues: // + // > Empty buffers are placed in one virtqueue for receiving + // > packets, and outgoing packets are enqueued into another + // > for transmission in that order. + // // TODO(andypython): Should we use the same IRQ vector for both? let rx_queue = device .transport @@ -47,8 +79,41 @@ fn deamon(_deamon: redox_daemon::Daemon) -> Result<(), Error> { .transport .setup_queue(virtio_core::MSIX_PRIMARY_VECTOR)?; - device.transport.finalize_features(); - loop {} + device.transport.run_device(); + + let mut name = pci_config.func.name(); + name.push_str("_virtio_net"); + + // Create the network scheme. + // + // FIXME(andypython): It should be fine to have multiple network devices. + let socket_fd = syscall::open( + &format!(":network"), + syscall::O_RDWR | syscall::O_CREAT | syscall::O_CLOEXEC, + ) + .map_err(Error::SyscallError)?; + + let mut socket_fd = unsafe { File::from_raw_fd(socket_fd as RawFd) }; + let mut scheme = NetworkScheme::new(rx_queue, tx_queue); + + let _ = netutils::setcfg("mac", &mac_addr); + + deamon.ready().expect("virtio-netd: failed to deamonize"); + + loop { + let mut packet = Packet::default(); + socket_fd + .read(&mut packet) + .expect("virtio-netd: failed to read packet"); + + let result = scheme.handle(&mut packet); + // `packet.a` contains the return value. + packet.a = result.expect("virtio-netd: failed to handle packet"); + + socket_fd + .write(&packet) + .expect("virtio-netd: failed to write packet"); + } } fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! { diff --git a/virtio-netd/src/scheme.rs b/virtio-netd/src/scheme.rs new file mode 100644 index 0000000000..19152c19d5 --- /dev/null +++ b/virtio-netd/src/scheme.rs @@ -0,0 +1,165 @@ +use std::collections::BTreeMap; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +use syscall::Error as SysError; +use syscall::*; + +use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags}; +use virtio_core::transport::Queue; + +use crate::{VirtHeader, MAX_BUFFER_LEN}; + +pub struct NetworkScheme<'a> { + /// Reciever Queue. + rx: Arc>, + rx_buffers: Vec>, + + /// Transmiter Queue. + tx: Arc>, + /// File descriptor handles. + handles: BTreeMap, + next_id: AtomicUsize, + + recv_head: u16, +} + +impl<'a> NetworkScheme<'a> { + pub fn new(rx: Arc>, tx: Arc>) -> Self { + // Populate all of the `rx_queue` with buffers to maximize performence. + let mut rx_buffers = vec![]; + for i in 0..(rx.descriptor_len() as usize) { + rx_buffers.push(unsafe { Dma::<[u8]>::zeroed_unsized(MAX_BUFFER_LEN) }.unwrap()); + + let chain = ChainBuilder::new() + .chain(Buffer::new_unsized(&rx_buffers[i]).flags(DescriptorFlags::WRITE_ONLY)) + .build(); + + rx.send(chain); + } + + Self { + rx, + rx_buffers, + tx, + + handles: BTreeMap::new(), + next_id: AtomicUsize::new(0), + + recv_head: 0, + } + } + + /// Returns the number of bytes read. Returns `0` if the operation would block. + fn try_recv(&mut self, target: &mut [u8]) -> usize { + let header_size = core::mem::size_of::(); + + let mut queue = self.rx.inner.lock().unwrap(); + + if self.recv_head == queue.used.head_index() { + // The read would block. + return 0; + } + + let idx = queue.used.head_index() as usize; + let element = queue.used.get_element_at(idx - 1); + + let descriptor_idx = element.table_index.get(); + let payload_size = element.written.get() as usize - header_size; + + // XXX: The header and packet are added as one output descriptor to the transmit queue, + // and the device is notified of the new entry (see 5.1.5 Device Initialization). + let buffer = &self.rx_buffers[descriptor_idx as usize]; + // TODO: Check the header. + let _header = unsafe { &*(buffer.as_ptr() as *const VirtHeader) }; + let packet = &buffer[header_size..(header_size + payload_size)]; + + // Copy the packet into the buffer. + target[..payload_size].copy_from_slice(&packet); + + self.recv_head = queue.used.head_index(); + payload_size + } +} + +impl<'a> SchemeBlockMut for NetworkScheme<'a> { + fn open( + &mut self, + _path: &str, + flags: usize, + uid: u32, + _gid: u32, + ) -> syscall::Result> { + if uid != 0 { + return Err(SysError::new(EACCES)); + } + + let id = self.next_id.fetch_add(1, Ordering::SeqCst); + self.handles.insert(id, flags); + + Ok(Some(id)) + } + + fn read(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result> { + let flags = *self.handles.get(&id).ok_or(SysError::new(EBADF))?; + let bytes = self.try_recv(buf); + + if bytes != 0 { + // We read some bytes. + Ok(Some(bytes)) + } else if flags & O_NONBLOCK == O_NONBLOCK { + // We are in non-blocking mode. + Err(SysError::new(EWOULDBLOCK)) + } else { + // Block + unimplemented!() + } + } + + fn write(&mut self, id: usize, buffer: &[u8]) -> syscall::Result> { + if self.handles.get(&id).is_none() { + return Err(SysError::new(EBADF)); + } + + let header = unsafe { Dma::::zeroed()?.assume_init() }; + + // TODO: Does the payload actually need to be a DMA buffer? + let mut payload = unsafe { Dma::<[u8]>::zeroed_unsized(buffer.len())? }; + payload.copy_from_slice(buffer); + + let chain = ChainBuilder::new() + .chain(Buffer::new(&header).flags(DescriptorFlags::NEXT)) + .chain(Buffer::new_unsized(&payload)) + .build(); + + self.tx.send(chain); + core::mem::forget(payload); + + Ok(Some(buffer.len())) + } + + fn dup(&mut self, _old_id: usize, _buf: &[u8]) -> syscall::Result> { + unimplemented!() + } + + fn fevent( + &mut self, + id: usize, + _flags: syscall::EventFlags, + ) -> syscall::Result> { + let _flags = self.handles.get(&id).ok_or(SysError::new(EBADF))?; + Ok(Some(syscall::EventFlags::empty())) + } + + fn fpath(&mut self, _id: usize, _buf: &mut [u8]) -> syscall::Result> { + unimplemented!() + } + + fn fsync(&mut self, _id: usize) -> syscall::Result> { + unimplemented!() + } + + fn close(&mut self, _id: usize) -> syscall::Result> { + unimplemented!() + } +} From 92fd7fc553c65734c83ce743c01d093270695fe0 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Wed, 28 Jun 2023 15:06:23 +1000 Subject: [PATCH 4/7] virtio-core::ChainBuilder: automagically add the `NEXT` flag Signed-off-by: Anhad Singh --- fmt.sh | 13 +++++++++++++ virtio-blkd/src/scheme.rs | 15 ++++++--------- virtio-core/src/spec.rs | 9 +++++++-- virtio-netd/src/scheme.rs | 2 +- 4 files changed, 27 insertions(+), 12 deletions(-) create mode 100755 fmt.sh diff --git a/fmt.sh b/fmt.sh new file mode 100755 index 0000000000..b0b137080b --- /dev/null +++ b/fmt.sh @@ -0,0 +1,13 @@ +#!/usr/bin/bash + +pushd virtio-core +cargo fmt +popd + +pushd virtio-netd +cargo fmt +popd + +pushd virtio-blkd +cargo fmt +popd diff --git a/virtio-blkd/src/scheme.rs b/virtio-blkd/src/scheme.rs index a6247e2c6b..c71e29d463 100644 --- a/virtio-blkd/src/scheme.rs +++ b/virtio-blkd/src/scheme.rs @@ -59,8 +59,8 @@ impl BlkExtension for Queue<'_> { let status = syscall::Dma::new(u8::MAX).unwrap(); let chain = ChainBuilder::new() - .chain(Buffer::new(&req).flags(DescriptorFlags::NEXT)) - .chain(Buffer::new(&result).flags(DescriptorFlags::WRITE_ONLY | DescriptorFlags::NEXT)) + .chain(Buffer::new(&req)) + .chain(Buffer::new(&result).flags(DescriptorFlags::WRITE_ONLY)) .chain(Buffer::new(&status).flags(DescriptorFlags::WRITE_ONLY)) .build(); @@ -90,8 +90,8 @@ impl BlkExtension for Queue<'_> { let status = syscall::Dma::new(u8::MAX).unwrap(); let chain = ChainBuilder::new() - .chain(Buffer::new(&req).flags(DescriptorFlags::NEXT)) - .chain(Buffer::new(&result).flags(DescriptorFlags::NEXT)) + .chain(Buffer::new(&req)) + .chain(Buffer::new(&result)) .chain(Buffer::new(&status).flags(DescriptorFlags::WRITE_ONLY)) .build(); @@ -162,11 +162,8 @@ impl<'a> DiskScheme<'a> { let status = syscall::Dma::new(u8::MAX).unwrap(); let chain = ChainBuilder::new() - .chain(Buffer::new(&req).flags(DescriptorFlags::NEXT)) - .chain( - Buffer::new(&result) - .flags(DescriptorFlags::WRITE_ONLY | DescriptorFlags::NEXT), - ) + .chain(Buffer::new(&req)) + .chain(Buffer::new(&result).flags(DescriptorFlags::WRITE_ONLY)) .chain(Buffer::new(&status).flags(DescriptorFlags::WRITE_ONLY)) .build(); diff --git a/virtio-core/src/spec.rs b/virtio-core/src/spec.rs index 37dd2b4fad..072314de2b 100644 --- a/virtio-core/src/spec.rs +++ b/virtio-core/src/spec.rs @@ -234,6 +234,7 @@ impl Buffer { } } +/// XXX: The [`DescriptorFlags::NEXT`] flag is set automatically. pub struct ChainBuilder { buffers: Vec, } @@ -245,12 +246,16 @@ impl ChainBuilder { } } - pub fn chain(mut self, buffer: Buffer) -> Self { + pub fn chain(mut self, mut buffer: Buffer) -> Self { + buffer.flags |= DescriptorFlags::NEXT; self.buffers.push(buffer); self } - pub fn build(self) -> Vec { + pub fn build(mut self) -> Vec { + let last_buffer = self.buffers.last_mut().expect("virtio-core: empty chain"); + last_buffer.flags.remove(DescriptorFlags::NEXT); + self.buffers } } diff --git a/virtio-netd/src/scheme.rs b/virtio-netd/src/scheme.rs index 19152c19d5..c3eb8fe4a7 100644 --- a/virtio-netd/src/scheme.rs +++ b/virtio-netd/src/scheme.rs @@ -128,7 +128,7 @@ impl<'a> SchemeBlockMut for NetworkScheme<'a> { payload.copy_from_slice(buffer); let chain = ChainBuilder::new() - .chain(Buffer::new(&header).flags(DescriptorFlags::NEXT)) + .chain(Buffer::new(&header)) .chain(Buffer::new_unsized(&payload)) .build(); From c898e2e01fb0e77749553e9f36a5e37fcf64efc0 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Thu, 29 Jun 2023 14:17:45 +1000 Subject: [PATCH 5/7] virtio-core: make `send` async This allows for us to do cool stuff such as `join!(queue.send(read_command), queue.send(write_command), queue.send(read_command_2))` Signed-off-by: Anhad Singh --- Cargo.lock | 16 ++- README.md | 2 +- virtio-blkd/Cargo.toml | 2 +- virtio-blkd/src/main.rs | 62 +-------- virtio-blkd/src/scheme.rs | 84 ++++-------- virtio-core/Cargo.toml | 3 + virtio-core/src/probe.rs | 3 - virtio-core/src/spec.rs | 56 +++++++- virtio-core/src/transport.rs | 245 +++++++++++++++++++++-------------- virtio-netd/Cargo.toml | 1 + virtio-netd/src/main.rs | 4 +- virtio-netd/src/scheme.rs | 22 ++-- 12 files changed, 260 insertions(+), 240 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ed00573d8..cc75943e6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -354,6 +354,16 @@ dependencies = [ "crossbeam-utils 0.8.15", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.15", +] + [[package]] name = "crossbeam-utils" version = "0.7.2" @@ -1910,12 +1920,12 @@ version = "0.1.0" dependencies = [ "anyhow", "block-io-wrapper", + "futures", "log", "partitionlib", "pcid", "redox-daemon", "redox-log", - "redox_event", "redox_syscall 0.3.5", "static_assertions", "thiserror", @@ -1927,9 +1937,12 @@ name = "virtio-core" version = "0.1.0" dependencies = [ "bitflags 2.3.2", + "crossbeam-queue", + "futures", "log", "pcid", "redox-log", + "redox_event", "redox_syscall 0.3.5", "static_assertions", "thiserror", @@ -1939,6 +1952,7 @@ dependencies = [ name = "virtio-netd" version = "0.1.0" dependencies = [ + "futures", "log", "netutils", "pcid", diff --git a/README.md b/README.md index 6023ec3f00..0262ed8d1b 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ These are the currently implemented devices/hardware interfaces. - usbctl - USB control (incomplete). - usbhidd - USB HID (incomplete). - usbscsid - USB SCSI (incomplete). -- virtiod - VirtIO (incomplete) (`virtio-blk`). +- virtio-* - VirtIO (incomplete) (`virtio-blk`, `virtio-net`). ## Contributing to Drivers diff --git a/virtio-blkd/Cargo.toml b/virtio-blkd/Cargo.toml index 0d9e12e5d0..461a0d679a 100644 --- a/virtio-blkd/Cargo.toml +++ b/virtio-blkd/Cargo.toml @@ -9,11 +9,11 @@ anyhow = "1.0.71" log = "0.4" thiserror = "1.0.40" static_assertions = "1.1.0" +futures = { version = "0.3.28", features = ["executor"] } redox-daemon = "0.1" redox-log = "0.1" redox_syscall = "0.3" -redox_event = { git = "https://gitlab.redox-os.org/redox-os/event.git" } partitionlib = { git = "https://gitlab.redox-os.org/redox-os/partitionlib.git" } block-io-wrapper = { path = "../block-io-wrapper" } diff --git a/virtio-blkd/src/main.rs b/virtio-blkd/src/main.rs index 64497da127..963156a37b 100644 --- a/virtio-blkd/src/main.rs +++ b/virtio-blkd/src/main.rs @@ -1,17 +1,15 @@ #![deny(trivial_numeric_casts, unused_allocation)] -#![feature(int_roundings)] +#![feature(int_roundings, async_fn_in_trait)] use std::fs::File; -use std::io; use std::io::{Read, Write}; -use std::os::fd::{AsRawFd, FromRawFd, RawFd}; +use std::os::fd::{FromRawFd, RawFd}; use static_assertions::const_assert_eq; use pcid_interface::*; use virtio_core::spec::*; -use event::EventQueue; use syscall::{Packet, SchemeBlockMut}; use virtio_core::utils::VolatileCell; @@ -93,67 +91,15 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { assert_eq!(pci_config.func.devid, 0x1001); log::info!("virtio-blk: initiating startup sequence :^)"); - let mut device = virtio_core::probe_device(&mut pcid_handle)?; + let device = virtio_core::probe_device(&mut pcid_handle)?; device.transport.finalize_features(); let queue = device .transport - .setup_queue(virtio_core::MSIX_PRIMARY_VECTOR)?; - let queue_copy = queue.clone(); + .setup_queue(virtio_core::MSIX_PRIMARY_VECTOR, &device.irq_handle)?; let device_space = unsafe { &mut *(device.device_space as *mut BlockDeviceConfig) }; - std::thread::spawn(move || { - let mut event_queue = EventQueue::::new().unwrap(); - let mut progress_head = 0; - - event_queue - .add( - device.irq_handle.as_raw_fd(), - move |_| -> Result, io::Error> { - // Read from ISR to acknowledge the interrupt. - let _isr = device.isr.get() as usize; - - let mut inner = queue_copy.inner.lock().unwrap(); - let used_head = inner.used.head_index(); - - if progress_head == used_head { - return Ok(None); - } - - for i in progress_head..used_head { - let used = inner.used.get_element_at(i as usize); - let mut desc_idx = used.table_index.get(); - inner.descriptor_stack.push_back(desc_idx as u16); - - loop { - let desc = &inner.descriptor[desc_idx as usize]; - if !desc.flags.contains(DescriptorFlags::NEXT) { - break; - } - - desc_idx = desc.next.into(); - inner.descriptor_stack.push_back(desc_idx as u16); - } - } - - progress_head = used_head; - drop(inner); - - let mut buf = [0u8; 8]; - device.irq_handle.read(&mut buf)?; - // Acknowledge the interrupt. - // irq_handle.write(&buf)?; - Ok(None) - }, - ) - .unwrap(); - - loop { - event_queue.run().unwrap(); - } - }); - // At this point the device is alive! device.transport.run_device(); diff --git a/virtio-blkd/src/scheme.rs b/virtio-blkd/src/scheme.rs index c71e29d463..8a3f6154cb 100644 --- a/virtio-blkd/src/scheme.rs +++ b/virtio-blkd/src/scheme.rs @@ -21,33 +21,12 @@ use crate::BlockVirtRequest; const BLK_SIZE: u64 = 512; trait BlkExtension { - /// XXX: Reads only one block despite the size of the output buffer. Use [`BlkExtension::read`] instead. - fn read_block(&self, block: u64, block_bytes: &mut [u8]) -> usize; - - /// XXX: Reads only one block despite the size of the output buffer. Use [`BlkExtension::write`] instead. - fn write_block(&self, block: u64, block_bytes: &[u8]) -> usize; - - // FIXME(andypython): The following two methods can be done asynchronously (i.e, send all of the commands at once - // and wait for the result in the end). - fn read(&self, block: u64, block_bytes: &mut [u8]) -> usize { - let sectors = block_bytes.len() / BLK_SIZE as usize; - - (0..sectors) - .map(|i| self.read_block(block + i as u64, &mut block_bytes[i * BLK_SIZE as usize..])) - .sum() - } - - fn write(&self, block: u64, block_bytes: &[u8]) -> usize { - let sectors = block_bytes.len() / BLK_SIZE as usize; - - (0..sectors) - .map(|i| self.write_block(block + i as u64, &block_bytes[i * BLK_SIZE as usize..])) - .sum() - } + async fn read(&self, block: u64, target: &mut [u8]) -> usize; + async fn write(&self, block: u64, target: &[u8]) -> usize; } impl BlkExtension for Queue<'_> { - fn read_block(&self, block: u64, block_bytes: &mut [u8]) -> usize { + async fn read(&self, block: u64, target: &mut [u8]) -> usize { let req = syscall::Dma::new(BlockVirtRequest { ty: BlockRequestTy::In, reserved: 0, @@ -55,28 +34,24 @@ impl BlkExtension for Queue<'_> { }) .unwrap(); - let result = syscall::Dma::new([0u8; 512]).unwrap(); + let result = unsafe { syscall::Dma::<[u8]>::zeroed_unsized(target.len()) }.unwrap(); let status = syscall::Dma::new(u8::MAX).unwrap(); let chain = ChainBuilder::new() .chain(Buffer::new(&req)) - .chain(Buffer::new(&result).flags(DescriptorFlags::WRITE_ONLY)) + .chain(Buffer::new_unsized(&result).flags(DescriptorFlags::WRITE_ONLY)) .chain(Buffer::new(&status).flags(DescriptorFlags::WRITE_ONLY)) .build(); - self.send(chain); + // XXX: Subtract 1 because the of status byte. + let written = self.send(chain).await as usize - 1; + assert_eq!(*status, 0); - // FIXME: interrupts are for a reason - while *status != 0 { - core::hint::spin_loop(); - } - - let size = core::cmp::min(block_bytes.len(), result.len()); - block_bytes[..size].copy_from_slice(&result.as_slice()[..size]); - size + target[..written].copy_from_slice(&result); + written } - fn write_block(&self, block: u64, block_bytes: &[u8]) -> usize { + async fn write(&self, block: u64, target: &[u8]) -> usize { let req = syscall::Dma::new(BlockVirtRequest { ty: BlockRequestTy::Out, reserved: 0, @@ -84,25 +59,21 @@ impl BlkExtension for Queue<'_> { }) .unwrap(); - let mut result = syscall::Dma::new([0u8; 512]).unwrap(); - result.copy_from_slice(&block_bytes[..512]); + let mut result = unsafe { syscall::Dma::<[u8]>::zeroed_unsized(target.len()) }.unwrap(); + result.copy_from_slice(target.as_ref()); let status = syscall::Dma::new(u8::MAX).unwrap(); let chain = ChainBuilder::new() .chain(Buffer::new(&req)) - .chain(Buffer::new(&result)) + .chain(Buffer::new_sized(&result, target.len())) .chain(Buffer::new(&status).flags(DescriptorFlags::WRITE_ONLY)) .build(); - self.send(chain); + self.send(chain).await as usize; + assert_eq!(*status, 0); - // FIXME: interrupts are for a reason - while *status != 0 { - core::hint::spin_loop(); - } - - block_bytes.len() + target.len() } } @@ -167,18 +138,11 @@ impl<'a> DiskScheme<'a> { .chain(Buffer::new(&status).flags(DescriptorFlags::WRITE_ONLY)) .build(); - self.scheme.queue.send(chain); - - // FIXME: interrupts are for a reason - while *status != 0 { - core::hint::spin_loop(); - } + futures::executor::block_on(self.scheme.queue.send(chain)); + assert_eq!(*status, 0); let size = core::cmp::min(block_bytes.len(), result.len()); block_bytes[..size].copy_from_slice(&result.as_slice()[..size]); - - std::thread::yield_now(); - Ok(()) }; @@ -328,7 +292,7 @@ impl<'a> SchemeBlockMut for DiskScheme<'a> { let abs_block = part.start_lba + rel_block; - let count = self.queue.read(abs_block, buf); + let count = futures::executor::block_on(self.queue.read(abs_block, buf)); *offset += count; Ok(Some(count)) } @@ -336,7 +300,9 @@ impl<'a> SchemeBlockMut for DiskScheme<'a> { Handle::Disk { ref mut offset } => { let block_size = self.cfg.block_size(); - let count = self.queue.read((*offset as u64) / block_size as u64, buf); + let count = futures::executor::block_on( + self.queue.read((*offset as u64) / block_size as u64, buf), + ); *offset += count; Ok(Some(count)) } @@ -347,7 +313,9 @@ impl<'a> SchemeBlockMut for DiskScheme<'a> { match *self.handles.get_mut(&id).ok_or(Error::new(EBADF))? { Handle::Disk { ref mut offset } => { let block_size = self.cfg.block_size(); - let count = self.queue.write((*offset as u64) / block_size as u64, buf); + let count = futures::executor::block_on( + self.queue.write((*offset as u64) / block_size as u64, buf), + ); *offset += count; Ok(Some(count)) diff --git a/virtio-core/Cargo.toml b/virtio-core/Cargo.toml index 87467d6bfb..9a8c197b5d 100644 --- a/virtio-core/Cargo.toml +++ b/virtio-core/Cargo.toml @@ -10,7 +10,10 @@ bitflags = "2.3.2" redox_syscall = "0.3" log = "0.4" thiserror = "1.0.40" +futures = { version = "0.3.28", features = ["executor"] } +crossbeam-queue = "0.3.8" redox-log = "0.1" +redox_event = { git = "https://gitlab.redox-os.org/redox-os/event.git" } pcid = { path = "../pcid" } diff --git a/virtio-core/src/probe.rs b/virtio-core/src/probe.rs index 34fbc00bed..a68ed54541 100644 --- a/virtio-core/src/probe.rs +++ b/virtio-core/src/probe.rs @@ -23,7 +23,6 @@ pub struct Device<'a> { struct MsixInfo { pub virt_table_base: NonNull, - pub virt_pba_base: NonNull, pub capability: MsixCapability, } @@ -84,11 +83,9 @@ fn enable_msix(pcid_handle: &mut PcidServerHandle) -> Result { } let virt_table_base = ((table_base - bar_ptr as usize) + address) as *mut MsixTableEntry; - let virt_pba_base = ((pba_base - bar_ptr as usize) + address) as *mut u64; let mut info = MsixInfo { virt_table_base: NonNull::new(virt_table_base).unwrap(), - virt_pba_base: NonNull::new(virt_pba_base).unwrap(), capability, }; diff --git a/virtio-core/src/spec.rs b/virtio-core/src/spec.rs index 072314de2b..eb8f12f829 100644 --- a/virtio-core/src/spec.rs +++ b/virtio-core/src/spec.rs @@ -1,5 +1,7 @@ //! https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.html +use std::sync::atomic::{AtomicU16, AtomicU32, AtomicU64, Ordering}; + use crate::utils::{IncompleteArrayField, VolatileCell}; use static_assertions::const_assert_eq; @@ -116,16 +118,42 @@ bitflags::bitflags! { #[repr(C)] pub struct Descriptor { /// Address (guest-physical). - pub address: u64, + pub address: AtomicU64, /// Size of the descriptor. - pub size: u32, - pub flags: DescriptorFlags, + pub size: AtomicU32, + flags: AtomicU16, /// Index of next desciptor in chain. - pub next: u16, + pub next: AtomicU16, } const_assert_eq!(core::mem::size_of::(), 16); +impl Descriptor { + pub fn set_addr(&self, addr: u64) { + self.address.store(addr, Ordering::SeqCst) + } + + pub fn set_size(&self, size: u32) { + self.size.store(size, Ordering::SeqCst) + } + + pub fn set_next(&self, next: Option) { + self.next.store(next.unwrap_or_default(), Ordering::SeqCst) + } + + pub fn set_flags(&self, flags: DescriptorFlags) { + self.flags.store(flags.bits(), Ordering::SeqCst) + } + + pub fn next(&self) -> u16 { + self.next.load(Ordering::SeqCst) + } + + pub fn flags(&self) -> DescriptorFlags { + DescriptorFlags::from_bits_truncate(self.flags.load(Ordering::SeqCst)) + } +} + /// This indicates compliance with the version 1 VirtIO specification. /// /// See `6.1 Driver Requirements: Reserved Feature Bits` section of the VirtIO @@ -140,7 +168,13 @@ pub const VIRTIO_NET_F_MAC: u32 = 5; // chain. #[repr(C)] pub struct AvailableRingElement { - pub table_index: VolatileCell, + pub table_index: AtomicU16, +} + +impl AvailableRingElement { + pub fn set_table_index(&self, index: u16) { + self.table_index.store(index, Ordering::SeqCst) + } } const_assert_eq!(core::mem::size_of::(), 2); @@ -148,7 +182,7 @@ const_assert_eq!(core::mem::size_of::(), 2); #[repr(C)] pub struct AvailableRing { pub flags: VolatileCell, - pub head_index: VolatileCell, + pub head_index: AtomicU16, pub elements: IncompleteArrayField, } @@ -158,7 +192,7 @@ impl Default for AvailableRing { fn default() -> Self { Self { flags: VolatileCell::new(0), - head_index: VolatileCell::new(0), + head_index: AtomicU16::new(0), elements: IncompleteArrayField::new(), } } @@ -228,6 +262,14 @@ impl Buffer { } } + pub fn new_sized(val: &syscall::Dma<[T]>, size: usize) -> Self { + Self { + buffer: val.physical(), + size, + flags: DescriptorFlags::empty(), + } + } + pub fn flags(mut self, flags: DescriptorFlags) -> Self { self.flags = flags; self diff --git a/virtio-core/src/transport.rs b/virtio-core/src/transport.rs index cbcb7f860d..ae94c70316 100644 --- a/virtio-core/src/transport.rs +++ b/virtio-core/src/transport.rs @@ -1,13 +1,17 @@ use crate::spec::*; -use crate::utils::{align, VolatileCell}; +use crate::utils::align; +use event::EventQueue; use syscall::{Dma, PHYSMAP_WRITE}; use core::mem::size_of; -use core::sync::atomic::{fence, AtomicU16, Ordering}; +use core::sync::atomic::{AtomicU16, Ordering}; -use std::collections::VecDeque; +use std::fs::File; +use std::future::Future; +use std::os::fd::AsRawFd; use std::sync::{Arc, Mutex, Weak}; +use std::task::{Poll, Waker}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -64,24 +68,63 @@ const fn queue_part_sizes(queue_size: usize) -> (usize, usize, usize) { ) } -pub struct QueueInner<'a> { - pub descriptor: Dma<[Descriptor]>, - pub available: Available<'a>, - pub used: Used<'a>, - - /// Keeps track of unused descriptor indicies. - pub descriptor_stack: VecDeque, - - notification_bell: &'a mut VolatileCell, - head_index: u16, +pub struct PendingRequest<'a> { + queue: Arc>, + first_descriptor: u32, } -unsafe impl Sync for QueueInner<'_> {} -unsafe impl Send for QueueInner<'_> {} +impl<'a> Future for PendingRequest<'a> { + type Output = u32; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + // XXX: Register the waker before checking the queue to avoid the race condition + // where you lose a notification. + self.queue + .waker + .lock() + .unwrap() + .insert(self.first_descriptor, cx.waker().clone()); + + let used_head = self.queue.used.head_index(); + let used_element = self.queue.used.get_element_at((used_head - 1) as usize); + let written = used_element.written.get(); + + let mut table_index = used_element.table_index.get(); + + if table_index == self.first_descriptor { + // The request has been completed; recycle the descriptors used. + while self.queue.descriptor[table_index as usize] + .flags() + .contains(DescriptorFlags::NEXT) + { + let next_index = self.queue.descriptor[table_index as usize].next(); + self.queue.descriptor_stack.push(table_index as u16); + table_index = next_index.into(); + } + + // Push the last descriptor. + self.queue.descriptor_stack.push(table_index as u16); + self.queue + .waker + .lock() + .unwrap() + .remove(&self.first_descriptor); + return Poll::Ready(written); + } else { + return Poll::Pending; + } + } +} pub struct Queue<'a> { - pub inner: Mutex>, pub queue_index: u16, + pub waker: Mutex>, + pub used: Used<'a>, + pub descriptor: Dma<[Descriptor]>, + pub available: Available<'a>, + + notification_bell: &'a mut AtomicU16, + descriptor_stack: crossbeam_queue::SegQueue, sref: Weak, } @@ -91,101 +134,79 @@ impl<'a> Queue<'a> { available: Available<'a>, used: Used<'a>, - notification_bell: &'a mut VolatileCell, + notification_bell: &'a mut AtomicU16, queue_index: u16, ) -> Arc { + let descriptor_stack = crossbeam_queue::SegQueue::new(); + (0..descriptor.len() as u16).for_each(|i| descriptor_stack.push(i)); + Arc::new_cyclic(|sref| Self { - inner: Mutex::new(QueueInner { - head_index: 0, - descriptor_stack: (0..descriptor.len() as u16).collect(), - - descriptor, - available, - used, - - notification_bell, - }), - + notification_bell, + available, + descriptor, + used, + waker: Mutex::new(std::collections::HashMap::new()), queue_index, + descriptor_stack, sref: sref.clone(), }) } - pub fn send(&self, chain: Vec) { + #[must_use = "The function returns a future that must be awaited to ensure the sent request is completed."] + pub fn send(&self, chain: Vec) -> PendingRequest<'a> { let mut first_descriptor: Option = None; let mut last_descriptor: Option = None; for buffer in chain.iter() { - let descriptor = self.alloc_descriptor(); - - let mut inner = self.inner.lock().unwrap(); + let descriptor = self.descriptor_stack.pop().unwrap() as usize; if first_descriptor.is_none() { first_descriptor = Some(descriptor); } - inner.descriptor[descriptor].address = buffer.buffer as u64; - inner.descriptor[descriptor].flags = buffer.flags; - inner.descriptor[descriptor].size = buffer.size as u32; + self.descriptor[descriptor].set_addr(buffer.buffer as u64); + self.descriptor[descriptor].set_flags(buffer.flags); + self.descriptor[descriptor].set_size(buffer.size as u32); if let Some(index) = last_descriptor { - inner.descriptor[index].next = descriptor as u16; + self.descriptor[index].set_next(Some(descriptor as u16)); } last_descriptor = Some(descriptor); } - let mut inner = self.inner.lock().unwrap(); - let last_descriptor = last_descriptor.unwrap(); let first_descriptor = first_descriptor.unwrap(); - inner.descriptor[last_descriptor].next = 0; + self.descriptor[last_descriptor].set_next(None); - fence(Ordering::SeqCst); - let index = inner.head_index as usize; + let index = self.available.head_index() as usize; - inner - .available + self.available .get_element_at(index) - .table_index - .set(first_descriptor as u16); + .set_table_index(first_descriptor as u16); - fence(Ordering::SeqCst); - inner.available.set_head_idx(index as u16 + 1); - inner.head_index += 1; + self.available.set_head_idx(index as u16 + 1); + self.notification_bell + .store(self.queue_index, Ordering::SeqCst); - assert_eq!(inner.used.flags(), 0); - inner.notification_bell.set(self.queue_index); - } + assert_eq!(self.used.flags(), 0); - fn alloc_descriptor(&self) -> usize { - let mut inner = self.inner.lock().unwrap(); - - if let Some(index) = inner.descriptor_stack.pop_front() { - index as usize - } else { - log::warn!("virtiod-core: descriptors exhausted, waiting on garabage collector"); - drop(inner); - - // Wait for the garbage collector thread to release some descriptors. - // - // TODO(andypython): Instead of just yielding, we should have a proper notificiation - // mechanism. I am not aware whats the standard way redox applications - // or drivers implement basically a WaitQueue which you can use to wake - // up a thread. The descripts really should NEVER run out, but if they - // do, have a proper way to handle them. - std::thread::yield_now(); - self.alloc_descriptor() + PendingRequest { + queue: self.sref.upgrade().unwrap(), + first_descriptor: first_descriptor as u32, } } /// Returns the number of descriptors in the descriptor table of this queue. pub fn descriptor_len(&self) -> usize { - self.inner.lock().unwrap().descriptor.len() + self.descriptor.len() } } +unsafe impl Sync for Queue<'_> {} +unsafe impl Send for Queue<'_> {} + pub struct Available<'a> { addr: usize, size: usize, @@ -216,20 +237,24 @@ impl<'a> Available<'a> { /// ## Panics /// This function panics if the index is out of bounds. - pub fn get_element_at(&mut self, index: usize) -> &mut AvailableRingElement { + pub fn get_element_at(&self, index: usize) -> &AvailableRingElement { // SAFETY: We have exclusive access to the elements and the number of elements // is correct; same as the queue size. unsafe { self.ring .elements - .as_mut_slice(self.queue_size) - .get_mut(index % 256) + .as_slice(self.queue_size) + .get(index % 256) .expect("virtio-core::available: index out of bounds") } } - pub fn set_head_idx(&mut self, index: u16) { - self.ring.head_index.set(index); + pub fn head_index(&self) -> u16 { + self.ring.head_index.load(Ordering::SeqCst) + } + + pub fn set_head_idx(&self, index: u16) { + self.ring.head_index.store(index, Ordering::SeqCst); } pub fn phys_addr(&self) -> usize { @@ -278,7 +303,21 @@ impl<'a> Used<'a> { /// ## Panics /// This function panics if the index is out of bounds. - pub fn get_element_at(&mut self, index: usize) -> &mut UsedRingElement { + pub fn get_element_at(&self, index: usize) -> &UsedRingElement { + // SAFETY: We have exclusive access to the elements and the number of elements + // is correct; same as the queue size. + unsafe { + self.ring + .elements + .as_slice(self.queue_size) + .get(index % 256) + .expect("virtio-core::used: index out of bounds") + } + } + + /// ## Panics + /// This function panics if the index is out of bounds. + pub fn get_mut_element_at(&mut self, index: usize) -> &mut UsedRingElement { // SAFETY: We have exclusive access to the elements and the number of elements // is correct; same as the queue size. unsafe { @@ -320,27 +359,19 @@ pub struct StandardTransport<'a> { notify_mul: u32, queue_index: AtomicU16, - sref: Weak, } impl<'a> StandardTransport<'a> { pub fn new(common: &'a mut CommonCfg, notify: *const u8, notify_mul: u32) -> Arc { - Arc::new_cyclic(|sref| Self { + Arc::new(Self { common: Mutex::new(common), notify, notify_mul, queue_index: AtomicU16::new(0), - sref: sref.clone(), }) } - pub fn sref(&self) -> Arc { - // UNWRAP: The constructor ensures that we are wrapped in our own `Arc`. So this - // unwrap is going to be unreachable. - self.sref.upgrade().unwrap() - } - pub fn check_device_feature(&self, feature: u32) -> bool { let mut common = self.common.lock().unwrap(); @@ -384,7 +415,7 @@ impl<'a> StandardTransport<'a> { .set(status | DeviceStatusFlags::DRIVER_OK); } - pub fn setup_queue(&self, vector: u16) -> Result>, Error> { + pub fn setup_queue(&self, vector: u16, irq_handle: &File) -> Result>, Error> { let mut common = self.common.lock().unwrap(); let queue_index = self.queue_index.fetch_add(1, Ordering::SeqCst); @@ -398,14 +429,17 @@ impl<'a> StandardTransport<'a> { Dma::<[Descriptor]>::zeroed_unsized(queue_size).map_err(Error::SyscallError)? }; - let mut avail = Available::new(queue_size)?; + 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.set(u16::MAX); - used.get_element_at(i).table_index.set(u32::MAX); + avail + .get_element_at(i) + .table_index + .store(u16::MAX, Ordering::Relaxed); + used.get_mut_element_at(i).table_index.set(u32::MAX); } common.queue_desc.set(descriptor.physical() as u64); @@ -421,16 +455,33 @@ impl<'a> StandardTransport<'a> { let notification_bell = unsafe { let offset = self.notify_mul * queue_notify_idx as u32; - &mut *(self.notify.add(offset as usize) as *mut VolatileCell) + &mut *(self.notify.add(offset as usize) as *mut AtomicU16) }; log::info!("virtio-core: enabled queue #{queue_index} (size={queue_size})"); - Ok(Queue::new( - descriptor, - avail, - used, - notification_bell, - queue_index, - )) + + let queue = Queue::new(descriptor, avail, used, notification_bell, queue_index); + + let queue_copy = queue.clone(); + let irq_fd = irq_handle.as_raw_fd(); + + std::thread::spawn(move || { + let mut event_queue = EventQueue::::new().unwrap(); + + event_queue + .add(irq_fd, move |_| -> Result, std::io::Error> { + for (_, task) in queue_copy.waker.lock().unwrap().iter() { + task.wake_by_ref(); + } + Ok(None) + }) + .unwrap(); + + loop { + event_queue.run().unwrap(); + } + }); + + Ok(queue) } } diff --git a/virtio-netd/Cargo.toml b/virtio-netd/Cargo.toml index 8a905d18e8..f4a116f10c 100644 --- a/virtio-netd/Cargo.toml +++ b/virtio-netd/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] log = "0.4" static_assertions = "1.1.0" +futures = { version = "0.3.28", features = ["executor"] } virtio-core = { path = "../virtio-core" } pcid = { path = "../pcid" } diff --git a/virtio-netd/src/main.rs b/virtio-netd/src/main.rs index 172034c2e2..e6fb3ee842 100644 --- a/virtio-netd/src/main.rs +++ b/virtio-netd/src/main.rs @@ -73,11 +73,11 @@ fn deamon(deamon: redox_daemon::Daemon) -> Result<(), Error> { // TODO(andypython): Should we use the same IRQ vector for both? let rx_queue = device .transport - .setup_queue(virtio_core::MSIX_PRIMARY_VECTOR)?; + .setup_queue(virtio_core::MSIX_PRIMARY_VECTOR, &device.irq_handle)?; let tx_queue = device .transport - .setup_queue(virtio_core::MSIX_PRIMARY_VECTOR)?; + .setup_queue(virtio_core::MSIX_PRIMARY_VECTOR, &device.irq_handle)?; device.transport.run_device(); diff --git a/virtio-netd/src/scheme.rs b/virtio-netd/src/scheme.rs index c3eb8fe4a7..251a501ce0 100644 --- a/virtio-netd/src/scheme.rs +++ b/virtio-netd/src/scheme.rs @@ -35,7 +35,7 @@ impl<'a> NetworkScheme<'a> { .chain(Buffer::new_unsized(&rx_buffers[i]).flags(DescriptorFlags::WRITE_ONLY)) .build(); - rx.send(chain); + let _ = rx.send(chain); } Self { @@ -54,15 +54,13 @@ impl<'a> NetworkScheme<'a> { fn try_recv(&mut self, target: &mut [u8]) -> usize { let header_size = core::mem::size_of::(); - let mut queue = self.rx.inner.lock().unwrap(); - - if self.recv_head == queue.used.head_index() { + if self.recv_head == self.rx.used.head_index() { // The read would block. return 0; } - let idx = queue.used.head_index() as usize; - let element = queue.used.get_element_at(idx - 1); + let idx = self.rx.used.head_index() as usize; + let element = self.rx.used.get_element_at(idx - 1); let descriptor_idx = element.table_index.get(); let payload_size = element.written.get() as usize - header_size; @@ -77,7 +75,7 @@ impl<'a> NetworkScheme<'a> { // Copy the packet into the buffer. target[..payload_size].copy_from_slice(&packet); - self.recv_head = queue.used.head_index(); + self.recv_head = self.rx.used.head_index(); payload_size } } @@ -123,7 +121,6 @@ impl<'a> SchemeBlockMut for NetworkScheme<'a> { let header = unsafe { Dma::::zeroed()?.assume_init() }; - // TODO: Does the payload actually need to be a DMA buffer? let mut payload = unsafe { Dma::<[u8]>::zeroed_unsized(buffer.len())? }; payload.copy_from_slice(buffer); @@ -132,9 +129,7 @@ impl<'a> SchemeBlockMut for NetworkScheme<'a> { .chain(Buffer::new_unsized(&payload)) .build(); - self.tx.send(chain); - core::mem::forget(payload); - + futures::executor::block_on(self.tx.send(chain)); Ok(Some(buffer.len())) } @@ -147,7 +142,10 @@ impl<'a> SchemeBlockMut for NetworkScheme<'a> { id: usize, _flags: syscall::EventFlags, ) -> syscall::Result> { - let _flags = self.handles.get(&id).ok_or(SysError::new(EBADF))?; + if self.handles.get(&id).is_none() { + return Err(SysError::new(EBADF)); + } + Ok(Some(syscall::EventFlags::empty())) } From a720cf6f4491a7e5d9c8ba73c307450dffdfb121 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Thu, 29 Jun 2023 16:36:02 +1000 Subject: [PATCH 6/7] virtio-core::probe: make sure the addr is aligned sys_physmap() requires the address to be page aligned. This fixes the panic inside the `virtio-gpu` driver. Signed-off-by: Anhad Singh --- virtio-core/src/probe.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/virtio-core/src/probe.rs b/virtio-core/src/probe.rs index a68ed54541..f627de9e68 100644 --- a/virtio-core/src/probe.rs +++ b/virtio-core/src/probe.rs @@ -12,7 +12,7 @@ use syscall::{Io, PHYSMAP_NO_CACHE, PHYSMAP_WRITE}; use crate::spec::*; use crate::transport::{Error, StandardTransport}; -use crate::utils::VolatileCell; +use crate::utils::{VolatileCell, align_down}; pub struct Device<'a> { pub transport: Arc>, @@ -178,11 +178,21 @@ pub fn probe_device<'a>(pcid_handle: &mut PcidServerHandle) -> Result }; let address = unsafe { - syscall::physmap( - addr + capability.offset as usize, - capability.length as usize, + let addr = addr + capability.offset as usize; + + // XXX: physmap() requires the address to be page aligned. + let aligned_addr = align_down(addr); + let offset = addr - aligned_addr; + + let size = offset + capability.length as usize; + + let addr = syscall::physmap( + aligned_addr, + size, PHYSMAP_WRITE | PHYSMAP_NO_CACHE, - )? + )?; + + addr + offset }; match capability.cfg_type { From 4bbda21f0bcf6c024516dffaccf2d1ad12c23e90 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Thu, 29 Jun 2023 16:37:59 +1000 Subject: [PATCH 7/7] drivers: start building `virtio-gpu` Signed-off-by: Anhad Singh --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + README.md | 2 +- initfs.toml | 9 +++++++++ virtio-core/src/utils.rs | 4 ++++ virtio-gpud/Cargo.toml | 13 +++++++++++++ virtio-gpud/src/main.rs | 28 ++++++++++++++++++++++++++++ 7 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 virtio-gpud/Cargo.toml create mode 100644 virtio-gpud/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index cc75943e6c..69722a682a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1948,6 +1948,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "virtio-gpud" +version = "0.1.0" +dependencies = [ + "log", + "pcid", + "redox-daemon", + "virtio-core", +] + [[package]] name = "virtio-netd" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 717d492594..2ccefbc9b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ members = [ "virtio-blkd", "virtio-netd", + "virtio-gpud", "virtio-core", ] diff --git a/README.md b/README.md index 0262ed8d1b..72e8c1788d 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ These are the currently implemented devices/hardware interfaces. - usbctl - USB control (incomplete). - usbhidd - USB HID (incomplete). - usbscsid - USB SCSI (incomplete). -- virtio-* - VirtIO (incomplete) (`virtio-blk`, `virtio-net`). +- virtio-* - VirtIO (incomplete) (`virtio-blk`, `virtio-net`, `virtio-gpu`). ## Contributing to Drivers diff --git a/initfs.toml b/initfs.toml index 067001a19d..631768911d 100644 --- a/initfs.toml +++ b/initfs.toml @@ -41,3 +41,12 @@ vendor = 6900 device = 4096 command = ["virtio-netd"] use_channel = true + +[[drivers]] +name = "virtio-gpu" +class = 3 +subclass = 0 +vendor = 6900 +device = 4176 +command = ["virtio-gpud"] +use_channel = true diff --git a/virtio-core/src/utils.rs b/virtio-core/src/utils.rs index d05b74c41e..85d39e54af 100644 --- a/virtio-core/src/utils.rs +++ b/virtio-core/src/utils.rs @@ -64,6 +64,10 @@ pub const fn align(val: usize, align: usize) -> usize { (val + align) & !align } +pub const fn align_down(addr: usize) -> usize { + addr & !(syscall::PAGE_SIZE - 1) +} + #[cfg(target_os = "redox")] pub fn setup_logging(level: log::LevelFilter, name: &str) { use redox_log::{OutputBuilder, RedoxLogger}; diff --git a/virtio-gpud/Cargo.toml b/virtio-gpud/Cargo.toml new file mode 100644 index 0000000000..17fdf4384a --- /dev/null +++ b/virtio-gpud/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "virtio-gpud" +version = "0.1.0" +edition = "2021" +authors = ["Anhad Singh "] + +[dependencies] +log = "0.4" + +virtio-core = { path = "../virtio-core" } +pcid = { path = "../pcid" } + +redox-daemon = "0.1" diff --git a/virtio-gpud/src/main.rs b/virtio-gpud/src/main.rs new file mode 100644 index 0000000000..140c73ac2c --- /dev/null +++ b/virtio-gpud/src/main.rs @@ -0,0 +1,28 @@ +use pcid_interface::PcidServerHandle; +use virtio_core::transport::Error; + +fn deamon(deamon: redox_daemon::Daemon) -> Result<(), Error> { + let mut pcid_handle = PcidServerHandle::connect_default()?; + + // Double check that we have the right device. + // + // 0x1050 - virtio-gpu + let pci_config = pcid_handle.fetch_config()?; + + assert_eq!(pci_config.func.devid, 0x1050); + log::info!("virtio-gpu: initiating startup sequence :^)"); + + let device = virtio_core::probe_device(&mut pcid_handle)?; + loop {} +} + +fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! { + deamon(redox_daemon).unwrap(); + unreachable!(); +} + +pub fn main() { + #[cfg(target_os = "redox")] + virtio_core::utils::setup_logging(log::LevelFilter::Trace, "virtio-gpud"); + redox_daemon::Daemon::new(daemon_runner).expect("virtio-core: failed to daemonize"); +}