blob: 9022e57012f5f377180bb1391bf36e722d53e51e [file] [log] [blame]
use super::*;
use crate::queue::VirtQueue;
use crate::transport::Transport;
use crate::volatile::{volread, Volatile};
use bitflags::*;
use log::*;
const QUEUE: u16 = 0;
/// The virtio block device is a simple virtual block device (ie. 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.
pub struct VirtIOBlk<H: Hal, T: Transport> {
transport: T,
queue: VirtQueue<H>,
capacity: usize,
}
impl<H: Hal, T: Transport> VirtIOBlk<H, T> {
/// Create a new VirtIO-Blk driver.
pub fn new(mut transport: T) -> Result<Self> {
transport.begin_init(|features| {
let features = BlkFeature::from_bits_truncate(features);
info!("device features: {:?}", features);
// negotiate these flags only
let supported_features = BlkFeature::empty();
(features & supported_features).bits()
});
// read configuration space
let config = transport.config_space::<BlkConfig>()?;
info!("config: {:?}", config);
// Safe because config is a valid pointer to the device configuration space.
let capacity = unsafe {
volread!(config, capacity_low) as u64 | (volread!(config, capacity_high) as u64) << 32
};
info!("found a block device of size {}KB", capacity / 2);
let queue = VirtQueue::new(&mut transport, QUEUE, 16)?;
transport.finish_init();
Ok(VirtIOBlk {
transport,
queue,
capacity: capacity as usize,
})
}
/// Acknowledge interrupt.
pub fn ack_interrupt(&mut self) -> bool {
self.transport.ack_interrupt()
}
/// Read a block.
pub fn read_block(&mut self, block_id: usize, buf: &mut [u8]) -> Result {
assert_eq!(buf.len(), BLK_SIZE);
let req = BlkReq {
type_: ReqType::In,
reserved: 0,
sector: block_id as u64,
};
let mut resp = BlkResp::default();
self.queue.add_notify_wait_pop(
&[req.as_buf()],
&[buf, resp.as_buf_mut()],
&mut self.transport,
)?;
match resp.status {
RespStatus::Ok => Ok(()),
_ => Err(Error::IoError),
}
}
/// Read a block in a non-blocking way which means that it returns immediately.
///
/// # Arguments
///
/// * `block_id` - The identifier of the block to read.
/// * `buf` - The buffer in the memory which the block is read into.
/// * `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.
///
/// # Usage
///
/// 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.
///
/// # 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.
pub unsafe fn read_block_nb(
&mut self,
block_id: usize,
buf: &mut [u8],
resp: &mut BlkResp,
) -> Result<u16> {
assert_eq!(buf.len(), BLK_SIZE);
let req = BlkReq {
type_: ReqType::In,
reserved: 0,
sector: block_id as u64,
};
let token = self.queue.add(&[req.as_buf()], &[buf, resp.as_buf_mut()])?;
self.transport.notify(QUEUE);
Ok(token)
}
/// Write a block.
pub fn write_block(&mut self, block_id: usize, buf: &[u8]) -> Result {
assert_eq!(buf.len(), BLK_SIZE);
let req = BlkReq {
type_: ReqType::Out,
reserved: 0,
sector: block_id as u64,
};
let mut resp = BlkResp::default();
self.queue.add_notify_wait_pop(
&[req.as_buf(), buf],
&[resp.as_buf_mut()],
&mut self.transport,
)?;
match resp.status {
RespStatus::Ok => Ok(()),
_ => Err(Error::IoError),
}
}
//// Write a block in a non-blocking way which means that it returns immediately.
///
/// # Arguments
///
/// * `block_id` - The identifier of the block to write.
/// * `buf` - The buffer in the 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.
///
/// # Usage
///
/// See also [VirtIOBlk::read_block_nb()].
///
/// # Safety
///
/// See also [VirtIOBlk::read_block_nb()].
pub unsafe fn write_block_nb(
&mut self,
block_id: usize,
buf: &[u8],
resp: &mut BlkResp,
) -> Result<u16> {
assert_eq!(buf.len(), BLK_SIZE);
let req = BlkReq {
type_: ReqType::Out,
reserved: 0,
sector: block_id as u64,
};
let token = self.queue.add(&[req.as_buf(), buf], &[resp.as_buf_mut()])?;
self.transport.notify(QUEUE);
Ok(token)
}
/// During an interrupt, it fetches a token of a completed request from the used
/// ring and return it. If all completed requests have already been fetched, return
/// Err(Error::NotReady).
pub fn pop_used(&mut self) -> Result<u16> {
self.queue.pop_used().map(|p| p.0)
}
/// Return size of its VirtQueue.
/// It can be used to tell the caller how many channels he should monitor on.
pub fn virt_queue_size(&self) -> u16 {
self.queue.size()
}
}
impl<H: Hal, T: Transport> Drop for VirtIOBlk<H, T> {
fn drop(&mut self) {
// Clear any pointers pointing to DMA regions, so the device doesn't try to access them
// after they have been freed.
self.transport.queue_unset(QUEUE);
}
}
#[repr(C)]
struct BlkConfig {
/// Number of 512 Bytes sectors
capacity_low: Volatile<u32>,
capacity_high: Volatile<u32>,
size_max: Volatile<u32>,
seg_max: Volatile<u32>,
cylinders: Volatile<u16>,
heads: Volatile<u8>,
sectors: Volatile<u8>,
blk_size: Volatile<u32>,
physical_block_exp: Volatile<u8>,
alignment_offset: Volatile<u8>,
min_io_size: Volatile<u16>,
opt_io_size: Volatile<u32>,
// ... ignored
}
#[repr(C)]
#[derive(Debug)]
struct BlkReq {
type_: ReqType,
reserved: u32,
sector: u64,
}
/// Response of a VirtIOBlk request.
#[repr(C)]
#[derive(Debug)]
pub struct BlkResp {
status: RespStatus,
}
impl BlkResp {
/// Return the status of a VirtIOBlk request.
pub fn status(&self) -> RespStatus {
self.status
}
}
#[repr(u32)]
#[derive(Debug)]
enum ReqType {
In = 0,
Out = 1,
Flush = 4,
Discard = 11,
WriteZeroes = 13,
}
/// Status of a VirtIOBlk request.
#[repr(u8)]
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum RespStatus {
/// Ok.
Ok = 0,
/// IoErr.
IoErr = 1,
/// Unsupported yet.
Unsupported = 2,
/// Not ready.
_NotReady = 3,
}
impl Default for BlkResp {
fn default() -> Self {
BlkResp {
status: RespStatus::_NotReady,
}
}
}
const BLK_SIZE: usize = 512;
bitflags! {
struct BlkFeature: u64 {
/// Device supports request barriers. (legacy)
const BARRIER = 1 << 0;
/// Maximum size of any single segment is in `size_max`.
const SIZE_MAX = 1 << 1;
/// Maximum number of segments in a request is in `seg_max`.
const SEG_MAX = 1 << 2;
/// Disk-style geometry specified in geometry.
const GEOMETRY = 1 << 4;
/// Device is read-only.
const RO = 1 << 5;
/// Block size of disk is in `blk_size`.
const BLK_SIZE = 1 << 6;
/// Device supports scsi packet commands. (legacy)
const SCSI = 1 << 7;
/// Cache flush command support.
const FLUSH = 1 << 9;
/// Device exports information on optimal I/O alignment.
const TOPOLOGY = 1 << 10;
/// Device can toggle its cache between writeback and writethrough modes.
const CONFIG_WCE = 1 << 11;
/// Device can support discard command, maximum discard sectors size in
/// `max_discard_sectors` and maximum discard segment number in
/// `max_discard_seg`.
const DISCARD = 1 << 13;
/// Device can support write zeroes command, maximum write zeroes sectors
/// size in `max_write_zeroes_sectors` and maximum write zeroes segment
/// number in `max_write_zeroes_seg`.
const WRITE_ZEROES = 1 << 14;
// device independent
const NOTIFY_ON_EMPTY = 1 << 24; // legacy
const ANY_LAYOUT = 1 << 27; // legacy
const RING_INDIRECT_DESC = 1 << 28;
const RING_EVENT_IDX = 1 << 29;
const UNUSED = 1 << 30; // legacy
const VERSION_1 = 1 << 32; // detect legacy
// the following since virtio v1.1
const ACCESS_PLATFORM = 1 << 33;
const RING_PACKED = 1 << 34;
const IN_ORDER = 1 << 35;
const ORDER_PLATFORM = 1 << 36;
const SR_IOV = 1 << 37;
const NOTIFICATION_DATA = 1 << 38;
}
}
unsafe impl AsBuf for BlkReq {}
unsafe impl AsBuf for BlkResp {}