|
@@ -52,6 +52,8 @@ pub struct VirtQueue<H: Hal, const SIZE: usize> {
|
|
|
/// Our trusted copy of `avail.idx`.
|
|
|
avail_idx: u16,
|
|
|
last_used_idx: u16,
|
|
|
+ /// Whether the `VIRTIO_F_EVENT_IDX` feature has been negotiated.
|
|
|
+ event_idx: bool,
|
|
|
#[cfg(feature = "alloc")]
|
|
|
indirect: bool,
|
|
|
#[cfg(feature = "alloc")]
|
|
@@ -59,8 +61,19 @@ pub struct VirtQueue<H: Hal, const SIZE: usize> {
|
|
|
}
|
|
|
|
|
|
impl<H: Hal, const SIZE: usize> VirtQueue<H, SIZE> {
|
|
|
- /// Create a new VirtQueue.
|
|
|
- pub fn new<T: Transport>(transport: &mut T, idx: u16, indirect: bool) -> Result<Self> {
|
|
|
+ /// Creates a new VirtQueue.
|
|
|
+ ///
|
|
|
+ /// * `indirect`: Whether to use indirect descriptors. This should be set if the
|
|
|
+ /// `VIRTIO_F_INDIRECT_DESC` feature has been negotiated with the device.
|
|
|
+ /// * `event_idx`: Whether to use the `used_event` and `avail_event` fields for notification
|
|
|
+ /// suppression. This should be set if the `VIRTIO_F_EVENT_IDX` feature has been negotiated
|
|
|
+ /// with the device.
|
|
|
+ pub fn new<T: Transport>(
|
|
|
+ transport: &mut T,
|
|
|
+ idx: u16,
|
|
|
+ indirect: bool,
|
|
|
+ event_idx: bool,
|
|
|
+ ) -> Result<Self> {
|
|
|
if transport.queue_used(idx) {
|
|
|
return Err(Error::AlreadyUsed);
|
|
|
}
|
|
@@ -115,6 +128,7 @@ impl<H: Hal, const SIZE: usize> VirtQueue<H, SIZE> {
|
|
|
desc_shadow,
|
|
|
avail_idx: 0,
|
|
|
last_used_idx: 0,
|
|
|
+ event_idx,
|
|
|
#[cfg(feature = "alloc")]
|
|
|
indirect,
|
|
|
#[cfg(feature = "alloc")]
|
|
@@ -310,9 +324,16 @@ impl<H: Hal, const SIZE: usize> VirtQueue<H, SIZE> {
|
|
|
// Read barrier, so we read a fresh value from the device.
|
|
|
fence(Ordering::SeqCst);
|
|
|
|
|
|
- // Safe because self.used points to a valid, aligned, initialised, dereferenceable, readable
|
|
|
- // instance of UsedRing.
|
|
|
- unsafe { (*self.used.as_ptr()).flags & 0x0001 == 0 }
|
|
|
+ if self.event_idx {
|
|
|
+ // Safe because self.used points to a valid, aligned, initialised, dereferenceable, readable
|
|
|
+ // instance of UsedRing.
|
|
|
+ let avail_event = unsafe { (*self.used.as_ptr()).avail_event };
|
|
|
+ self.avail_idx >= avail_event.wrapping_add(1)
|
|
|
+ } else {
|
|
|
+ // Safe because self.used points to a valid, aligned, initialised, dereferenceable, readable
|
|
|
+ // instance of UsedRing.
|
|
|
+ unsafe { (*self.used.as_ptr()).flags & 0x0001 == 0 }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// Copies the descriptor at the given index from `desc_shadow` to `desc`, so it can be seen by
|
|
@@ -735,7 +756,8 @@ struct UsedRing<const SIZE: usize> {
|
|
|
flags: u16,
|
|
|
idx: u16,
|
|
|
ring: [UsedElem; SIZE],
|
|
|
- avail_event: u16, // unused
|
|
|
+ /// Only used if `VIRTIO_F_EVENT_IDX` is negotiated.
|
|
|
+ avail_event: u16,
|
|
|
}
|
|
|
|
|
|
#[repr(C)]
|
|
@@ -917,10 +939,16 @@ pub(crate) fn fake_read_write_queue<const QUEUE_SIZE: usize>(
|
|
|
mod tests {
|
|
|
use super::*;
|
|
|
use crate::{
|
|
|
+ device::common::Feature,
|
|
|
hal::fake::FakeHal,
|
|
|
- transport::mmio::{MmioTransport, VirtIOHeader, MODERN_VERSION},
|
|
|
+ transport::{
|
|
|
+ fake::{FakeTransport, QueueStatus, State},
|
|
|
+ mmio::{MmioTransport, VirtIOHeader, MODERN_VERSION},
|
|
|
+ DeviceStatus, DeviceType,
|
|
|
+ },
|
|
|
};
|
|
|
use core::ptr::NonNull;
|
|
|
+ use std::sync::{Arc, Mutex};
|
|
|
|
|
|
#[test]
|
|
|
fn invalid_queue_size() {
|
|
@@ -928,7 +956,7 @@ mod tests {
|
|
|
let mut transport = unsafe { MmioTransport::new(NonNull::from(&mut header)) }.unwrap();
|
|
|
// Size not a power of 2.
|
|
|
assert_eq!(
|
|
|
- VirtQueue::<FakeHal, 3>::new(&mut transport, 0, false).unwrap_err(),
|
|
|
+ VirtQueue::<FakeHal, 3>::new(&mut transport, 0, false, false).unwrap_err(),
|
|
|
Error::InvalidParam
|
|
|
);
|
|
|
}
|
|
@@ -938,7 +966,7 @@ mod tests {
|
|
|
let mut header = VirtIOHeader::make_fake_header(MODERN_VERSION, 1, 0, 0, 4);
|
|
|
let mut transport = unsafe { MmioTransport::new(NonNull::from(&mut header)) }.unwrap();
|
|
|
assert_eq!(
|
|
|
- VirtQueue::<FakeHal, 8>::new(&mut transport, 0, false).unwrap_err(),
|
|
|
+ VirtQueue::<FakeHal, 8>::new(&mut transport, 0, false, false).unwrap_err(),
|
|
|
Error::InvalidParam
|
|
|
);
|
|
|
}
|
|
@@ -947,9 +975,9 @@ mod tests {
|
|
|
fn queue_already_used() {
|
|
|
let mut header = VirtIOHeader::make_fake_header(MODERN_VERSION, 1, 0, 0, 4);
|
|
|
let mut transport = unsafe { MmioTransport::new(NonNull::from(&mut header)) }.unwrap();
|
|
|
- VirtQueue::<FakeHal, 4>::new(&mut transport, 0, false).unwrap();
|
|
|
+ VirtQueue::<FakeHal, 4>::new(&mut transport, 0, false, false).unwrap();
|
|
|
assert_eq!(
|
|
|
- VirtQueue::<FakeHal, 4>::new(&mut transport, 0, false).unwrap_err(),
|
|
|
+ VirtQueue::<FakeHal, 4>::new(&mut transport, 0, false, false).unwrap_err(),
|
|
|
Error::AlreadyUsed
|
|
|
);
|
|
|
}
|
|
@@ -958,7 +986,7 @@ mod tests {
|
|
|
fn add_empty() {
|
|
|
let mut header = VirtIOHeader::make_fake_header(MODERN_VERSION, 1, 0, 0, 4);
|
|
|
let mut transport = unsafe { MmioTransport::new(NonNull::from(&mut header)) }.unwrap();
|
|
|
- let mut queue = VirtQueue::<FakeHal, 4>::new(&mut transport, 0, false).unwrap();
|
|
|
+ let mut queue = VirtQueue::<FakeHal, 4>::new(&mut transport, 0, false, false).unwrap();
|
|
|
assert_eq!(
|
|
|
unsafe { queue.add(&[], &mut []) }.unwrap_err(),
|
|
|
Error::InvalidParam
|
|
@@ -969,7 +997,7 @@ mod tests {
|
|
|
fn add_too_many() {
|
|
|
let mut header = VirtIOHeader::make_fake_header(MODERN_VERSION, 1, 0, 0, 4);
|
|
|
let mut transport = unsafe { MmioTransport::new(NonNull::from(&mut header)) }.unwrap();
|
|
|
- let mut queue = VirtQueue::<FakeHal, 4>::new(&mut transport, 0, false).unwrap();
|
|
|
+ let mut queue = VirtQueue::<FakeHal, 4>::new(&mut transport, 0, false, false).unwrap();
|
|
|
assert_eq!(queue.available_desc(), 4);
|
|
|
assert_eq!(
|
|
|
unsafe { queue.add(&[&[], &[], &[]], &mut [&mut [], &mut []]) }.unwrap_err(),
|
|
@@ -981,7 +1009,7 @@ mod tests {
|
|
|
fn add_buffers() {
|
|
|
let mut header = VirtIOHeader::make_fake_header(MODERN_VERSION, 1, 0, 0, 4);
|
|
|
let mut transport = unsafe { MmioTransport::new(NonNull::from(&mut header)) }.unwrap();
|
|
|
- let mut queue = VirtQueue::<FakeHal, 4>::new(&mut transport, 0, false).unwrap();
|
|
|
+ let mut queue = VirtQueue::<FakeHal, 4>::new(&mut transport, 0, false, false).unwrap();
|
|
|
assert_eq!(queue.available_desc(), 4);
|
|
|
|
|
|
// Add a buffer chain consisting of two device-readable parts followed by two
|
|
@@ -1044,7 +1072,7 @@ mod tests {
|
|
|
|
|
|
let mut header = VirtIOHeader::make_fake_header(MODERN_VERSION, 1, 0, 0, 4);
|
|
|
let mut transport = unsafe { MmioTransport::new(NonNull::from(&mut header)) }.unwrap();
|
|
|
- let mut queue = VirtQueue::<FakeHal, 4>::new(&mut transport, 0, true).unwrap();
|
|
|
+ let mut queue = VirtQueue::<FakeHal, 4>::new(&mut transport, 0, true, false).unwrap();
|
|
|
assert_eq!(queue.available_desc(), 4);
|
|
|
|
|
|
// Add a buffer chain consisting of two device-readable parts followed by two
|
|
@@ -1089,4 +1117,80 @@ mod tests {
|
|
|
assert_eq!((*indirect_descriptors)[3].flags, DescFlags::WRITE);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /// Tests that the queue notifies the device about added buffers, if it hasn't suppressed
|
|
|
+ /// notifications.
|
|
|
+ #[test]
|
|
|
+ fn add_notify() {
|
|
|
+ let mut config_space = ();
|
|
|
+ let state = Arc::new(Mutex::new(State {
|
|
|
+ queues: vec![QueueStatus::default()],
|
|
|
+ ..Default::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::<FakeHal, 4>::new(&mut transport, 0, false, false).unwrap();
|
|
|
+
|
|
|
+ // Add a buffer chain with a single device-readable part.
|
|
|
+ unsafe { queue.add(&[&[42]], &mut []) }.unwrap();
|
|
|
+
|
|
|
+ // Check that the transport would be notified.
|
|
|
+ assert_eq!(queue.should_notify(), true);
|
|
|
+
|
|
|
+ // SAFETY: the various parts of the queue are properly aligned, dereferenceable and
|
|
|
+ // initialised, and nothing else is accessing them at the same time.
|
|
|
+ unsafe {
|
|
|
+ // Suppress notifications.
|
|
|
+ (*queue.used.as_ptr()).flags = 0x01;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check that the transport would not be notified.
|
|
|
+ assert_eq!(queue.should_notify(), false);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Tests that the queue notifies the device about added buffers, if it hasn't suppressed
|
|
|
+ /// 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 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::<FakeHal, 4>::new(&mut transport, 0, false, true).unwrap();
|
|
|
+
|
|
|
+ // Add a buffer chain with a single device-readable part.
|
|
|
+ assert_eq!(unsafe { queue.add(&[&[42]], &mut []) }.unwrap(), 0);
|
|
|
+
|
|
|
+ // Check that the transport would be notified.
|
|
|
+ assert_eq!(queue.should_notify(), true);
|
|
|
+
|
|
|
+ // SAFETY: the various parts of the queue are properly aligned, dereferenceable and
|
|
|
+ // initialised, and nothing else is accessing them at the same time.
|
|
|
+ unsafe {
|
|
|
+ // Suppress notifications.
|
|
|
+ (*queue.used.as_ptr()).avail_event = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check that the transport would not be notified.
|
|
|
+ assert_eq!(queue.should_notify(), false);
|
|
|
+
|
|
|
+ // Add another buffer chain.
|
|
|
+ assert_eq!(unsafe { queue.add(&[&[42]], &mut []) }.unwrap(), 1);
|
|
|
+
|
|
|
+ // Check that the transport should be notified again now.
|
|
|
+ assert_eq!(queue.should_notify(), true);
|
|
|
+ }
|
|
|
}
|