Эх сурвалжийг харах

Add support for flushing block device.

Andrew Walbran 1 жил өмнө
parent
commit
a6d6aebed9
1 өөрчлөгдсөн 105 нэмэгдсэн , 9 устгасан
  1. 105 9
      src/device/blk.rs

+ 105 - 9
src/device/blk.rs

@@ -11,6 +11,7 @@ use zerocopy::{AsBytes, FromBytes};
 
 const QUEUE: u16 = 0;
 const QUEUE_SIZE: u16 = 16;
+const SUPPORTED_FEATURES: BlkFeature = BlkFeature::RO.union(BlkFeature::FLUSH);
 
 /// Driver for a VirtIO block device.
 ///
@@ -42,24 +43,23 @@ pub struct VirtIOBlk<H: Hal, T: Transport> {
     transport: T,
     queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
     capacity: u64,
-    readonly: bool,
+    negotiated_features: BlkFeature,
 }
 
 impl<H: Hal, T: Transport> VirtIOBlk<H, T> {
     /// Create a new VirtIO-Blk driver.
     pub fn new(mut transport: T) -> Result<Self> {
-        let mut readonly = false;
+        let mut negotiated_features = BlkFeature::empty();
 
         transport.begin_init(|features| {
             let features = BlkFeature::from_bits_truncate(features);
             info!("device features: {:?}", features);
-            readonly = features.contains(BlkFeature::RO);
-            // negotiate these flags only
-            let supported_features = BlkFeature::empty();
-            (features & supported_features).bits()
+            negotiated_features = features & SUPPORTED_FEATURES;
+            // Negotiate these features only.
+            negotiated_features.bits()
         });
 
-        // read configuration space
+        // Read configuration space.
         let config = transport.config_space::<BlkConfig>()?;
         info!("config: {:?}", config);
         // Safe because config is a valid pointer to the device configuration space.
@@ -75,7 +75,7 @@ impl<H: Hal, T: Transport> VirtIOBlk<H, T> {
             transport,
             queue,
             capacity,
-            readonly,
+            negotiated_features,
         })
     }
 
@@ -86,7 +86,7 @@ impl<H: Hal, T: Transport> VirtIOBlk<H, T> {
 
     /// Returns true if the block device is read-only, or false if it allows writes.
     pub fn readonly(&self) -> bool {
-        self.readonly
+        self.negotiated_features.contains(BlkFeature::RO)
     }
 
     /// Acknowledges a pending interrupt, if any.
@@ -96,6 +96,29 @@ impl<H: Hal, T: Transport> VirtIOBlk<H, T> {
         self.transport.ack_interrupt()
     }
 
+    /// Requests the device to flush any pending writes to storage.
+    ///
+    /// This will be ignored if the device doesn't support the `VIRTIO_BLK_F_FLUSH` feature.
+    pub fn flush(&mut self) -> Result {
+        if self.negotiated_features.contains(BlkFeature::FLUSH) {
+            let req = BlkReq {
+                type_: ReqType::Flush,
+                reserved: 0,
+                sector: 0,
+            };
+            let mut resp = BlkResp::default();
+            self.queue.add_notify_wait_pop(
+                &[req.as_bytes()],
+                &mut [resp.as_bytes_mut()],
+                &mut self.transport,
+            )?;
+
+            resp.status.into()
+        } else {
+            Ok(())
+        }
+    }
+
     /// Reads a block into the given buffer.
     ///
     /// Blocks until the read completes or there is an error.
@@ -661,6 +684,79 @@ mod tests {
         buffer[0..9].copy_from_slice(b"Test data");
         blk.write_block(42, &mut buffer).unwrap();
 
+        // Request to flush should be ignored as the device doesn't support it.
+        blk.flush().unwrap();
+
+        handle.join().unwrap();
+    }
+
+    #[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 state = Arc::new(Mutex::new(State {
+            status: DeviceStatus::empty(),
+            driver_features: 0,
+            guest_page_size: 0,
+            interrupt_pending: false,
+            queues: vec![QueueStatus::default()],
+        }));
+        let transport = FakeTransport {
+            device_type: DeviceType::Console,
+            max_queue_size: QUEUE_SIZE.into(),
+            device_features: BlkFeature::FLUSH.bits(),
+            config_space: NonNull::from(&mut config_space),
+            state: state.clone(),
+        };
+        let mut blk = VirtIOBlk::<FakeHal, FakeTransport<BlkConfig>>::new(transport).unwrap();
+
+        // Start a thread to simulate the device waiting for a flush request.
+        let handle = thread::spawn(move || {
+            println!("Device waiting for a request.");
+            State::wait_until_queue_notified(&state, QUEUE);
+            println!("Transmit queue was notified.");
+
+            state
+                .lock()
+                .unwrap()
+                .read_write_queue::<{ QUEUE_SIZE as usize }>(QUEUE, |request| {
+                    assert_eq!(
+                        request,
+                        BlkReq {
+                            type_: ReqType::Flush,
+                            reserved: 0,
+                            sector: 0,
+                        }
+                        .as_bytes()
+                    );
+
+                    let mut response = Vec::new();
+                    response.extend_from_slice(
+                        BlkResp {
+                            status: RespStatus::OK,
+                        }
+                        .as_bytes(),
+                    );
+
+                    response
+                });
+        });
+
+        // Request to flush.
+        blk.flush().unwrap();
+
         handle.join().unwrap();
     }
 }