Skip to content

Commit 62f3e4f

Browse files
authored
Add fake transport and a test for console driver. (#17)
* Assert that token assumption holds. * Add fake transport and a test for console driver. * Add test helper to queue to avoid exposing types and fields.
1 parent e6d9ff1 commit 62f3e4f

File tree

6 files changed

+246
-3
lines changed

6 files changed

+246
-3
lines changed

src/console.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,63 @@ bitflags! {
143143
const NOTIFICATION_DATA = 1 << 38;
144144
}
145145
}
146+
147+
#[cfg(test)]
148+
mod tests {
149+
use super::*;
150+
use crate::{
151+
hal::fake::FakeHal,
152+
transport::fake::{FakeTransport, QueueStatus, State},
153+
};
154+
use alloc::{sync::Arc, vec};
155+
use core::ptr::NonNull;
156+
use std::sync::Mutex;
157+
158+
#[test]
159+
fn receive() {
160+
let mut config_space = Config {
161+
cols: ReadOnly::new(0),
162+
rows: ReadOnly::new(0),
163+
max_nr_ports: ReadOnly::new(0),
164+
emerg_wr: WriteOnly::new(0),
165+
};
166+
let state = Arc::new(Mutex::new(State {
167+
status: DeviceStatus::empty(),
168+
driver_features: 0,
169+
guest_page_size: 0,
170+
interrupt_pending: false,
171+
queues: vec![QueueStatus::default(); 2],
172+
}));
173+
let transport = FakeTransport {
174+
device_type: DeviceType::Console,
175+
max_queue_size: 2,
176+
device_features: 0,
177+
config_space: NonNull::from(&mut config_space).cast(),
178+
state: state.clone(),
179+
};
180+
let mut console = VirtIOConsole::<FakeHal, FakeTransport>::new(transport).unwrap();
181+
182+
// Nothing is available to receive.
183+
assert_eq!(console.recv(false).unwrap(), None);
184+
assert_eq!(console.recv(true).unwrap(), None);
185+
186+
// Still nothing after a spurious interrupt.
187+
assert_eq!(console.ack_interrupt(), Ok(false));
188+
assert_eq!(console.recv(false).unwrap(), None);
189+
190+
// Make a character available, and simulate an interrupt.
191+
{
192+
let mut state = state.lock().unwrap();
193+
state.write_to_queue(QUEUE_SIZE, QUEUE_RECEIVEQ_PORT_0, &[42]);
194+
195+
state.interrupt_pending = true;
196+
}
197+
assert_eq!(console.ack_interrupt(), Ok(true));
198+
assert_eq!(state.lock().unwrap().interrupt_pending, false);
199+
200+
// Receive the character. If we don't pop it it is still there to read again.
201+
assert_eq!(console.recv(false).unwrap(), Some(42));
202+
assert_eq!(console.recv(true).unwrap(), Some(42));
203+
assert_eq!(console.recv(true).unwrap(), None);
204+
}
205+
}

src/input.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ impl<H: Hal, T: Transport> VirtIOInput<'_, H, T> {
5656
if let Ok((token, _)) = self.event_queue.pop_used() {
5757
let event = &mut self.event_buf[token as usize];
5858
// requeue
59-
if self.event_queue.add(&[], &[event.as_buf_mut()]).is_ok() {
59+
if let Ok(new_token) = self.event_queue.add(&[], &[event.as_buf_mut()]) {
60+
// This only works because nothing happen between `pop_used` and `add` that affects
61+
// the list of free descriptors in the queue, so `add` reuses the descriptor which
62+
// was just freed by `pop_used`.
63+
assert_eq!(new_token, token);
6064
return Some(*event);
6165
}
6266
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! VirtIO guest drivers.
22
3-
#![no_std]
3+
#![cfg_attr(not(test), no_std)]
44
#![deny(unused_must_use, missing_docs)]
55
#![allow(clippy::identity_op)]
66
#![allow(dead_code)]

src/queue.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use core::mem::size_of;
22
use core::slice;
33
use core::sync::atomic::{fence, Ordering};
4+
#[cfg(test)]
5+
use core::{cmp::min, ptr};
46

57
use super::*;
68
use crate::transport::Transport;
@@ -270,6 +272,65 @@ struct UsedElem {
270272
len: Volatile<u32>,
271273
}
272274

275+
/// Simulates the device writing to a VirtIO queue, for use in tests.
276+
///
277+
/// The fake device always uses descriptors in order.
278+
#[cfg(test)]
279+
pub(crate) fn fake_write_to_queue(
280+
queue_size: u16,
281+
receive_queue_descriptors: *const Descriptor,
282+
receive_queue_driver_area: VirtAddr,
283+
receive_queue_device_area: VirtAddr,
284+
data: &[u8],
285+
) {
286+
let descriptors =
287+
unsafe { slice::from_raw_parts(receive_queue_descriptors, queue_size as usize) };
288+
let available_ring = receive_queue_driver_area as *const AvailRing;
289+
let used_ring = receive_queue_device_area as *mut UsedRing;
290+
unsafe {
291+
// Make sure there is actually at least one descriptor available to write to.
292+
assert_ne!((*available_ring).idx.read(), (*used_ring).idx.read());
293+
// The fake device always uses descriptors in order, like VIRTIO_F_IN_ORDER, so
294+
// `used_ring.idx` marks the next descriptor we should take from the available ring.
295+
let next_slot = (*used_ring).idx.read() & (queue_size - 1);
296+
let head_descriptor_index = (*available_ring).ring[next_slot as usize].read();
297+
let mut descriptor = &descriptors[head_descriptor_index as usize];
298+
299+
// Loop through all descriptors in the chain, writing data to them.
300+
let mut remaining_data = data;
301+
loop {
302+
// Check the buffer and write to it.
303+
let flags = descriptor.flags.read();
304+
assert!(flags.contains(DescFlags::WRITE));
305+
let buffer_length = descriptor.len.read() as usize;
306+
let length_to_write = min(remaining_data.len(), buffer_length);
307+
ptr::copy(
308+
remaining_data.as_ptr(),
309+
descriptor.addr.read() as *mut u8,
310+
length_to_write,
311+
);
312+
remaining_data = &remaining_data[length_to_write..];
313+
314+
if flags.contains(DescFlags::NEXT) {
315+
let next = descriptor.next.read() as usize;
316+
descriptor = &descriptors[next];
317+
} else {
318+
assert_eq!(remaining_data.len(), 0);
319+
break;
320+
}
321+
}
322+
323+
// Mark the buffer as used.
324+
(*used_ring).ring[next_slot as usize]
325+
.id
326+
.write(head_descriptor_index as u32);
327+
(*used_ring).ring[next_slot as usize]
328+
.len
329+
.write(data.len() as u32);
330+
(*used_ring).idx.update(|idx| *idx += 1);
331+
}
332+
}
333+
273334
#[cfg(test)]
274335
mod tests {
275336
use super::*;

src/transport/fake.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
use super::{DeviceStatus, Transport};
2+
use crate::{
3+
queue::{fake_write_to_queue, Descriptor},
4+
DeviceType, PhysAddr,
5+
};
6+
use alloc::{sync::Arc, vec::Vec};
7+
use core::ptr::NonNull;
8+
use std::sync::Mutex;
9+
10+
/// A fake implementation of [`Transport`] for unit tests.
11+
#[derive(Debug)]
12+
pub struct FakeTransport {
13+
pub device_type: DeviceType,
14+
pub max_queue_size: u32,
15+
pub device_features: u64,
16+
pub config_space: NonNull<u64>,
17+
pub state: Arc<Mutex<State>>,
18+
}
19+
20+
impl Transport for FakeTransport {
21+
fn device_type(&self) -> DeviceType {
22+
self.device_type
23+
}
24+
25+
fn read_device_features(&mut self) -> u64 {
26+
self.device_features
27+
}
28+
29+
fn write_driver_features(&mut self, driver_features: u64) {
30+
self.state.lock().unwrap().driver_features = driver_features;
31+
}
32+
33+
fn max_queue_size(&self) -> u32 {
34+
self.max_queue_size
35+
}
36+
37+
fn notify(&mut self, queue: u32) {
38+
self.state.lock().unwrap().queues[queue as usize].notified = true;
39+
}
40+
41+
fn set_status(&mut self, status: DeviceStatus) {
42+
self.state.lock().unwrap().status = status;
43+
}
44+
45+
fn set_guest_page_size(&mut self, guest_page_size: u32) {
46+
self.state.lock().unwrap().guest_page_size = guest_page_size;
47+
}
48+
49+
fn queue_set(
50+
&mut self,
51+
queue: u32,
52+
size: u32,
53+
descriptors: PhysAddr,
54+
driver_area: PhysAddr,
55+
device_area: PhysAddr,
56+
) {
57+
let mut state = self.state.lock().unwrap();
58+
state.queues[queue as usize].size = size;
59+
state.queues[queue as usize].descriptors = descriptors;
60+
state.queues[queue as usize].driver_area = driver_area;
61+
state.queues[queue as usize].device_area = device_area;
62+
}
63+
64+
fn queue_used(&mut self, queue: u32) -> bool {
65+
self.state.lock().unwrap().queues[queue as usize].descriptors != 0
66+
}
67+
68+
fn ack_interrupt(&mut self) -> bool {
69+
let mut state = self.state.lock().unwrap();
70+
let pending = state.interrupt_pending;
71+
if pending {
72+
state.interrupt_pending = false;
73+
}
74+
pending
75+
}
76+
77+
fn config_space(&self) -> NonNull<u64> {
78+
self.config_space
79+
}
80+
}
81+
82+
#[derive(Debug, Default)]
83+
pub struct State {
84+
pub status: DeviceStatus,
85+
pub driver_features: u64,
86+
pub guest_page_size: u32,
87+
pub interrupt_pending: bool,
88+
pub queues: Vec<QueueStatus>,
89+
}
90+
91+
impl State {
92+
/// Simulates the device writing to the given queue.
93+
///
94+
/// The fake device always uses descriptors in order.
95+
pub fn write_to_queue(&mut self, queue_size: u16, queue_index: usize, data: &[u8]) {
96+
let receive_queue = &self.queues[queue_index];
97+
assert_ne!(receive_queue.descriptors, 0);
98+
fake_write_to_queue(
99+
queue_size,
100+
receive_queue.descriptors as *const Descriptor,
101+
receive_queue.driver_area,
102+
receive_queue.device_area,
103+
data,
104+
);
105+
}
106+
}
107+
108+
#[derive(Clone, Debug, Default, Eq, PartialEq)]
109+
pub struct QueueStatus {
110+
pub size: u32,
111+
pub descriptors: PhysAddr,
112+
pub driver_area: PhysAddr,
113+
pub device_area: PhysAddr,
114+
pub notified: bool,
115+
}

src/transport/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#[cfg(test)]
2+
pub mod fake;
13
pub mod mmio;
24

35
use crate::{PhysAddr, PAGE_SIZE};
@@ -70,6 +72,7 @@ pub trait Transport {
7072

7173
bitflags! {
7274
/// The device status field.
75+
#[derive(Default)]
7376
pub struct DeviceStatus: u32 {
7477
/// Indicates that the guest OS has found the device and recognized it
7578
/// as a valid virtio device.
@@ -99,7 +102,7 @@ bitflags! {
99102

100103
/// Types of virtio devices.
101104
#[repr(u8)]
102-
#[derive(Debug, Eq, PartialEq)]
105+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103106
#[allow(missing_docs)]
104107
pub enum DeviceType {
105108
Invalid = 0,

0 commit comments

Comments
 (0)