Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for flushing block devices and getting their IDs #100

Merged
merged 4 commits into from
Jul 13, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
276 changes: 243 additions & 33 deletions src/device/blk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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.
Expand All @@ -75,7 +75,7 @@ impl<H: Hal, T: Transport> VirtIOBlk<H, T> {
transport,
queue,
capacity,
readonly,
negotiated_features,
})
}

Expand All @@ -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.
Expand All @@ -96,25 +96,85 @@ impl<H: Hal, T: Transport> VirtIOBlk<H, T> {
self.transport.ack_interrupt()
}

/// 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 {
type_: ReqType::In,
reserved: 0,
sector: block_id as u64,
};
/// Sends the given request to the device and waits for a response, with no extra data.
fn request(&mut self, request: BlkReq) -> Result {
let mut resp = BlkResp::default();
self.queue.add_notify_wait_pop(
&[req.as_bytes()],
&mut [buf, resp.as_bytes_mut()],
&[request.as_bytes()],
&mut [resp.as_bytes_mut()],
&mut self.transport,
)?;
resp.status.into()
}

/// Sends the given request to the device and waits for a response, including the given data.
fn request_read(&mut self, request: BlkReq, data: &mut [u8]) -> Result {
let mut resp = BlkResp::default();
self.queue.add_notify_wait_pop(
&[request.as_bytes()],
&mut [data, resp.as_bytes_mut()],
&mut self.transport,
)?;
resp.status.into()
}

/// Sends the given request and data to the device and waits for a response.
fn request_write(&mut self, request: BlkReq, data: &[u8]) -> Result {
let mut resp = BlkResp::default();
self.queue.add_notify_wait_pop(
&[request.as_bytes(), data],
&mut [resp.as_bytes_mut()],
&mut self.transport,
)?;
resp.status.into()
}

/// 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) {
self.request(BlkReq {
type_: ReqType::Flush,
..Default::default()
})
} else {
Ok(())
}
}

/// Gets the device ID.
///
/// The ID is written as ASCII into the given buffer, which must be 20 bytes long, and the used
/// length returned.
pub fn device_id(&mut self, id: &mut [u8; 20]) -> Result<usize> {
self.request_read(
BlkReq {
type_: ReqType::GetId,
..Default::default()
},
id,
)?;

let length = id.iter().position(|&x| x == 0).unwrap_or(20);
Ok(length)
}

/// 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);
self.request_read(
BlkReq {
type_: ReqType::In,
reserved: 0,
sector: block_id as u64,
},
buf,
)
}

/// Submits a request to read a block, but returns immediately without waiting for the read to
/// complete.
///
Expand Down Expand Up @@ -217,18 +277,14 @@ impl<H: Hal, T: Transport> VirtIOBlk<H, T> {
/// 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 {
type_: ReqType::Out,
reserved: 0,
sector: block_id as u64,
};
let mut resp = BlkResp::default();
self.queue.add_notify_wait_pop(
&[req.as_bytes(), buf],
&mut [resp.as_bytes_mut()],
&mut self.transport,
)?;
resp.status.into()
self.request_write(
BlkReq {
type_: ReqType::Out,
sector: block_id as u64,
..Default::default()
},
buf,
)
}

/// Submits a request to write a block, but returns immediately without waiting for the write to
Expand Down Expand Up @@ -372,8 +428,11 @@ enum ReqType {
In = 0,
Out = 1,
Flush = 4,
GetId = 8,
GetLifetime = 10,
Discard = 11,
WriteZeroes = 13,
SecureErase = 14,
}

/// Status of a VirtIOBlk request.
Expand Down Expand Up @@ -439,6 +498,8 @@ bitflags! {
const TOPOLOGY = 1 << 10;
/// Device can toggle its cache between writeback and writethrough modes.
const CONFIG_WCE = 1 << 11;
/// Device supports multiqueue.
const MQ = 1 << 12;
/// Device can support discard command, maximum discard sectors size in
/// `max_discard_sectors` and maximum discard segment number in
/// `max_discard_seg`.
Expand All @@ -447,6 +508,10 @@ bitflags! {
/// size in `max_write_zeroes_sectors` and maximum write zeroes segment
/// number in `max_write_zeroes_seg`.
const WRITE_ZEROES = 1 << 14;
/// Device supports providing storage lifetime information.
const LIFETIME = 1 << 15;
/// Device can support the secure erase command.
const SECURE_ERASE = 1 << 16;

// device independent
const NOTIFY_ON_EMPTY = 1 << 24; // legacy
Expand Down Expand Up @@ -661,6 +726,151 @@ 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();
}

#[test]
fn device_id() {
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: 0,
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::GetId,
reserved: 0,
sector: 0,
}
.as_bytes()
);

let mut response = Vec::new();
response.extend_from_slice(b"device_id\0\0\0\0\0\0\0\0\0\0\0");
response.extend_from_slice(
BlkResp {
status: RespStatus::OK,
}
.as_bytes(),
);

response
});
});

let mut id = [0; 20];
let length = blk.device_id(&mut id).unwrap();
assert_eq!(&id[0..length], b"device_id");

handle.join().unwrap();
}
}