|
@@ -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)]
|