diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 00000000..5461e78f --- /dev/null +++ b/src/config.rs @@ -0,0 +1,114 @@ +//! Types and macros for VirtIO device configuration space. + +use crate::{transport::Transport, Error}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +/// A configuration space register from which the driver can only read. +#[derive(Default, FromBytes, Immutable, IntoBytes)] +#[repr(transparent)] +pub struct ReadOnly(pub T); + +impl ReadOnly { + /// Constructs a new instance for testing. + pub const fn new(value: T) -> Self { + Self(value) + } +} + +/// A configuration space register to which the driver can only write. +#[derive(Default, FromBytes, Immutable, IntoBytes)] +#[repr(transparent)] +pub struct WriteOnly(pub T); + +impl WriteOnly { + /// Constructs a new instance for testing. + pub const fn new(value: T) -> Self { + Self(value) + } +} + +/// A configuration space register which the driver may both read and write. +#[derive(Default, FromBytes, Immutable, IntoBytes)] +#[repr(transparent)] +pub struct ReadWrite(pub T); + +impl ReadWrite { + /// Constructs a new instance for testing. + pub const fn new(value: T) -> Self { + Self(value) + } +} + +/// Marker trait for configuration space registers from which the driver may read. +pub trait ConfigReadable {} + +/// Marker trait for configuration space registers to which the driver may write. +pub trait ConfigWritable {} + +impl ConfigReadable for ReadOnly {} +impl ConfigReadable for ReadWrite {} +impl ConfigWritable for ReadWrite {} +impl ConfigWritable for WriteOnly {} + +/// Wrapper for `Transport::read_config_space`` with an extra dummy parameter to force the correct +/// type to be inferred. +#[inline(always)] +pub(crate) fn read_help( + transport: &T, + offset: usize, + _dummy_r: Option, +) -> Result +where + T: Transport, + V: FromBytes, + R: ConfigReadable, +{ + transport.read_config_space(offset) +} + +/// Wrapper for Transport::write_config_space with an extra dummy parameter to force the correct +/// type to be inferred. +#[inline(always)] +pub(crate) fn write_help( + transport: &mut T, + offset: usize, + value: V, + _dummy_w: Option, +) -> Result<(), Error> +where + T: Transport, + V: Immutable + IntoBytes, + W: ConfigWritable, +{ + transport.write_config_space(offset, value) +} + +/// Reads the given field of the given struct from the device config space via the given transport. +macro_rules! read_config { + ($transport:expr, $struct:ty, $field:ident) => {{ + let dummy_struct: Option<$struct> = None; + let dummy_field = dummy_struct.map(|s| s.$field); + crate::config::read_help( + &$transport, + core::mem::offset_of!($struct, $field), + dummy_field, + ) + }}; +} + +/// Writes the given field of the given struct from the device config space via the given transport. +macro_rules! write_config { + ($transport:expr, $struct:ty, $field:ident, $value:expr) => {{ + let dummy_struct: Option<$struct> = None; + let dummy_field = dummy_struct.map(|s| s.$field); + crate::config::write_help( + &mut $transport, + core::mem::offset_of!($struct, $field), + $value, + dummy_field, + ) + }}; +} + +pub(crate) use read_config; +pub(crate) use write_config; diff --git a/src/device/blk.rs b/src/device/blk.rs index 6fc9d252..dc4a6c52 100644 --- a/src/device/blk.rs +++ b/src/device/blk.rs @@ -1,12 +1,11 @@ //! Driver for VirtIO block devices. +use crate::config::{read_config, ReadOnly}; use crate::hal::Hal; use crate::queue::VirtQueue; use crate::transport::Transport; -use crate::volatile::Volatile; use crate::{Error, Result}; use bitflags::bitflags; -use core::mem::offset_of; use log::info; use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; @@ -57,12 +56,8 @@ impl VirtIOBlk { // Read configuration space. let capacity = transport.read_consistent(|| { - Ok( - transport.read_config_space::(offset_of!(BlkConfig, capacity_low))? as u64 - | (transport.read_config_space::(offset_of!(BlkConfig, capacity_high))? - as u64) - << 32, - ) + Ok(read_config!(transport, BlkConfig, capacity_low)? as u64 + | (read_config!(transport, BlkConfig, capacity_high)? as u64) << 32) })?; info!("found a block device of size {}KB", capacity / 2); @@ -393,21 +388,22 @@ impl Drop for VirtIOBlk { } } +#[derive(FromBytes, Immutable, IntoBytes)] #[repr(C)] struct BlkConfig { /// Number of 512 Bytes sectors - capacity_low: Volatile, - capacity_high: Volatile, - size_max: Volatile, - seg_max: Volatile, - cylinders: Volatile, - heads: Volatile, - sectors: Volatile, - blk_size: Volatile, - physical_block_exp: Volatile, - alignment_offset: Volatile, - min_io_size: Volatile, - opt_io_size: Volatile, + capacity_low: ReadOnly, + capacity_high: ReadOnly, + size_max: ReadOnly, + seg_max: ReadOnly, + cylinders: ReadOnly, + heads: ReadOnly, + sectors: ReadOnly, + blk_size: ReadOnly, + physical_block_exp: ReadOnly, + alignment_offset: ReadOnly, + min_io_size: ReadOnly, + opt_io_size: ReadOnly, // ... ignored } @@ -564,34 +560,33 @@ mod tests { }, }; use alloc::{sync::Arc, vec}; - use core::{mem::size_of, ptr::NonNull}; + use core::mem::size_of; use std::{sync::Mutex, thread}; #[test] fn config() { - let mut config_space = BlkConfig { - capacity_low: Volatile::new(0x42), - capacity_high: Volatile::new(0x02), - size_max: Volatile::new(0), - seg_max: Volatile::new(0), - cylinders: Volatile::new(0), - heads: Volatile::new(0), - sectors: Volatile::new(0), - blk_size: Volatile::new(0), - physical_block_exp: Volatile::new(0), - alignment_offset: Volatile::new(0), - min_io_size: Volatile::new(0), - opt_io_size: Volatile::new(0), + let config_space = BlkConfig { + capacity_low: ReadOnly::new(0x42), + capacity_high: ReadOnly::new(0x02), + size_max: ReadOnly::new(0), + seg_max: ReadOnly::new(0), + cylinders: ReadOnly::new(0), + heads: ReadOnly::new(0), + sectors: ReadOnly::new(0), + blk_size: ReadOnly::new(0), + physical_block_exp: ReadOnly::new(0), + alignment_offset: ReadOnly::new(0), + min_io_size: ReadOnly::new(0), + opt_io_size: ReadOnly::new(0), }; - let state = Arc::new(Mutex::new(State { - queues: vec![QueueStatus::default()], - ..Default::default() - })); + let state = Arc::new(Mutex::new(State::new( + vec![QueueStatus::default()], + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Block, max_queue_size: QUEUE_SIZE.into(), device_features: BlkFeature::RO.bits(), - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let blk = VirtIOBlk::>::new(transport).unwrap(); @@ -602,29 +597,28 @@ mod tests { #[test] fn read() { - let mut config_space = BlkConfig { - capacity_low: Volatile::new(66), - capacity_high: Volatile::new(0), - size_max: Volatile::new(0), - seg_max: Volatile::new(0), - cylinders: Volatile::new(0), - heads: Volatile::new(0), - sectors: Volatile::new(0), - blk_size: Volatile::new(0), - physical_block_exp: Volatile::new(0), - alignment_offset: Volatile::new(0), - min_io_size: Volatile::new(0), - opt_io_size: Volatile::new(0), + let config_space = BlkConfig { + capacity_low: ReadOnly::new(66), + capacity_high: ReadOnly::new(0), + size_max: ReadOnly::new(0), + seg_max: ReadOnly::new(0), + cylinders: ReadOnly::new(0), + heads: ReadOnly::new(0), + sectors: ReadOnly::new(0), + blk_size: ReadOnly::new(0), + physical_block_exp: ReadOnly::new(0), + alignment_offset: ReadOnly::new(0), + min_io_size: ReadOnly::new(0), + opt_io_size: ReadOnly::new(0), }; - let state = Arc::new(Mutex::new(State { - queues: vec![QueueStatus::default()], - ..Default::default() - })); + let state = Arc::new(Mutex::new(State::new( + vec![QueueStatus::default()], + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Block, max_queue_size: QUEUE_SIZE.into(), device_features: BlkFeature::RING_INDIRECT_DESC.bits(), - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let mut blk = VirtIOBlk::>::new(transport).unwrap(); @@ -672,29 +666,28 @@ mod tests { #[test] fn write() { - let mut config_space = BlkConfig { - capacity_low: Volatile::new(66), - capacity_high: Volatile::new(0), - size_max: Volatile::new(0), - seg_max: Volatile::new(0), - cylinders: Volatile::new(0), - heads: Volatile::new(0), - sectors: Volatile::new(0), - blk_size: Volatile::new(0), - physical_block_exp: Volatile::new(0), - alignment_offset: Volatile::new(0), - min_io_size: Volatile::new(0), - opt_io_size: Volatile::new(0), + let config_space = BlkConfig { + capacity_low: ReadOnly::new(66), + capacity_high: ReadOnly::new(0), + size_max: ReadOnly::new(0), + seg_max: ReadOnly::new(0), + cylinders: ReadOnly::new(0), + heads: ReadOnly::new(0), + sectors: ReadOnly::new(0), + blk_size: ReadOnly::new(0), + physical_block_exp: ReadOnly::new(0), + alignment_offset: ReadOnly::new(0), + min_io_size: ReadOnly::new(0), + opt_io_size: ReadOnly::new(0), }; - let state = Arc::new(Mutex::new(State { - queues: vec![QueueStatus::default()], - ..Default::default() - })); + let state = Arc::new(Mutex::new(State::new( + vec![QueueStatus::default()], + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Block, max_queue_size: QUEUE_SIZE.into(), device_features: BlkFeature::RING_INDIRECT_DESC.bits(), - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let mut blk = VirtIOBlk::>::new(transport).unwrap(); @@ -747,29 +740,28 @@ mod tests { #[test] fn flush() { - let mut config_space = BlkConfig { - capacity_low: Volatile::new(66), - capacity_high: Volatile::new(0), - size_max: Volatile::new(0), - seg_max: Volatile::new(0), - cylinders: Volatile::new(0), - heads: Volatile::new(0), - sectors: Volatile::new(0), - blk_size: Volatile::new(0), - physical_block_exp: Volatile::new(0), - alignment_offset: Volatile::new(0), - min_io_size: Volatile::new(0), - opt_io_size: Volatile::new(0), + let config_space = BlkConfig { + capacity_low: ReadOnly::new(66), + capacity_high: ReadOnly::new(0), + size_max: ReadOnly::new(0), + seg_max: ReadOnly::new(0), + cylinders: ReadOnly::new(0), + heads: ReadOnly::new(0), + sectors: ReadOnly::new(0), + blk_size: ReadOnly::new(0), + physical_block_exp: ReadOnly::new(0), + alignment_offset: ReadOnly::new(0), + min_io_size: ReadOnly::new(0), + opt_io_size: ReadOnly::new(0), }; - let state = Arc::new(Mutex::new(State { - queues: vec![QueueStatus::default()], - ..Default::default() - })); + let state = Arc::new(Mutex::new(State::new( + vec![QueueStatus::default()], + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Block, max_queue_size: QUEUE_SIZE.into(), device_features: (BlkFeature::RING_INDIRECT_DESC | BlkFeature::FLUSH).bits(), - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let mut blk = VirtIOBlk::>::new(transport).unwrap(); @@ -814,29 +806,28 @@ mod tests { #[test] fn device_id() { - let mut config_space = BlkConfig { - capacity_low: Volatile::new(66), - capacity_high: Volatile::new(0), - size_max: Volatile::new(0), - seg_max: Volatile::new(0), - cylinders: Volatile::new(0), - heads: Volatile::new(0), - sectors: Volatile::new(0), - blk_size: Volatile::new(0), - physical_block_exp: Volatile::new(0), - alignment_offset: Volatile::new(0), - min_io_size: Volatile::new(0), - opt_io_size: Volatile::new(0), + let config_space = BlkConfig { + capacity_low: ReadOnly::new(66), + capacity_high: ReadOnly::new(0), + size_max: ReadOnly::new(0), + seg_max: ReadOnly::new(0), + cylinders: ReadOnly::new(0), + heads: ReadOnly::new(0), + sectors: ReadOnly::new(0), + blk_size: ReadOnly::new(0), + physical_block_exp: ReadOnly::new(0), + alignment_offset: ReadOnly::new(0), + min_io_size: ReadOnly::new(0), + opt_io_size: ReadOnly::new(0), }; - let state = Arc::new(Mutex::new(State { - queues: vec![QueueStatus::default()], - ..Default::default() - })); + let state = Arc::new(Mutex::new(State::new( + vec![QueueStatus::default()], + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Block, max_queue_size: QUEUE_SIZE.into(), device_features: BlkFeature::RING_INDIRECT_DESC.bits(), - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let mut blk = VirtIOBlk::>::new(transport).unwrap(); diff --git a/src/device/console.rs b/src/device/console.rs index a8d6095a..c8c49d19 100644 --- a/src/device/console.rs +++ b/src/device/console.rs @@ -3,16 +3,16 @@ #[cfg(feature = "embedded-io")] mod embedded_io; +use crate::config::{read_config, write_config, ReadOnly, WriteOnly}; use crate::hal::Hal; use crate::queue::VirtQueue; use crate::transport::Transport; -use crate::volatile::{ReadOnly, WriteOnly}; use crate::{Error, Result, PAGE_SIZE}; use alloc::boxed::Box; use bitflags::bitflags; use core::fmt::{self, Display, Formatter, Write}; -use core::mem::offset_of; use log::error; +use zerocopy::{FromBytes, Immutable, IntoBytes}; const QUEUE_RECEIVEQ_PORT_0: u16 = 0; const QUEUE_TRANSMITQ_PORT_0: u16 = 1; @@ -129,8 +129,8 @@ impl VirtIOConsole { if self.negotiated_features.contains(Features::SIZE) { self.transport.read_consistent(|| { Ok(Some(Size { - columns: self.transport.read_config_space(offset_of!(Config, cols))?, - rows: self.transport.read_config_space(offset_of!(Config, rows))?, + columns: read_config!(self.transport, Config, cols)?, + rows: read_config!(self.transport, Config, rows)?, })) }) } else { @@ -240,8 +240,7 @@ impl VirtIOConsole { /// Returns an error if the device doesn't support emergency write. pub fn emergency_write(&mut self, chr: u8) -> Result<()> { if self.negotiated_features.contains(Features::EMERG_WRITE) { - self.transport - .write_config_space::(offset_of!(Config, emerg_wr), chr.into())?; + write_config!(self.transport, Config, emerg_wr, chr.into())?; Ok(()) } else { Err(Error::Unsupported) @@ -267,6 +266,7 @@ impl Drop for VirtIOConsole { } } +#[derive(FromBytes, Immutable, IntoBytes)] #[repr(C)] struct Config { cols: ReadOnly, @@ -311,26 +311,24 @@ mod tests { }, }; use alloc::{sync::Arc, vec}; - use core::ptr::NonNull; use std::{sync::Mutex, thread}; #[test] fn config_info_no_features() { - let mut config_space = Config { + let config_space = Config { cols: ReadOnly::new(80), rows: ReadOnly::new(42), max_nr_ports: ReadOnly::new(0), emerg_wr: WriteOnly::default(), }; - let state = Arc::new(Mutex::new(State { - queues: vec![QueueStatus::default(), QueueStatus::default()], - ..Default::default() - })); + let state = Arc::new(Mutex::new(State::new( + vec![QueueStatus::default(), QueueStatus::default()], + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Console, max_queue_size: 2, device_features: 0, - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let console = VirtIOConsole::>::new(transport).unwrap(); @@ -340,21 +338,20 @@ mod tests { #[test] fn config_info() { - let mut config_space = Config { + let config_space = Config { cols: ReadOnly::new(80), rows: ReadOnly::new(42), max_nr_ports: ReadOnly::new(0), emerg_wr: WriteOnly::default(), }; - let state = Arc::new(Mutex::new(State { - queues: vec![QueueStatus::default(), QueueStatus::default()], - ..Default::default() - })); + let state = Arc::new(Mutex::new(State::new( + vec![QueueStatus::default(), QueueStatus::default()], + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Console, max_queue_size: 2, device_features: 0x07, - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let console = VirtIOConsole::>::new(transport).unwrap(); @@ -370,46 +367,44 @@ mod tests { #[test] fn emergency_write() { - let mut config_space = Config { + let config_space = Config { cols: ReadOnly::new(0), rows: ReadOnly::new(0), max_nr_ports: ReadOnly::new(0), emerg_wr: WriteOnly::default(), }; - let state = Arc::new(Mutex::new(State { - queues: vec![QueueStatus::default(), QueueStatus::default()], - ..Default::default() - })); + let state = Arc::new(Mutex::new(State::new( + vec![QueueStatus::default(), QueueStatus::default()], + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Console, max_queue_size: 2, device_features: 0x07, - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let mut console = VirtIOConsole::>::new(transport).unwrap(); console.emergency_write(42).unwrap(); - assert_eq!(config_space.emerg_wr.0, 42); + assert_eq!(state.lock().unwrap().config_space.emerg_wr.0, 42); } #[test] fn receive() { - let mut config_space = Config { + let config_space = Config { cols: ReadOnly::new(0), rows: ReadOnly::new(0), max_nr_ports: ReadOnly::new(0), emerg_wr: WriteOnly::default(), }; - let state = Arc::new(Mutex::new(State { - queues: vec![QueueStatus::default(), QueueStatus::default()], - ..Default::default() - })); + let state = Arc::new(Mutex::new(State::new( + vec![QueueStatus::default(), QueueStatus::default()], + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Console, max_queue_size: 2, device_features: 0, - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let mut console = VirtIOConsole::>::new(transport).unwrap(); @@ -440,21 +435,20 @@ mod tests { #[test] fn send() { - let mut config_space = Config { + let config_space = Config { cols: ReadOnly::new(0), rows: ReadOnly::new(0), max_nr_ports: ReadOnly::new(0), emerg_wr: WriteOnly::default(), }; - let state = Arc::new(Mutex::new(State { - queues: vec![QueueStatus::default(), QueueStatus::default()], - ..Default::default() - })); + let state = Arc::new(Mutex::new(State::new( + vec![QueueStatus::default(), QueueStatus::default()], + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Console, max_queue_size: 2, device_features: 0, - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let mut console = VirtIOConsole::>::new(transport).unwrap(); diff --git a/src/device/gpu.rs b/src/device/gpu.rs index d299a990..75a5fefa 100644 --- a/src/device/gpu.rs +++ b/src/device/gpu.rs @@ -1,13 +1,12 @@ //! Driver for VirtIO GPU devices. +use crate::config::{read_config, ReadOnly, WriteOnly}; use crate::hal::{BufferDirection, Dma, Hal}; use crate::queue::VirtQueue; use crate::transport::Transport; -use crate::volatile::{ReadOnly, Volatile, WriteOnly}; use crate::{pages, Error, Result, PAGE_SIZE}; use alloc::boxed::Box; use bitflags::bitflags; -use core::mem::offset_of; use log::info; use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout}; @@ -44,8 +43,8 @@ impl VirtIOGpu { let negotiated_features = transport.begin_init(SUPPORTED_FEATURES); // read configuration space - let events_read = transport.read_config_space::(offset_of!(Config, events_read))?; - let num_scanouts = transport.read_config_space::(offset_of!(Config, num_scanouts))?; + let events_read = read_config!(transport, Config, events_read)?; + let num_scanouts = read_config!(transport, Config, num_scanouts)?; info!( "events_read: {:#x}, num_scanouts: {:#x}", events_read, num_scanouts @@ -303,7 +302,7 @@ struct Config { /// Specifies the maximum number of scanouts supported by the device. /// /// Minimum value is 1, maximum value is 16. - num_scanouts: Volatile, + num_scanouts: ReadOnly, } /// Display configuration has changed. diff --git a/src/device/input.rs b/src/device/input.rs index 6259e391..1947497a 100644 --- a/src/device/input.rs +++ b/src/device/input.rs @@ -1,10 +1,10 @@ //! Driver for VirtIO input devices. use super::common::Feature; +use crate::config::{read_config, write_config, ReadOnly, WriteOnly}; use crate::hal::Hal; use crate::queue::VirtQueue; use crate::transport::Transport; -use crate::volatile::{ReadOnly, WriteOnly}; use crate::Error; use alloc::{boxed::Box, string::String}; use core::cmp::min; @@ -103,11 +103,9 @@ impl VirtIOInput { subsel: u8, out: &mut [u8], ) -> Result { - self.transport - .write_config_space(offset_of!(Config, select), select as u8)?; - self.transport - .write_config_space(offset_of!(Config, subsel), subsel)?; - let size: u8 = self.transport.read_config_space(offset_of!(Config, size))?; + write_config!(self.transport, Config, select, select as u8)?; + write_config!(self.transport, Config, subsel, subsel)?; + let size: u8 = read_config!(self.transport, Config, size)?; // Safe because config points to a valid MMIO region for the config space. let size_to_copy = min(usize::from(size), out.len()); for (i, out_item) in out.iter_mut().take(size_to_copy).enumerate() { @@ -126,14 +124,9 @@ impl VirtIOInput { select: InputConfigSelect, subsel: u8, ) -> Result, Error> { - self.transport - .write_config_space(offset_of!(Config, select), select as u8)?; - self.transport - .write_config_space(offset_of!(Config, subsel), subsel)?; - let size = usize::from( - self.transport - .read_config_space::(offset_of!(Config, size))?, - ); + write_config!(self.transport, Config, select, select as u8)?; + write_config!(self.transport, Config, subsel, subsel)?; + let size = usize::from(read_config!(self.transport, Config, size)?); if size > CONFIG_DATA_MAX_LENGTH { return Err(Error::IoError); } @@ -254,6 +247,7 @@ pub enum InputConfigSelect { AbsInfo = 0x12, } +#[derive(FromBytes, Immutable, IntoBytes)] #[repr(C)] struct Config { select: WriteOnly, @@ -324,41 +318,52 @@ mod tests { }, }; use alloc::{sync::Arc, vec}; - use core::{convert::TryInto, ptr::NonNull}; + use core::convert::TryInto; use std::sync::Mutex; #[test] fn config() { const DEFAULT_DATA: ReadOnly = ReadOnly::new(0); - let mut config_space = Config { + let config_space = Config { select: WriteOnly::default(), subsel: WriteOnly::default(), size: ReadOnly::new(0), _reserved: Default::default(), data: [DEFAULT_DATA; 128], }; - let state = Arc::new(Mutex::new(State { - queues: vec![QueueStatus::default(), QueueStatus::default()], - ..Default::default() - })); + let state = Arc::new(Mutex::new(State::new( + vec![QueueStatus::default(), QueueStatus::default()], + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Block, max_queue_size: QUEUE_SIZE.try_into().unwrap(), device_features: 0, - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let mut input = VirtIOInput::>::new(transport).unwrap(); - set_data(&mut config_space, "Test input device".as_bytes()); + set_data( + &mut state.lock().unwrap().config_space, + "Test input device".as_bytes(), + ); assert_eq!(input.name().unwrap(), "Test input device"); - assert_eq!(config_space.select.0, InputConfigSelect::IdName as u8); - assert_eq!(config_space.subsel.0, 0); + assert_eq!( + state.lock().unwrap().config_space.select.0, + InputConfigSelect::IdName as u8 + ); + assert_eq!(state.lock().unwrap().config_space.subsel.0, 0); - set_data(&mut config_space, "Serial number".as_bytes()); + set_data( + &mut state.lock().unwrap().config_space, + "Serial number".as_bytes(), + ); assert_eq!(input.serial_number().unwrap(), "Serial number"); - assert_eq!(config_space.select.0, InputConfigSelect::IdSerial as u8); - assert_eq!(config_space.subsel.0, 0); + assert_eq!( + state.lock().unwrap().config_space.select.0, + InputConfigSelect::IdSerial as u8 + ); + assert_eq!(state.lock().unwrap().config_space.subsel.0, 0); let ids = DevIDs { bustype: 0x4242, @@ -366,20 +371,29 @@ mod tests { vendor: 0x1234, version: 0x4321, }; - set_data(&mut config_space, ids.as_bytes()); + set_data(&mut state.lock().unwrap().config_space, ids.as_bytes()); assert_eq!(input.ids().unwrap(), ids); - assert_eq!(config_space.select.0, InputConfigSelect::IdDevids as u8); - assert_eq!(config_space.subsel.0, 0); + assert_eq!( + state.lock().unwrap().config_space.select.0, + InputConfigSelect::IdDevids as u8 + ); + assert_eq!(state.lock().unwrap().config_space.subsel.0, 0); - set_data(&mut config_space, &[0x12, 0x34, 0x56]); + set_data(&mut state.lock().unwrap().config_space, &[0x12, 0x34, 0x56]); assert_eq!(input.prop_bits().unwrap().as_ref(), &[0x12, 0x34, 0x56]); - assert_eq!(config_space.select.0, InputConfigSelect::PropBits as u8); - assert_eq!(config_space.subsel.0, 0); + assert_eq!( + state.lock().unwrap().config_space.select.0, + InputConfigSelect::PropBits as u8 + ); + assert_eq!(state.lock().unwrap().config_space.subsel.0, 0); - set_data(&mut config_space, &[0x42, 0x66]); + set_data(&mut state.lock().unwrap().config_space, &[0x42, 0x66]); assert_eq!(input.ev_bits(3).unwrap().as_ref(), &[0x42, 0x66]); - assert_eq!(config_space.select.0, InputConfigSelect::EvBits as u8); - assert_eq!(config_space.subsel.0, 3); + assert_eq!( + state.lock().unwrap().config_space.select.0, + InputConfigSelect::EvBits as u8 + ); + assert_eq!(state.lock().unwrap().config_space.subsel.0, 3); let abs_info = AbsInfo { min: 12, @@ -388,10 +402,13 @@ mod tests { flat: 10, res: 2, }; - set_data(&mut config_space, abs_info.as_bytes()); + set_data(&mut state.lock().unwrap().config_space, abs_info.as_bytes()); assert_eq!(input.abs_info(5).unwrap(), abs_info); - assert_eq!(config_space.select.0, InputConfigSelect::AbsInfo as u8); - assert_eq!(config_space.subsel.0, 5); + assert_eq!( + state.lock().unwrap().config_space.select.0, + InputConfigSelect::AbsInfo as u8 + ); + assert_eq!(state.lock().unwrap().config_space.subsel.0, 5); } fn set_data(config_space: &mut Config, value: &[u8]) { diff --git a/src/device/net/dev_raw.rs b/src/device/net/dev_raw.rs index 58d30d8d..ca6fd21d 100644 --- a/src/device/net/dev_raw.rs +++ b/src/device/net/dev_raw.rs @@ -1,11 +1,10 @@ use super::{Config, EthernetAddress, Features, VirtioNetHdr}; use super::{MIN_BUFFER_LEN, NET_HDR_SIZE, QUEUE_RECEIVE, QUEUE_TRANSMIT, SUPPORTED_FEATURES}; -use crate::device::net::Status; +use crate::config::read_config; use crate::hal::Hal; use crate::queue::VirtQueue; use crate::transport::Transport; use crate::{Error, Result}; -use core::mem::offset_of; use log::{debug, info, warn}; use zerocopy::IntoBytes; @@ -31,9 +30,8 @@ impl VirtIONetRaw(offset_of!(Config, status))?; + let mac = transport.read_consistent(|| read_config!(transport, Config, mac))?; + let status = read_config!(transport, Config, status)?; debug!("Got MAC={:02x?}, status={:?}", mac, status); let send_queue = VirtQueue::new( diff --git a/src/device/net/mod.rs b/src/device/net/mod.rs index d55a93b2..6adc7abf 100644 --- a/src/device/net/mod.rs +++ b/src/device/net/mod.rs @@ -10,7 +10,7 @@ pub use self::dev_raw::VirtIONetRaw; #[cfg(feature = "alloc")] pub use self::{dev::VirtIONet, net_buf::RxBuffer, net_buf::TxBuffer}; -use crate::volatile::ReadOnly; +use crate::config::ReadOnly; use bitflags::bitflags; use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; diff --git a/src/device/socket/connectionmanager.rs b/src/device/socket/connectionmanager.rs index 96c7cd24..8e823eb7 100644 --- a/src/device/socket/connectionmanager.rs +++ b/src/device/socket/connectionmanager.rs @@ -415,6 +415,7 @@ impl RingBuffer { mod tests { use super::*; use crate::{ + config::ReadOnly, device::socket::{ protocol::{ SocketType, StreamShutdown, VirtioVsockConfig, VirtioVsockHdr, VirtioVsockOp, @@ -426,10 +427,9 @@ mod tests { fake::{FakeTransport, QueueStatus, State}, DeviceType, }, - volatile::ReadOnly, }; use alloc::{sync::Arc, vec}; - use core::{mem::size_of, ptr::NonNull}; + use core::mem::size_of; use std::{sync::Mutex, thread}; use zerocopy::{FromBytes, IntoBytes}; @@ -446,23 +446,22 @@ mod tests { let hello_from_guest = "Hello from guest"; let hello_from_host = "Hello from host"; - let mut config_space = VirtioVsockConfig { + let config_space = VirtioVsockConfig { guest_cid_low: ReadOnly::new(66), guest_cid_high: ReadOnly::new(0), }; - let state = Arc::new(Mutex::new(State { - queues: vec![ + let state = Arc::new(Mutex::new(State::new( + vec![ QueueStatus::default(), QueueStatus::default(), QueueStatus::default(), ], - ..Default::default() - })); + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Socket, max_queue_size: 32, device_features: 0, - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let mut socket = VsockConnectionManager::new( @@ -661,23 +660,22 @@ mod tests { port: host_port, }; - let mut config_space = VirtioVsockConfig { + let config_space = VirtioVsockConfig { guest_cid_low: ReadOnly::new(66), guest_cid_high: ReadOnly::new(0), }; - let state = Arc::new(Mutex::new(State { - queues: vec![ + let state = Arc::new(Mutex::new(State::new( + vec![ QueueStatus::default(), QueueStatus::default(), QueueStatus::default(), ], - ..Default::default() - })); + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Socket, max_queue_size: 32, device_features: 0, - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let mut socket = VsockConnectionManager::new( diff --git a/src/device/socket/protocol.rs b/src/device/socket/protocol.rs index af5b85df..baffadd7 100644 --- a/src/device/socket/protocol.rs +++ b/src/device/socket/protocol.rs @@ -1,7 +1,7 @@ //! This module defines the socket device protocol according to the virtio spec v1.1 5.10 Socket Device use super::error::{self, SocketError}; -use crate::volatile::ReadOnly; +use crate::config::ReadOnly; use bitflags::bitflags; use core::{ convert::{TryFrom, TryInto}, @@ -32,6 +32,7 @@ impl From for U16 { } /// VirtioVsockConfig is the vsock device configuration space. +#[derive(FromBytes, Immutable, IntoBytes)] #[repr(C)] pub struct VirtioVsockConfig { /// The guest_cid field contains the guest’s context ID, which uniquely identifies diff --git a/src/device/socket/vsock.rs b/src/device/socket/vsock.rs index 5270e82e..8a95ee49 100644 --- a/src/device/socket/vsock.rs +++ b/src/device/socket/vsock.rs @@ -6,11 +6,12 @@ use super::protocol::{ Feature, StreamShutdown, VirtioVsockConfig, VirtioVsockHdr, VirtioVsockOp, VsockAddr, }; use super::DEFAULT_RX_BUFFER_SIZE; +use crate::config::read_config; use crate::hal::Hal; use crate::queue::{owning::OwningQueue, VirtQueue}; use crate::transport::Transport; use crate::Result; -use core::mem::{offset_of, size_of}; +use core::mem::size_of; use log::debug; use zerocopy::{FromBytes, IntoBytes}; @@ -249,12 +250,8 @@ impl VirtIOSocket(offset_of!(VirtioVsockConfig, guest_cid_low))? - as u64 - | (transport - .read_config_space::(offset_of!(VirtioVsockConfig, guest_cid_high))? - as u64) - << 32, + read_config!(transport, VirtioVsockConfig, guest_cid_low)? as u64 + | (read_config!(transport, VirtioVsockConfig, guest_cid_high)? as u64) << 32, ) })?; debug!("guest cid: {guest_cid:?}"); @@ -465,36 +462,34 @@ fn read_header_and_body(buffer: &[u8]) -> Result<(VirtioVsockHdr, &[u8])> { mod tests { use super::*; use crate::{ + config::ReadOnly, hal::fake::FakeHal, transport::{ fake::{FakeTransport, QueueStatus, State}, DeviceType, }, - volatile::ReadOnly, }; use alloc::{sync::Arc, vec}; - use core::ptr::NonNull; use std::sync::Mutex; #[test] fn config() { - let mut config_space = VirtioVsockConfig { + let config_space = VirtioVsockConfig { guest_cid_low: ReadOnly::new(66), guest_cid_high: ReadOnly::new(0), }; - let state = Arc::new(Mutex::new(State { - queues: vec![ + let state = Arc::new(Mutex::new(State::new( + vec![ QueueStatus::default(), QueueStatus::default(), QueueStatus::default(), ], - ..Default::default() - })); + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Socket, max_queue_size: 32, device_features: 0, - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let socket = diff --git a/src/device/sound.rs b/src/device/sound.rs index 1bd73e8c..f5f71ee6 100644 --- a/src/device/sound.rs +++ b/src/device/sound.rs @@ -5,9 +5,9 @@ mod fake; use super::common::Feature; use crate::{ + config::{read_config, ReadOnly}, queue::{owning::OwningQueue, VirtQueue}, transport::Transport, - volatile::ReadOnly, Error, Hal, Result, PAGE_SIZE, }; use alloc::{boxed::Box, collections::BTreeMap, vec, vec::Vec}; @@ -16,7 +16,7 @@ use core::{ array, fmt::{self, Debug, Display, Formatter}, hint::spin_loop, - mem::{offset_of, size_of}, + mem::size_of, ops::RangeInclusive, }; use enumn::N; @@ -96,9 +96,9 @@ impl VirtIOSound { )?; // read configuration space - let jacks = transport.read_config_space(offset_of!(VirtIOSoundConfig, jacks))?; - let streams = transport.read_config_space(offset_of!(VirtIOSoundConfig, streams))?; - let chmaps = transport.read_config_space(offset_of!(VirtIOSoundConfig, chmaps))?; + let jacks = read_config!(transport, VirtIOSoundConfig, jacks)?; + let streams = read_config!(transport, VirtIOSoundConfig, streams)?; + let chmaps = read_config!(transport, VirtIOSoundConfig, chmaps)?; info!( "[sound device] config: jacks: {}, streams: {}, chmaps: {}", jacks, streams, chmaps @@ -1012,6 +1012,7 @@ impl From for u8 { } } +#[derive(FromBytes, Immutable, IntoBytes)] #[repr(C)] struct VirtIOSoundConfig { jacks: ReadOnly, @@ -1558,39 +1559,37 @@ impl Display for VirtIOSndChmapInfo { mod tests { use super::*; use crate::{ + config::ReadOnly, hal::fake::FakeHal, transport::{ fake::{FakeTransport, QueueStatus, State}, DeviceType, }, - volatile::ReadOnly, }; use alloc::{sync::Arc, vec}; - use core::ptr::NonNull; use fake::FakeSoundDevice; use std::sync::Mutex; #[test] fn config() { - let mut config_space = VirtIOSoundConfig { + let config_space = VirtIOSoundConfig { jacks: ReadOnly::new(3), streams: ReadOnly::new(4), chmaps: ReadOnly::new(2), }; - let state = Arc::new(Mutex::new(State { - queues: vec![ + let state = Arc::new(Mutex::new(State::new( + vec![ QueueStatus::default(), QueueStatus::default(), QueueStatus::default(), QueueStatus::default(), ], - ..Default::default() - })); + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Sound, max_queue_size: 32, device_features: 0, - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let sound = diff --git a/src/device/sound/fake.rs b/src/device/sound/fake.rs index 15d81ad3..d698824f 100644 --- a/src/device/sound/fake.rs +++ b/src/device/sound/fake.rs @@ -6,18 +6,17 @@ use super::{ QUEUE_SIZE, TX_QUEUE_IDX, }; use crate::{ + config::ReadOnly, device::sound::{VirtIOSndPcmHdr, VirtIOSndPcmSetParams}, transport::{ fake::{FakeTransport, QueueStatus, State}, DeviceType, }, - volatile::ReadOnly, }; use alloc::{sync::Arc, vec}; use core::{ convert::{TryFrom, TryInto}, mem::size_of, - ptr::NonNull, time::Duration, }; use std::{ @@ -32,7 +31,7 @@ use zerocopy::{FromBytes, IntoBytes}; #[derive(Clone, Debug)] pub struct FakeSoundDevice { - pub state: Arc>, + pub state: Arc>>, pub terminate: Arc, /// The paramaters set for each stream, if any. pub params: Arc>>>, @@ -49,25 +48,24 @@ impl FakeSoundDevice { pcm_infos: Vec, chmap_infos: Vec, ) -> (Self, FakeTransport) { - let mut config_space = VirtIOSoundConfig { + let config_space = VirtIOSoundConfig { jacks: ReadOnly::new(jack_infos.len().try_into().unwrap()), streams: ReadOnly::new(pcm_infos.len().try_into().unwrap()), chmaps: ReadOnly::new(chmap_infos.len().try_into().unwrap()), }; - let state = Arc::new(Mutex::new(State { - queues: vec![ + let state = Arc::new(Mutex::new(State::new( + vec![ QueueStatus::default(), QueueStatus::default(), QueueStatus::default(), QueueStatus::default(), ], - ..Default::default() - })); + config_space, + ))); let transport = FakeTransport { device_type: DeviceType::Socket, max_queue_size: 32, device_features: 0, - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let params = repeat_with(|| None).take(pcm_infos.len()).collect(); diff --git a/src/lib.rs b/src/lib.rs index aa3737df..0d38c94b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,6 +49,7 @@ #[cfg(any(feature = "alloc", test))] extern crate alloc; +mod config; pub mod device; #[cfg(feature = "embedded-io")] mod embedded_io; diff --git a/src/queue.rs b/src/queue.rs index b21d8429..a4e01548 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -1164,16 +1164,11 @@ mod tests { /// Tests that the queue advises the device that notifications are needed. #[test] fn set_dev_notify() { - let mut config_space = (); - let state = Arc::new(Mutex::new(State { - queues: vec![QueueStatus::default()], - ..Default::default() - })); + let state = Arc::new(Mutex::new(State::new(vec![QueueStatus::default()], ()))); let mut transport = FakeTransport { device_type: DeviceType::Block, max_queue_size: 4, device_features: 0, - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let mut queue = VirtQueue::::new(&mut transport, 0, false, false).unwrap(); @@ -1205,16 +1200,11 @@ mod tests { /// notifications. #[test] fn add_notify() { - let mut config_space = (); - let state = Arc::new(Mutex::new(State { - queues: vec![QueueStatus::default()], - ..Default::default() - })); + let state = Arc::new(Mutex::new(State::new(vec![QueueStatus::default()], ()))); let mut transport = FakeTransport { device_type: DeviceType::Block, max_queue_size: 4, device_features: 0, - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let mut queue = VirtQueue::::new(&mut transport, 0, false, false).unwrap(); @@ -1240,16 +1230,11 @@ mod tests { /// notifications with the `avail_event` index. #[test] fn add_notify_event_idx() { - let mut config_space = (); - let state = Arc::new(Mutex::new(State { - queues: vec![QueueStatus::default()], - ..Default::default() - })); + let state = Arc::new(Mutex::new(State::new(vec![QueueStatus::default()], ()))); let mut transport = FakeTransport { device_type: DeviceType::Block, max_queue_size: 4, device_features: Feature::RING_EVENT_IDX.bits(), - config_space: NonNull::from(&mut config_space), state: state.clone(), }; let mut queue = VirtQueue::::new(&mut transport, 0, false, true).unwrap(); diff --git a/src/transport/fake.rs b/src/transport/fake.rs index 0fc00dc7..dd7ecd37 100644 --- a/src/transport/fake.rs +++ b/src/transport/fake.rs @@ -5,23 +5,27 @@ use crate::{ }; use alloc::{sync::Arc, vec::Vec}; use core::{ - ptr::NonNull, + fmt::{self, Debug, Formatter}, sync::atomic::{AtomicBool, Ordering}, time::Duration, }; use std::{sync::Mutex, thread}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; /// A fake implementation of [`Transport`] for unit tests. #[derive(Debug)] -pub struct FakeTransport { +pub struct FakeTransport { + /// The type of device which the transport should claim to be for. pub device_type: DeviceType, + /// The maximum queue size supported by the transport. pub max_queue_size: u32, + /// The device features which should be reported by the transport. pub device_features: u64, - pub config_space: NonNull, - pub state: Arc>, + /// The mutable state of the transport. + pub state: Arc>>, } -impl Transport for FakeTransport { +impl Transport for FakeTransport { fn device_type(&self) -> DeviceType { self.device_type } @@ -100,7 +104,7 @@ impl Transport for FakeTransport { self.state.lock().unwrap().config_generation } - fn read_config_space(&self, offset: usize) -> Result { + fn read_config_space(&self, offset: usize) -> Result { assert!(align_of::() <= 4, "Driver expected config space alignment of {} bytes, but VirtIO only guarantees 4 byte alignment.", align_of::()); @@ -109,11 +113,17 @@ impl Transport for FakeTransport { if size_of::() < offset + size_of::() { Err(Error::ConfigSpaceTooSmall) } else { - unsafe { Ok(self.config_space.cast::().byte_add(offset).read()) } + let state = self.state.lock().unwrap(); + let bytes = &state.config_space.as_bytes()[offset..offset + size_of::()]; + Ok(T::read_from_bytes(bytes).unwrap()) } } - fn write_config_space(&mut self, offset: usize, value: T) -> Result<(), Error> { + fn write_config_space( + &mut self, + offset: usize, + value: T, + ) -> Result<(), Error> { assert!(align_of::() <= 4, "Driver expected config space alignment of {} bytes, but VirtIO only guarantees 4 byte alignment.", align_of::()); @@ -122,25 +132,59 @@ impl Transport for FakeTransport { if size_of::() < offset + size_of::() { Err(Error::ConfigSpaceTooSmall) } else { - unsafe { - self.config_space.cast::().byte_add(offset).write(value); - } + let mut state = self.state.lock().unwrap(); + let bytes = &mut state.config_space.as_mut_bytes()[offset..offset + size_of::()]; + value.write_to(bytes).unwrap(); Ok(()) } } } -#[derive(Debug, Default)] -pub struct State { +/// The mutable state of a fake transport. +pub struct State { + /// The status of the fake device. pub status: DeviceStatus, + /// The features which the driver says it supports. pub driver_features: u64, + /// The guest page size set by the driver. pub guest_page_size: u32, + /// Whether the transport has an interrupt pending. pub interrupt_pending: bool, + /// The state of the transport's queues. pub queues: Vec, + /// The config generation which the transport should report. pub config_generation: u32, + /// The state of the transport's VirtIO configuration space. + pub config_space: C, +} + +impl Debug for State { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_struct("State") + .field("status", &self.status) + .field("driver_features", &self.driver_features) + .field("guest_page_size", &self.guest_page_size) + .field("interrupt_pending", &self.interrupt_pending) + .field("queues", &self.queues) + .field("config_generation", &self.config_generation) + .field("config_space", &"...") + .finish() + } } -impl State { +impl State { + pub const fn new(queues: Vec, config_space: C) -> Self { + Self { + status: DeviceStatus::empty(), + driver_features: 0, + guest_page_size: 0, + interrupt_pending: false, + queues, + config_generation: 0, + config_space, + } + } + /// Simulates the device writing to the given queue. /// /// The fake device always uses descriptors in order.