Skip to content

Commit 4ee80e5

Browse files
authored
Add tests for VirtQueue (#12)
* Run unit tests on GitHub Actions. * Add some tests for VirtQueue initialisation. This required adding a fake HAL implementation, and a method to construct a fake header. * Test invalid cases of adding to queue. * Test adding buffers to queue. * VirtQueue struct doesn't need to be repr(C). It isn't shared with the host, only the DMA region (containing the descriptors, AvailRing and UsedRing) is.
1 parent 1581eae commit 4ee80e5

File tree

5 files changed

+220
-19
lines changed

5 files changed

+220
-19
lines changed

.github/workflows/main.yml

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,22 @@ jobs:
2525
build:
2626
runs-on: ubuntu-latest
2727
steps:
28-
- uses: actions/checkout@v2
29-
- uses: actions-rs/toolchain@v1
30-
with:
31-
profile: minimal
32-
toolchain: stable
33-
- name: Build
34-
uses: actions-rs/cargo@v1
35-
with:
36-
command: build
37-
args: --all-features
38-
- name: Docs
39-
uses: actions-rs/cargo@v1
40-
with:
41-
command: doc
28+
- uses: actions/checkout@v2
29+
- uses: actions-rs/toolchain@v1
30+
with:
31+
profile: minimal
32+
toolchain: stable
33+
- name: Build
34+
uses: actions-rs/cargo@v1
35+
with:
36+
command: build
37+
args: --all-features
38+
- name: Docs
39+
uses: actions-rs/cargo@v1
40+
with:
41+
command: doc
42+
- name: Test
43+
uses: actions-rs/cargo@v1
44+
with:
45+
command: test
46+
args: --all-features

src/hal.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#[cfg(test)]
2+
pub mod fake;
3+
14
use super::*;
25
use core::marker::PhantomData;
36

@@ -8,6 +11,7 @@ pub type VirtAddr = usize;
811
pub type PhysAddr = usize;
912

1013
/// A region of contiguous physical memory used for DMA.
14+
#[derive(Debug)]
1115
pub struct DMA<H: Hal> {
1216
paddr: usize,
1317
pages: usize,

src/hal/fake.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//! Fake HAL implementation for tests.
2+
3+
use crate::{Hal, PhysAddr, VirtAddr, PAGE_SIZE};
4+
use alloc::alloc::{alloc_zeroed, dealloc, handle_alloc_error};
5+
use core::alloc::Layout;
6+
7+
#[derive(Debug)]
8+
pub struct FakeHal;
9+
10+
/// Fake HAL implementation for use in unit tests.
11+
impl Hal for FakeHal {
12+
fn dma_alloc(pages: usize) -> PhysAddr {
13+
assert_ne!(pages, 0);
14+
let layout = Layout::from_size_align(pages * PAGE_SIZE, PAGE_SIZE).unwrap();
15+
// Safe because the size and alignment of the layout are non-zero.
16+
let ptr = unsafe { alloc_zeroed(layout) };
17+
if ptr.is_null() {
18+
handle_alloc_error(layout);
19+
}
20+
ptr as PhysAddr
21+
}
22+
23+
fn dma_dealloc(paddr: PhysAddr, pages: usize) -> i32 {
24+
assert_ne!(pages, 0);
25+
let layout = Layout::from_size_align(pages * PAGE_SIZE, PAGE_SIZE).unwrap();
26+
// Safe because the layout is the same as was used when the memory was allocated by
27+
// `dma_alloc` above.
28+
unsafe {
29+
dealloc(paddr as *mut u8, layout);
30+
}
31+
0
32+
}
33+
34+
fn phys_to_virt(paddr: PhysAddr) -> VirtAddr {
35+
paddr
36+
}
37+
38+
fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr {
39+
vaddr
40+
}
41+
}

src/header.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use crate::PAGE_SIZE;
22
use bitflags::*;
33
use volatile::{ReadOnly, Volatile, WriteOnly};
44

5+
const MAGIC_VALUE: u32 = 0x7472_6976;
6+
57
/// MMIO Device Legacy Register Interface.
68
///
79
/// Ref: 4.2.4 Legacy interface
@@ -148,7 +150,7 @@ pub struct VirtIOHeader {
148150
impl VirtIOHeader {
149151
/// Verify a valid header.
150152
pub fn verify(&self) -> bool {
151-
self.magic.read() == 0x7472_6976 && self.version.read() == 1 && self.device_id.read() != 0
153+
self.magic.read() == MAGIC_VALUE && self.version.read() == 1 && self.device_id.read() != 0
152154
}
153155

154156
/// Get the device type.
@@ -244,6 +246,53 @@ impl VirtIOHeader {
244246
pub fn config_space(&self) -> *mut u64 {
245247
(self as *const _ as usize + CONFIG_SPACE_OFFSET) as _
246248
}
249+
250+
/// Constructs a fake virtio header for use in unit tests.
251+
#[cfg(test)]
252+
pub fn make_fake_header(
253+
device_id: u32,
254+
vendor_id: u32,
255+
device_features: u32,
256+
queue_num_max: u32,
257+
) -> Self {
258+
Self {
259+
magic: ReadOnly::new(MAGIC_VALUE),
260+
version: ReadOnly::new(1),
261+
device_id: ReadOnly::new(device_id),
262+
vendor_id: ReadOnly::new(vendor_id),
263+
device_features: ReadOnly::new(device_features),
264+
device_features_sel: WriteOnly::default(),
265+
__r1: Default::default(),
266+
driver_features: Default::default(),
267+
driver_features_sel: Default::default(),
268+
guest_page_size: Default::default(),
269+
__r2: Default::default(),
270+
queue_sel: Default::default(),
271+
queue_num_max: ReadOnly::new(queue_num_max),
272+
queue_num: Default::default(),
273+
queue_align: Default::default(),
274+
queue_pfn: Default::default(),
275+
queue_ready: Default::default(),
276+
__r3: Default::default(),
277+
queue_notify: Default::default(),
278+
__r4: Default::default(),
279+
interrupt_status: Default::default(),
280+
interrupt_ack: Default::default(),
281+
__r5: Default::default(),
282+
status: Volatile::new(DeviceStatus::empty()),
283+
__r6: Default::default(),
284+
queue_desc_low: Default::default(),
285+
queue_desc_high: Default::default(),
286+
__r7: Default::default(),
287+
queue_avail_low: Default::default(),
288+
queue_avail_high: Default::default(),
289+
__r8: Default::default(),
290+
queue_used_low: Default::default(),
291+
queue_used_high: Default::default(),
292+
__r9: Default::default(),
293+
config_generation: Default::default(),
294+
}
295+
}
247296
}
248297

249298
bitflags! {

src/queue.rs

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use volatile::Volatile;
1111
/// The mechanism for bulk data transport on virtio devices.
1212
///
1313
/// Each device can have zero or more virtqueues.
14-
#[repr(C)]
14+
#[derive(Debug)]
1515
pub struct VirtQueue<'a, H: Hal> {
1616
/// DMA guard
1717
dma: DMA<H>,
@@ -24,7 +24,10 @@ pub struct VirtQueue<'a, H: Hal> {
2424

2525
/// The index of queue
2626
queue_idx: u32,
27-
/// The size of queue
27+
/// The size of the queue.
28+
///
29+
/// This is both the number of descriptors, and the number of slots in the available and used
30+
/// rings.
2831
queue_size: u16,
2932
/// The number of used queues.
3033
num_used: u16,
@@ -44,7 +47,7 @@ impl<H: Hal> VirtQueue<'_, H> {
4447
return Err(Error::InvalidParam);
4548
}
4649
let layout = VirtQueueLayout::new(size);
47-
// alloc continuous pages
50+
// Allocate contiguous pages.
4851
let dma = DMA::new(layout.size / PAGE_SIZE)?;
4952

5053
header.queue_set(idx as u32, size as u32, PAGE_SIZE as u32, dma.pfn());
@@ -54,7 +57,7 @@ impl<H: Hal> VirtQueue<'_, H> {
5457
let avail = unsafe { &mut *((dma.vaddr() + layout.avail_offset) as *mut AvailRing) };
5558
let used = unsafe { &mut *((dma.vaddr() + layout.used_offset) as *mut UsedRing) };
5659

57-
// link descriptors together
60+
// Link descriptors together.
5861
for i in 0..(size - 1) {
5962
desc[i as usize].next.write(i + 1);
6063
}
@@ -260,3 +263,102 @@ struct UsedElem {
260263
id: Volatile<u32>,
261264
len: Volatile<u32>,
262265
}
266+
267+
#[cfg(test)]
268+
mod tests {
269+
use super::*;
270+
use crate::hal::fake::FakeHal;
271+
use core::mem::zeroed;
272+
273+
#[test]
274+
fn invalid_queue_size() {
275+
let mut header = unsafe { zeroed() };
276+
// Size not a power of 2.
277+
assert_eq!(
278+
VirtQueue::<FakeHal>::new(&mut header, 0, 3).unwrap_err(),
279+
Error::InvalidParam
280+
);
281+
}
282+
283+
#[test]
284+
fn queue_too_big() {
285+
let mut header = VirtIOHeader::make_fake_header(0, 0, 0, 4);
286+
assert_eq!(
287+
VirtQueue::<FakeHal>::new(&mut header, 0, 5).unwrap_err(),
288+
Error::InvalidParam
289+
);
290+
}
291+
292+
#[test]
293+
fn queue_already_used() {
294+
let mut header = VirtIOHeader::make_fake_header(0, 0, 0, 4);
295+
VirtQueue::<FakeHal>::new(&mut header, 0, 4).unwrap();
296+
assert_eq!(
297+
VirtQueue::<FakeHal>::new(&mut header, 0, 4).unwrap_err(),
298+
Error::AlreadyUsed
299+
);
300+
}
301+
302+
#[test]
303+
fn add_empty() {
304+
let mut header = VirtIOHeader::make_fake_header(0, 0, 0, 4);
305+
let mut queue = VirtQueue::<FakeHal>::new(&mut header, 0, 4).unwrap();
306+
assert_eq!(queue.add(&[], &[]).unwrap_err(), Error::InvalidParam);
307+
}
308+
309+
#[test]
310+
fn add_too_big() {
311+
let mut header = VirtIOHeader::make_fake_header(0, 0, 0, 4);
312+
let mut queue = VirtQueue::<FakeHal>::new(&mut header, 0, 4).unwrap();
313+
assert_eq!(queue.available_desc(), 4);
314+
assert_eq!(
315+
queue
316+
.add(&[&[], &[], &[]], &[&mut [], &mut []])
317+
.unwrap_err(),
318+
Error::BufferTooSmall
319+
);
320+
}
321+
322+
#[test]
323+
fn add_buffers() {
324+
let mut header = VirtIOHeader::make_fake_header(0, 0, 0, 4);
325+
let mut queue = VirtQueue::<FakeHal>::new(&mut header, 0, 4).unwrap();
326+
assert_eq!(queue.size(), 4);
327+
assert_eq!(queue.available_desc(), 4);
328+
329+
// Add a buffer chain consisting of two device-readable parts followed by two
330+
// device-writable parts.
331+
let token = queue
332+
.add(&[&[1, 2], &[3]], &[&mut [0, 0], &mut [0]])
333+
.unwrap();
334+
335+
assert_eq!(queue.available_desc(), 0);
336+
assert!(!queue.can_pop());
337+
338+
let first_descriptor_index = queue.avail.ring[0].read();
339+
assert_eq!(first_descriptor_index, token);
340+
assert_eq!(queue.desc[first_descriptor_index as usize].len.read(), 2);
341+
assert_eq!(
342+
queue.desc[first_descriptor_index as usize].flags.read(),
343+
DescFlags::NEXT
344+
);
345+
let second_descriptor_index = queue.desc[first_descriptor_index as usize].next.read();
346+
assert_eq!(queue.desc[second_descriptor_index as usize].len.read(), 1);
347+
assert_eq!(
348+
queue.desc[second_descriptor_index as usize].flags.read(),
349+
DescFlags::NEXT
350+
);
351+
let third_descriptor_index = queue.desc[second_descriptor_index as usize].next.read();
352+
assert_eq!(queue.desc[third_descriptor_index as usize].len.read(), 2);
353+
assert_eq!(
354+
queue.desc[third_descriptor_index as usize].flags.read(),
355+
DescFlags::NEXT | DescFlags::WRITE
356+
);
357+
let fourth_descriptor_index = queue.desc[third_descriptor_index as usize].next.read();
358+
assert_eq!(queue.desc[fourth_descriptor_index as usize].len.read(), 1);
359+
assert_eq!(
360+
queue.desc[fourth_descriptor_index as usize].flags.read(),
361+
DescFlags::WRITE
362+
);
363+
}
364+
}

0 commit comments

Comments
 (0)