Selaa lähdekoodia

Merge pull request #53 from rcore-os/tests

Add more tests
chyyuu 2 vuotta sitten
vanhempi
commit
d3056cee9f
4 muutettua tiedostoa jossa 350 lisäystä ja 35 poistoa
  1. 203 0
      src/device/blk.rs
  2. 45 1
      src/device/console.rs
  3. 48 26
      src/queue.rs
  4. 54 8
      src/transport/fake.rs

+ 203 - 0
src/device/blk.rs

@@ -460,3 +460,206 @@ bitflags! {
         const NOTIFICATION_DATA     = 1 << 38;
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{
+        hal::fake::FakeHal,
+        transport::{
+            fake::{FakeTransport, QueueStatus, State},
+            DeviceStatus, DeviceType,
+        },
+    };
+    use alloc::{sync::Arc, vec};
+    use core::{mem::size_of, ptr::NonNull};
+    use std::{sync::Mutex, thread, time::Duration};
+
+    #[test]
+    fn config() {
+        let mut config_space = BlkConfig {
+            capacity_low: Volatile::new(0x42),
+            capacity_high: Volatile::new(0x02),
+            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(); 1],
+        }));
+        let transport = FakeTransport {
+            device_type: DeviceType::Console,
+            max_queue_size: QUEUE_SIZE.into(),
+            device_features: BlkFeature::RO.bits(),
+            config_space: NonNull::from(&mut config_space),
+            state: state.clone(),
+        };
+        let blk = VirtIOBlk::<FakeHal, FakeTransport<BlkConfig>>::new(transport).unwrap();
+
+        assert_eq!(blk.capacity(), 0x02_0000_0042);
+        assert_eq!(blk.readonly(), true);
+    }
+
+    #[test]
+    fn read() {
+        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(); 1],
+        }));
+        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 read request.
+        let handle = thread::spawn(move || {
+            println!("Device waiting for a request.");
+            while !state.lock().unwrap().queues[usize::from(QUEUE)].notified {
+                thread::sleep(Duration::from_millis(10));
+            }
+            println!("Transmit queue was notified.");
+
+            state
+                .lock()
+                .unwrap()
+                .read_write_queue::<{ QUEUE_SIZE as usize }>(QUEUE, |request| {
+                    assert_eq!(
+                        request,
+                        BlkReq {
+                            type_: ReqType::In,
+                            reserved: 0,
+                            sector: 42
+                        }
+                        .as_bytes()
+                    );
+
+                    let mut response = vec![0; SECTOR_SIZE];
+                    response[0..9].copy_from_slice(b"Test data");
+                    response.extend_from_slice(
+                        BlkResp {
+                            status: RespStatus::OK,
+                        }
+                        .as_bytes(),
+                    );
+
+                    response
+                });
+        });
+
+        // Read a block from the device.
+        let mut buffer = [0; 512];
+        blk.read_block(42, &mut buffer).unwrap();
+        assert_eq!(&buffer[0..9], b"Test data");
+
+        handle.join().unwrap();
+    }
+
+    #[test]
+    fn write() {
+        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(); 1],
+        }));
+        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 write request.
+        let handle = thread::spawn(move || {
+            println!("Device waiting for a request.");
+            while !state.lock().unwrap().queues[usize::from(QUEUE)].notified {
+                thread::sleep(Duration::from_millis(10));
+            }
+            println!("Transmit queue was notified.");
+
+            state
+                .lock()
+                .unwrap()
+                .read_write_queue::<{ QUEUE_SIZE as usize }>(QUEUE, |request| {
+                    assert_eq!(
+                        &request[0..size_of::<BlkReq>()],
+                        BlkReq {
+                            type_: ReqType::Out,
+                            reserved: 0,
+                            sector: 42
+                        }
+                        .as_bytes()
+                    );
+                    let data = &request[size_of::<BlkReq>()..];
+                    assert_eq!(data.len(), SECTOR_SIZE);
+                    assert_eq!(&data[0..9], b"Test data");
+
+                    let mut response = Vec::new();
+                    response.extend_from_slice(
+                        BlkResp {
+                            status: RespStatus::OK,
+                        }
+                        .as_bytes(),
+                    );
+
+                    response
+                });
+        });
+
+        // Write a block to the device.
+        let mut buffer = [0; 512];
+        buffer[0..9].copy_from_slice(b"Test data");
+        blk.write_block(42, &mut buffer).unwrap();
+
+        handle.join().unwrap();
+    }
+}

+ 45 - 1
src/device/console.rs

@@ -233,7 +233,7 @@ mod tests {
     };
     use alloc::{sync::Arc, vec};
     use core::ptr::NonNull;
-    use std::sync::Mutex;
+    use std::{sync::Mutex, thread, time::Duration};
 
     #[test]
     fn receive() {
@@ -282,4 +282,48 @@ mod tests {
         assert_eq!(console.recv(true).unwrap(), Some(42));
         assert_eq!(console.recv(true).unwrap(), None);
     }
+
+    #[test]
+    fn send() {
+        let mut config_space = Config {
+            cols: ReadOnly::new(0),
+            rows: ReadOnly::new(0),
+            max_nr_ports: ReadOnly::new(0),
+            emerg_wr: WriteOnly::default(),
+        };
+        let state = Arc::new(Mutex::new(State {
+            status: DeviceStatus::empty(),
+            driver_features: 0,
+            guest_page_size: 0,
+            interrupt_pending: false,
+            queues: vec![QueueStatus::default(); 2],
+        }));
+        let transport = FakeTransport {
+            device_type: DeviceType::Console,
+            max_queue_size: 2,
+            device_features: 0,
+            config_space: NonNull::from(&mut config_space),
+            state: state.clone(),
+        };
+        let mut console = VirtIOConsole::<FakeHal, FakeTransport<Config>>::new(transport).unwrap();
+
+        // Start a thread to simulate the device waiting for characters.
+        let handle = thread::spawn(move || {
+            println!("Device waiting for a character.");
+            while !state.lock().unwrap().queues[usize::from(QUEUE_TRANSMITQ_PORT_0)].notified {
+                thread::sleep(Duration::from_millis(10));
+            }
+            println!("Transmit queue was notified.");
+
+            let data = state
+                .lock()
+                .unwrap()
+                .read_from_queue::<QUEUE_SIZE>(QUEUE_TRANSMITQ_PORT_0);
+            assert_eq!(data, b"Q");
+        });
+
+        assert_eq!(console.send(b'Q'), Ok(()));
+
+        handle.join().unwrap();
+    }
 }

+ 48 - 26
src/queue.rs

@@ -523,23 +523,26 @@ struct UsedElem {
     len: u32,
 }
 
-/// Simulates the device writing to a VirtIO queue, for use in tests.
+/// Simulates the device reading from a VirtIO queue and writing a response back, for use in tests.
 ///
 /// The fake device always uses descriptors in order.
 #[cfg(test)]
-pub(crate) fn fake_write_to_queue<const QUEUE_SIZE: usize>(
-    receive_queue_descriptors: *const Descriptor,
-    receive_queue_driver_area: VirtAddr,
-    receive_queue_device_area: VirtAddr,
-    data: &[u8],
+pub(crate) fn fake_read_write_queue<const QUEUE_SIZE: usize>(
+    queue_descriptors: *const Descriptor,
+    queue_driver_area: VirtAddr,
+    queue_device_area: VirtAddr,
+    handler: impl FnOnce(Vec<u8>) -> Vec<u8>,
 ) {
-    let descriptors = ptr::slice_from_raw_parts(receive_queue_descriptors, QUEUE_SIZE);
-    let available_ring = receive_queue_driver_area as *const AvailRing<QUEUE_SIZE>;
-    let used_ring = receive_queue_device_area as *mut UsedRing<QUEUE_SIZE>;
+    use core::{ops::Deref, slice};
+
+    let descriptors = ptr::slice_from_raw_parts(queue_descriptors, QUEUE_SIZE);
+    let available_ring = queue_driver_area as *const AvailRing<QUEUE_SIZE>;
+    let used_ring = queue_device_area as *mut UsedRing<QUEUE_SIZE>;
+
     // Safe because the various pointers are properly aligned, dereferenceable, initialised, and
     // nothing else accesses them during this block.
     unsafe {
-        // Make sure there is actually at least one descriptor available to write to.
+        // Make sure there is actually at least one descriptor available to read from.
         assert_ne!((*available_ring).idx, (*used_ring).idx);
         // The fake device always uses descriptors in order, like VIRTIO_F_IN_ORDER, so
         // `used_ring.idx` marks the next descriptor we should take from the available ring.
@@ -547,32 +550,51 @@ pub(crate) fn fake_write_to_queue<const QUEUE_SIZE: usize>(
         let head_descriptor_index = (*available_ring).ring[next_slot as usize];
         let mut descriptor = &(*descriptors)[head_descriptor_index as usize];
 
-        // Loop through all descriptors in the chain, writing data to them.
-        let mut remaining_data = data;
-        loop {
-            // Check the buffer and write to it.
-            let flags = descriptor.flags;
-            assert!(flags.contains(DescFlags::WRITE));
-            let buffer_length = descriptor.len as usize;
-            let length_to_write = min(remaining_data.len(), buffer_length);
-            ptr::copy(
-                remaining_data.as_ptr(),
-                descriptor.addr as *mut u8,
-                length_to_write,
-            );
-            remaining_data = &remaining_data[length_to_write..];
+        // Loop through all input descriptors in the chain, reading data from them.
+        let mut input = Vec::new();
+        while !descriptor.flags.contains(DescFlags::WRITE) {
+            input.extend_from_slice(slice::from_raw_parts(
+                descriptor.addr as *const u8,
+                descriptor.len as usize,
+            ));
 
             if let Some(next) = descriptor.next() {
                 descriptor = &(*descriptors)[next as usize];
             } else {
-                assert_eq!(remaining_data.len(), 0);
                 break;
             }
         }
+        let input_length = input.len();
+
+        // Let the test handle the request.
+        let output = handler(input);
+
+        // Write the response to the remaining descriptors.
+        let mut remaining_output = output.deref();
+        if descriptor.flags.contains(DescFlags::WRITE) {
+            loop {
+                assert!(descriptor.flags.contains(DescFlags::WRITE));
+
+                let length_to_write = min(remaining_output.len(), descriptor.len as usize);
+                ptr::copy(
+                    remaining_output.as_ptr(),
+                    descriptor.addr as *mut u8,
+                    length_to_write,
+                );
+                remaining_output = &remaining_output[length_to_write..];
+
+                if let Some(next) = descriptor.next() {
+                    descriptor = &(*descriptors)[next as usize];
+                } else {
+                    break;
+                }
+            }
+        }
+        assert_eq!(remaining_output.len(), 0);
 
         // Mark the buffer as used.
         (*used_ring).ring[next_slot as usize].id = head_descriptor_index as u32;
-        (*used_ring).ring[next_slot as usize].len = data.len() as u32;
+        (*used_ring).ring[next_slot as usize].len = (input_length + output.len()) as u32;
         (*used_ring).idx += 1;
     }
 }

+ 54 - 8
src/transport/fake.rs

@@ -1,6 +1,6 @@
 use super::{DeviceStatus, DeviceType, Transport};
 use crate::{
-    queue::{fake_write_to_queue, Descriptor},
+    queue::{fake_read_write_queue, Descriptor},
     PhysAddr, Result,
 };
 use alloc::{sync::Arc, vec::Vec};
@@ -109,15 +109,61 @@ impl State {
     ///
     /// The fake device always uses descriptors in order.
     pub fn write_to_queue<const QUEUE_SIZE: usize>(&mut self, queue_index: u16, data: &[u8]) {
-        let receive_queue = &self.queues[queue_index as usize];
-        assert_ne!(receive_queue.descriptors, 0);
-        fake_write_to_queue::<QUEUE_SIZE>(
-            receive_queue.descriptors as *const Descriptor,
-            receive_queue.driver_area,
-            receive_queue.device_area,
-            data,
+        let queue = &self.queues[queue_index as usize];
+        assert_ne!(queue.descriptors, 0);
+        fake_read_write_queue::<QUEUE_SIZE>(
+            queue.descriptors as *const Descriptor,
+            queue.driver_area,
+            queue.device_area,
+            |input| {
+                assert_eq!(input, Vec::new());
+                data.to_owned()
+            },
         );
     }
+
+    /// Simulates the device reading from the given queue.
+    ///
+    /// Data is read into the `data` buffer passed in. Returns the number of bytes actually read.
+    ///
+    /// The fake device always uses descriptors in order.
+    pub fn read_from_queue<const QUEUE_SIZE: usize>(&mut self, queue_index: u16) -> Vec<u8> {
+        let queue = &self.queues[queue_index as usize];
+        assert_ne!(queue.descriptors, 0);
+
+        let mut ret = None;
+
+        // Read data from the queue but don't write any response.
+        fake_read_write_queue::<QUEUE_SIZE>(
+            queue.descriptors as *const Descriptor,
+            queue.driver_area,
+            queue.device_area,
+            |input| {
+                ret = Some(input);
+                Vec::new()
+            },
+        );
+
+        ret.unwrap()
+    }
+
+    /// Simulates the device reading data from the given queue and then writing a response back.
+    ///
+    /// The fake device always uses descriptors in order.
+    pub fn read_write_queue<const QUEUE_SIZE: usize>(
+        &mut self,
+        queue_index: u16,
+        handler: impl FnOnce(Vec<u8>) -> Vec<u8>,
+    ) {
+        let queue = &self.queues[queue_index as usize];
+        assert_ne!(queue.descriptors, 0);
+        fake_read_write_queue::<QUEUE_SIZE>(
+            queue.descriptors as *const Descriptor,
+            queue.driver_area,
+            queue.device_area,
+            handler,
+        )
+    }
 }
 
 #[derive(Clone, Debug, Default, Eq, PartialEq)]