Переглянути джерело

Merge pull request #40 from rcore-os/block

Improve non-blocking API of VirtIOBlk and add documentation.
Andrew Walbran 2 роки тому
батько
коміт
ef282cf3de
2 змінених файлів з 94 додано та 31 видалено
  1. 93 30
      src/blk.rs
  2. 1 1
      src/lib.rs

+ 93 - 30
src/blk.rs

@@ -8,10 +8,30 @@ use zerocopy::{AsBytes, FromBytes};
 
 const QUEUE: u16 = 0;
 
-/// The virtio block device is a simple virtual block device (ie. disk).
+/// Driver for a VirtIO block device.
 ///
-/// Read and write requests (and other exotic requests) are placed in the queue,
-/// and serviced (probably out of order) by the device except where noted.
+/// This is a simple virtual block device, e.g. disk.
+///
+/// Read and write requests (and other exotic requests) are placed in the queue and serviced
+/// (probably out of order) by the device except where noted.
+///
+/// # Example
+///
+/// ```
+/// # use virtio_drivers::{Error, Hal, Transport};
+/// use virtio_drivers::{VirtIOBlk, SECTOR_SIZE};
+/// # fn example<HalImpl: Hal, T: Transport>(transport: T) -> Result<(), Error> {
+/// let mut disk = VirtIOBlk::<HalImpl, _>::new(transport)?;
+///
+/// println!("VirtIO block device: {} kB", disk.capacity() * SECTOR_SIZE as u64 / 2);
+///
+/// // Read sector 0 and then copy it to sector 1.
+/// let mut buf = [0; SECTOR_SIZE];
+/// disk.read_block(0, &mut buf)?;
+/// disk.write_block(1, &buf)?;
+/// # Ok(())
+/// # }
+/// ```
 pub struct VirtIOBlk<H: Hal, T: Transport> {
     transport: T,
     queue: VirtQueue<H>,
@@ -53,7 +73,7 @@ impl<H: Hal, T: Transport> VirtIOBlk<H, T> {
         })
     }
 
-    /// Gets the capacity of the block device, in 512 byte sectors.
+    /// Gets the capacity of the block device, in 512 byte ([`SECTOR_SIZE`]) sectors.
     pub fn capacity(&self) -> u64 {
         self.capacity
     }
@@ -63,12 +83,16 @@ impl<H: Hal, T: Transport> VirtIOBlk<H, T> {
         self.readonly
     }
 
-    /// Acknowledge interrupt.
+    /// Acknowledges a pending interrupt, if any.
+    ///
+    /// Returns true if there was an interrupt to acknowledge.
     pub fn ack_interrupt(&mut self) -> bool {
         self.transport.ack_interrupt()
     }
 
-    /// Read a block.
+    /// Reads a block into the given buffer.
+    ///
+    /// Blocks until the read completes or there is an error.
     pub fn read_block(&mut self, block_id: usize, buf: &mut [u8]) -> Result {
         assert_eq!(buf.len(), SECTOR_SIZE);
         let req = BlkReq {
@@ -88,42 +112,63 @@ impl<H: Hal, T: Transport> VirtIOBlk<H, T> {
         }
     }
 
-    /// Read a block in a non-blocking way which means that it returns immediately.
+    /// Submits a request to read a block, but returns immediately without waiting for the read to
+    /// complete.
     ///
     /// # Arguments
     ///
     /// * `block_id` - The identifier of the block to read.
-    /// * `buf` - The buffer in the memory which the block is read into.
+    /// * `req` - A buffer which the driver can use for the request to send to the device. The
+    ///   contents don't matter as `read_block_nb` will initialise it, but like the other buffers it
+    ///   needs to be valid (and not otherwise used) until the corresponding `pop_used` call.
+    /// * `buf` - The buffer in memory into which the block should be read.
     /// * `resp` - A mutable reference to a variable provided by the caller
-    ///   which contains the status of the requests. The caller can safely
-    ///   read the variable only after the request is ready.
+    ///   to contain the status of the request. The caller can safely
+    ///   read the variable only after the request is complete.
     ///
     /// # Usage
     ///
-    /// It will submit request to the virtio block device and return a token identifying
+    /// It will submit request to the VirtIO block device and return a token identifying
     /// the position of the first Descriptor in the chain. If there are not enough
     /// Descriptors to allocate, then it returns [Error::BufferTooSmall].
     ///
-    /// After the request is ready, `resp` will be updated and the caller can get the
-    /// status of the request(e.g. succeed or failed) through it. However, the caller
-    /// **must not** spin on `resp` to wait for it to change. A safe way is to read it
-    /// after the same token as this method returns is fetched through [VirtIOBlk::pop_used()],
-    /// which means that the request has been ready.
+    /// The caller can then call `pop_used` to check whether the device has finished handling the
+    /// request. Once it has, the caller can then read the response and dispose of the buffers.
+    ///
+    /// ```
+    /// # use virtio_drivers::{BlkReq, BlkResp, Error, Hal, RespStatus, Transport, VirtIOBlk};
+    /// # fn example<H: Hal, T: Transport>(blk: &mut VirtIOBlk<H, T>) -> Result<(), Error> {
+    /// let mut request = BlkReq::default();
+    /// let mut buffer = [0; 512];
+    /// let mut response = BlkResp::default();
+    /// let token = unsafe { blk.read_block_nb(42, &mut request, &mut buffer, &mut response) }?;
+    ///
+    /// // Wait for an interrupt to tell us that the request completed...
+    ///
+    /// assert_eq!(blk.pop_used()?, token);
+    /// if response.status() == RespStatus::OK {
+    ///   println!("Successfully read block.");
+    /// } else {
+    ///   println!("Error {:?} reading block.", response.status());
+    /// }
+    /// # Ok(())
+    /// # }
+    /// ```
     ///
     /// # Safety
     ///
-    /// `buf` is still borrowed by the underlying virtio block device even if this
-    /// method returns. Thus, it is the caller's responsibility to guarantee that
-    /// `buf` is not accessed before the request is completed in order to avoid
-    /// data races.
+    /// `req`, `buf` and `resp` are still borrowed by the underlying VirtIO block device even after
+    /// this method returns. Thus, it is the caller's responsibility to guarantee that they are not
+    /// accessed before the request is completed in order to avoid data races.
     pub unsafe fn read_block_nb(
         &mut self,
         block_id: usize,
+        req: &mut BlkReq,
         buf: &mut [u8],
         resp: &mut BlkResp,
     ) -> Result<u16> {
         assert_eq!(buf.len(), SECTOR_SIZE);
-        let req = BlkReq {
+        *req = BlkReq {
             type_: ReqType::In,
             reserved: 0,
             sector: block_id as u64,
@@ -135,7 +180,9 @@ impl<H: Hal, T: Transport> VirtIOBlk<H, T> {
         Ok(token)
     }
 
-    /// Write a block.
+    /// Writes the contents of the given buffer to a block.
+    ///
+    /// Blocks until the write is complete or there is an error.
     pub fn write_block(&mut self, block_id: usize, buf: &[u8]) -> Result {
         assert_eq!(buf.len(), SECTOR_SIZE);
         let req = BlkReq {
@@ -155,31 +202,36 @@ impl<H: Hal, T: Transport> VirtIOBlk<H, T> {
         }
     }
 
-    //// Write a block in a non-blocking way which means that it returns immediately.
+    /// Submits a request to write a block, but returns immediately without waiting for the write to
+    /// complete.
     ///
     /// # Arguments
     ///
     /// * `block_id` - The identifier of the block to write.
-    /// * `buf` - The buffer in the memory containing the data to write to the block.
+    /// * `req` - A buffer which the driver can use for the request to send to the device. The
+    ///   contents don't matter as `read_block_nb` will initialise it, but like the other buffers it
+    ///   needs to be valid (and not otherwise used) until the corresponding `pop_used` call.
+    /// * `buf` - The buffer in memory containing the data to write to the block.
     /// * `resp` - A mutable reference to a variable provided by the caller
-    ///   which contains the status of the requests. The caller can safely
-    ///   read the variable only after the request is ready.
+    ///   to contain the status of the request. The caller can safely
+    ///   read the variable only after the request is complete.
     ///
     /// # Usage
     ///
-    /// See also [VirtIOBlk::read_block_nb()].
+    /// See [VirtIOBlk::read_block_nb].
     ///
     /// # Safety
     ///
-    /// See also [VirtIOBlk::read_block_nb()].
+    /// See  [VirtIOBlk::read_block_nb].
     pub unsafe fn write_block_nb(
         &mut self,
         block_id: usize,
+        req: &mut BlkReq,
         buf: &[u8],
         resp: &mut BlkResp,
     ) -> Result<u16> {
         assert_eq!(buf.len(), SECTOR_SIZE);
-        let req = BlkReq {
+        *req = BlkReq {
             type_: ReqType::Out,
             reserved: 0,
             sector: block_id as u64,
@@ -231,14 +283,25 @@ struct BlkConfig {
     // ... ignored
 }
 
+/// A VirtIO block device request.
 #[repr(C)]
 #[derive(AsBytes, Debug)]
-struct BlkReq {
+pub struct BlkReq {
     type_: ReqType,
     reserved: u32,
     sector: u64,
 }
 
+impl Default for BlkReq {
+    fn default() -> Self {
+        Self {
+            type_: ReqType::In,
+            reserved: 0,
+            sector: 0,
+        }
+    }
+}
+
 /// Response of a VirtIOBlk request.
 #[repr(C)]
 #[derive(AsBytes, Debug, FromBytes)]

+ 1 - 1
src/lib.rs

@@ -19,7 +19,7 @@ mod queue;
 mod transport;
 mod volatile;
 
-pub use self::blk::{BlkResp, RespStatus, VirtIOBlk};
+pub use self::blk::{BlkReq, BlkResp, RespStatus, VirtIOBlk, SECTOR_SIZE};
 pub use self::console::VirtIOConsole;
 pub use self::gpu::VirtIOGpu;
 pub use self::hal::{Hal, PhysAddr, VirtAddr};