Parcourir la source

Add support for VIRTIO_F_EVENT_IDX buffer notification suppression.

Andrew Walbran il y a 10 mois
Parent
commit
7385d61153
8 fichiers modifiés avec 129 ajouts et 39 suppressions
  1. 1 1
      README.md
  2. 3 1
      src/device/blk.rs
  3. 16 4
      src/device/console.rs
  4. 16 4
      src/device/gpu.rs
  5. 17 5
      src/device/input.rs
  6. 18 4
      src/device/net.rs
  7. 22 6
      src/device/socket/vsock.rs
  8. 36 14
      src/queue.rs

+ 1 - 1
README.md

@@ -33,7 +33,7 @@ VirtIO guest drivers in Rust. For **no_std** environment.
 | Feature flag                 | Supported |                                         |
 | ---------------------------- | --------- | --------------------------------------- |
 | `VIRTIO_F_INDIRECT_DESC`     | ✅        | Indirect descriptors                    |
-| `VIRTIO_F_EVENT_IDX`         |         | `avail_event` and `used_event` fields   |
+| `VIRTIO_F_EVENT_IDX`         |         | `avail_event` and `used_event` fields   |
 | `VIRTIO_F_VERSION_1`         | TODO      | VirtIO version 1 compliance             |
 | `VIRTIO_F_ACCESS_PLATFORM`   | ❌        | Limited device access to memory         |
 | `VIRTIO_F_RING_PACKED`       | ❌        | Packed virtqueue layout                 |

+ 3 - 1
src/device/blk.rs

@@ -13,7 +13,8 @@ const QUEUE: u16 = 0;
 const QUEUE_SIZE: u16 = 16;
 const SUPPORTED_FEATURES: BlkFeature = BlkFeature::RO
     .union(BlkFeature::FLUSH)
-    .union(BlkFeature::RING_INDIRECT_DESC);
+    .union(BlkFeature::RING_INDIRECT_DESC)
+    .union(BlkFeature::RING_EVENT_IDX);
 
 /// Driver for a VirtIO block device.
 ///
@@ -74,6 +75,7 @@ impl<H: Hal, T: Transport> VirtIOBlk<H, T> {
             &mut transport,
             QUEUE,
             negotiated_features.contains(BlkFeature::RING_INDIRECT_DESC),
+            negotiated_features.contains(BlkFeature::RING_EVENT_IDX),
         )?;
         transport.finish_init();
 

+ 16 - 4
src/device/console.rs

@@ -13,6 +13,7 @@ use log::info;
 const QUEUE_RECEIVEQ_PORT_0: u16 = 0;
 const QUEUE_TRANSMITQ_PORT_0: u16 = 1;
 const QUEUE_SIZE: usize = 2;
+const SUPPORTED_FEATURES: Features = Features::RING_EVENT_IDX;
 
 /// Driver for a VirtIO console device.
 ///
@@ -65,15 +66,26 @@ pub struct ConsoleInfo {
 impl<H: Hal, T: Transport> VirtIOConsole<H, T> {
     /// Creates a new VirtIO console driver.
     pub fn new(mut transport: T) -> Result<Self> {
+        let mut negotiated_features = Features::empty();
         transport.begin_init(|features| {
             let features = Features::from_bits_truncate(features);
             info!("Device features {:?}", features);
-            let supported_features = Features::empty();
-            (features & supported_features).bits()
+            negotiated_features = features & SUPPORTED_FEATURES;
+            negotiated_features.bits()
         });
         let config_space = transport.config_space::<Config>()?;
-        let receiveq = VirtQueue::new(&mut transport, QUEUE_RECEIVEQ_PORT_0, false)?;
-        let transmitq = VirtQueue::new(&mut transport, QUEUE_TRANSMITQ_PORT_0, false)?;
+        let receiveq = VirtQueue::new(
+            &mut transport,
+            QUEUE_RECEIVEQ_PORT_0,
+            false,
+            negotiated_features.contains(Features::RING_EVENT_IDX),
+        )?;
+        let transmitq = VirtQueue::new(
+            &mut transport,
+            QUEUE_TRANSMITQ_PORT_0,
+            false,
+            negotiated_features.contains(Features::RING_EVENT_IDX),
+        )?;
 
         // Safe because no alignment or initialisation is required for [u8], the DMA buffer is
         // dereferenceable, and the lifetime of the reference matches the lifetime of the DMA buffer

+ 16 - 4
src/device/gpu.rs

@@ -11,6 +11,7 @@ use log::info;
 use zerocopy::{AsBytes, FromBytes};
 
 const QUEUE_SIZE: u16 = 2;
+const SUPPORTED_FEATURES: Features = Features::RING_EVENT_IDX;
 
 /// A virtio based graphics adapter.
 ///
@@ -39,11 +40,12 @@ pub struct VirtIOGpu<H: Hal, T: Transport> {
 impl<H: Hal, T: Transport> VirtIOGpu<H, T> {
     /// Create a new VirtIO-Gpu driver.
     pub fn new(mut transport: T) -> Result<Self> {
+        let mut negotiated_features = Features::empty();
         transport.begin_init(|features| {
             let features = Features::from_bits_truncate(features);
             info!("Device features {:?}", features);
-            let supported_features = Features::empty();
-            (features & supported_features).bits()
+            negotiated_features = features & SUPPORTED_FEATURES;
+            negotiated_features.bits()
         });
 
         // read configuration space
@@ -57,8 +59,18 @@ impl<H: Hal, T: Transport> VirtIOGpu<H, T> {
             );
         }
 
-        let control_queue = VirtQueue::new(&mut transport, QUEUE_TRANSMIT, false)?;
-        let cursor_queue = VirtQueue::new(&mut transport, QUEUE_CURSOR, false)?;
+        let control_queue = VirtQueue::new(
+            &mut transport,
+            QUEUE_TRANSMIT,
+            false,
+            negotiated_features.contains(Features::RING_EVENT_IDX),
+        )?;
+        let cursor_queue = VirtQueue::new(
+            &mut transport,
+            QUEUE_CURSOR,
+            false,
+            negotiated_features.contains(Features::RING_EVENT_IDX),
+        )?;
 
         let queue_buf_send = FromBytes::new_box_slice_zeroed(PAGE_SIZE);
         let queue_buf_recv = FromBytes::new_box_slice_zeroed(PAGE_SIZE);

+ 17 - 5
src/device/input.rs

@@ -28,18 +28,29 @@ impl<H: Hal, T: Transport> VirtIOInput<H, T> {
     /// Create a new VirtIO-Input driver.
     pub fn new(mut transport: T) -> Result<Self> {
         let mut event_buf = Box::new([InputEvent::default(); QUEUE_SIZE]);
+
+        let mut negotiated_features = Feature::empty();
         transport.begin_init(|features| {
             let features = Feature::from_bits_truncate(features);
             info!("Device features: {:?}", features);
-            // negotiate these flags only
-            let supported_features = Feature::empty();
-            (features & supported_features).bits()
+            negotiated_features = features & SUPPORTED_FEATURES;
+            negotiated_features.bits()
         });
 
         let config = transport.config_space::<Config>()?;
 
-        let mut event_queue = VirtQueue::new(&mut transport, QUEUE_EVENT, false)?;
-        let status_queue = VirtQueue::new(&mut transport, QUEUE_STATUS, false)?;
+        let mut event_queue = VirtQueue::new(
+            &mut transport,
+            QUEUE_EVENT,
+            false,
+            negotiated_features.contains(Feature::RING_EVENT_IDX),
+        )?;
+        let status_queue = VirtQueue::new(
+            &mut transport,
+            QUEUE_STATUS,
+            false,
+            negotiated_features.contains(Feature::RING_EVENT_IDX),
+        )?;
         for (i, event) in event_buf.as_mut().iter_mut().enumerate() {
             // Safe because the buffer lasts as long as the queue.
             let token = unsafe { event_queue.add(&[], &mut [event.as_bytes_mut()])? };
@@ -193,6 +204,7 @@ pub struct InputEvent {
 
 const QUEUE_EVENT: u16 = 0;
 const QUEUE_STATUS: u16 = 1;
+const SUPPORTED_FEATURES: Feature = Feature::RING_EVENT_IDX;
 
 // a parameter that can change
 const QUEUE_SIZE: usize = 32;

+ 18 - 4
src/device/net.rs

@@ -112,11 +112,12 @@ pub struct VirtIONet<H: Hal, T: Transport, const QUEUE_SIZE: usize> {
 impl<H: Hal, T: Transport, const QUEUE_SIZE: usize> VirtIONet<H, T, QUEUE_SIZE> {
     /// Create a new VirtIO-Net driver.
     pub fn new(mut transport: T, buf_len: usize) -> Result<Self> {
+        let mut negotiated_features = Features::empty();
         transport.begin_init(|features| {
             let features = Features::from_bits_truncate(features);
             info!("Device features {:?}", features);
-            let supported_features = Features::MAC | Features::STATUS;
-            (features & supported_features).bits()
+            negotiated_features = features & SUPPORTED_FEATURES;
+            negotiated_features.bits()
         });
         // read configuration space
         let config = transport.config_space::<Config>()?;
@@ -139,8 +140,18 @@ impl<H: Hal, T: Transport, const QUEUE_SIZE: usize> VirtIONet<H, T, QUEUE_SIZE>
             return Err(Error::InvalidParam);
         }
 
-        let send_queue = VirtQueue::new(&mut transport, QUEUE_TRANSMIT, false)?;
-        let mut recv_queue = VirtQueue::new(&mut transport, QUEUE_RECEIVE, false)?;
+        let send_queue = VirtQueue::new(
+            &mut transport,
+            QUEUE_TRANSMIT,
+            false,
+            negotiated_features.contains(Features::RING_EVENT_IDX),
+        )?;
+        let mut recv_queue = VirtQueue::new(
+            &mut transport,
+            QUEUE_RECEIVE,
+            false,
+            negotiated_features.contains(Features::RING_EVENT_IDX),
+        )?;
 
         const NONE_BUF: Option<RxBuffer> = None;
         let mut rx_buffers = [NONE_BUF; QUEUE_SIZE];
@@ -403,3 +414,6 @@ impl GsoType {
 
 const QUEUE_RECEIVE: u16 = 0;
 const QUEUE_TRANSMIT: u16 = 1;
+const SUPPORTED_FEATURES: Features = Features::MAC
+    .union(Features::STATUS)
+    .union(Features::RING_EVENT_IDX);

+ 22 - 6
src/device/socket/vsock.rs

@@ -19,6 +19,7 @@ pub(crate) const TX_QUEUE_IDX: u16 = 1;
 const EVENT_QUEUE_IDX: u16 = 2;
 
 pub(crate) const QUEUE_SIZE: usize = 8;
+const SUPPORTED_FEATURES: Feature = Feature::RING_EVENT_IDX;
 
 /// The size in bytes of each buffer used in the RX virtqueue. This must be bigger than size_of::<VirtioVsockHdr>().
 const RX_BUFFER_SIZE: usize = 512;
@@ -241,12 +242,12 @@ impl<H: Hal, T: Transport> Drop for VirtIOSocket<H, T> {
 impl<H: Hal, T: Transport> VirtIOSocket<H, T> {
     /// Create a new VirtIO Vsock driver.
     pub fn new(mut transport: T) -> Result<Self> {
+        let mut negotiated_features = Feature::empty();
         transport.begin_init(|features| {
             let features = Feature::from_bits_truncate(features);
             debug!("Device features: {:?}", features);
-            // negotiate these flags only
-            let supported_features = Feature::empty();
-            (features & supported_features).bits()
+            negotiated_features = features & SUPPORTED_FEATURES;
+            negotiated_features.bits()
         });
 
         let config = transport.config_space::<VirtioVsockConfig>()?;
@@ -257,9 +258,24 @@ impl<H: Hal, T: Transport> VirtIOSocket<H, T> {
         };
         debug!("guest cid: {guest_cid:?}");
 
-        let mut rx = VirtQueue::new(&mut transport, RX_QUEUE_IDX, false)?;
-        let tx = VirtQueue::new(&mut transport, TX_QUEUE_IDX, false)?;
-        let event = VirtQueue::new(&mut transport, EVENT_QUEUE_IDX, false)?;
+        let mut rx = VirtQueue::new(
+            &mut transport,
+            RX_QUEUE_IDX,
+            false,
+            negotiated_features.contains(Feature::RING_EVENT_IDX),
+        )?;
+        let tx = VirtQueue::new(
+            &mut transport,
+            TX_QUEUE_IDX,
+            false,
+            negotiated_features.contains(Feature::RING_EVENT_IDX),
+        )?;
+        let event = VirtQueue::new(
+            &mut transport,
+            EVENT_QUEUE_IDX,
+            false,
+            negotiated_features.contains(Feature::RING_EVENT_IDX),
+        )?;
 
         // Allocate and add buffers for the RX queue.
         let mut rx_queue_buffers = [null_mut(); QUEUE_SIZE];

+ 36 - 14
src/queue.rs

@@ -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)]
@@ -928,7 +950,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 +960,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 +969,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 +980,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 +991,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 +1003,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 +1066,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