summary refs log tree commit diff
path: root/devices/src/virtio/block.rs
diff options
context:
space:
mode:
authorDylan Reid <dgreid@chromium.org>2017-10-06 15:26:46 -0700
committerchrome-bot <chrome-bot@chromium.org>2017-10-09 17:39:05 -0700
commitd169a8d9ed63ae33b8678821c9675963bcee3f92 (patch)
tree03f436cbaeeb74f8fa49e02232162fabed442ba5 /devices/src/virtio/block.rs
parent94bf1bf6b4a7791757937e2ffb2a81e797b82922 (diff)
downloadcrosvm-d169a8d9ed63ae33b8678821c9675963bcee3f92.tar
crosvm-d169a8d9ed63ae33b8678821c9675963bcee3f92.tar.gz
crosvm-d169a8d9ed63ae33b8678821c9675963bcee3f92.tar.bz2
crosvm-d169a8d9ed63ae33b8678821c9675963bcee3f92.tar.lz
crosvm-d169a8d9ed63ae33b8678821c9675963bcee3f92.tar.xz
crosvm-d169a8d9ed63ae33b8678821c9675963bcee3f92.tar.zst
crosvm-d169a8d9ed63ae33b8678821c9675963bcee3f92.zip
Move crosvm/hw to a new devices module
Moving the devices to their own module makes it easier to add tests that
use them.

Change-Id: I61bfef4037d16b20145b5fddce604835cdc4f67b
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/706559
Reviewed-by: Zach Reizner <zachr@chromium.org>
Diffstat (limited to 'devices/src/virtio/block.rs')
-rw-r--r--devices/src/virtio/block.rs406
1 files changed, 406 insertions, 0 deletions
diff --git a/devices/src/virtio/block.rs b/devices/src/virtio/block.rs
new file mode 100644
index 0000000..1af2693
--- /dev/null
+++ b/devices/src/virtio/block.rs
@@ -0,0 +1,406 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cmp;
+use std::fs::File;
+use std::io::{self, Seek, SeekFrom, Read, Write};
+use std::os::unix::io::{AsRawFd, RawFd};
+use std::result;
+use std::sync::Arc;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::thread::spawn;
+
+use sys_util::Result as SysResult;
+use sys_util::{EventFd, GuestAddress, GuestMemory, GuestMemoryError, Poller};
+
+use super::{VirtioDevice, Queue, DescriptorChain, INTERRUPT_STATUS_USED_RING, TYPE_BLOCK};
+
+const QUEUE_SIZE: u16 = 256;
+const QUEUE_SIZES: &'static [u16] = &[QUEUE_SIZE];
+const SECTOR_SHIFT: u8 = 9;
+const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
+
+const VIRTIO_BLK_T_IN: u32 = 0;
+const VIRTIO_BLK_T_OUT: u32 = 1;
+const VIRTIO_BLK_T_FLUSH: u32 = 4;
+
+const VIRTIO_BLK_S_OK: u8 = 0;
+const VIRTIO_BLK_S_IOERR: u8 = 1;
+const VIRTIO_BLK_S_UNSUPP: u8 = 2;
+
+#[derive(PartialEq)]
+enum RequestType {
+    In,
+    Out,
+    Flush,
+    Unsupported(u32),
+}
+
+#[derive(Debug)]
+enum ParseError {
+    /// Guest gave us bad memory addresses
+    GuestMemory(GuestMemoryError),
+    /// Guest gave us offsets that would have overflowed a usize.
+    CheckedOffset(GuestAddress, usize),
+    /// Guest gave us a write only descriptor that protocol says to read from.
+    UnexpectedWriteOnlyDescriptor,
+    /// Guest gave us a read only descriptor that protocol says to write to.
+    UnexpectedReadOnlyDescriptor,
+    /// Guest gave us too few descriptors in a descriptor chain.
+    DescriptorChainTooShort,
+    /// Guest gave us a descriptor that was too short to use.
+    DescriptorLengthTooSmall,
+}
+
+fn request_type(mem: &GuestMemory,
+                desc_addr: GuestAddress)
+                -> result::Result<RequestType, ParseError> {
+    let type_ = mem.read_obj_from_addr(desc_addr)
+        .map_err(ParseError::GuestMemory)?;
+    match type_ {
+        VIRTIO_BLK_T_IN => Ok(RequestType::In),
+        VIRTIO_BLK_T_OUT => Ok(RequestType::Out),
+        VIRTIO_BLK_T_FLUSH => Ok(RequestType::Flush),
+        t => Ok(RequestType::Unsupported(t)),
+    }
+}
+
+fn sector(mem: &GuestMemory, desc_addr: GuestAddress) -> result::Result<u64, ParseError> {
+    const SECTOR_OFFSET: usize = 8;
+    let addr = match mem.checked_offset(desc_addr, SECTOR_OFFSET) {
+        Some(v) => v,
+        None => return Err(ParseError::CheckedOffset(desc_addr, SECTOR_OFFSET)),
+    };
+
+    mem.read_obj_from_addr(addr)
+        .map_err(ParseError::GuestMemory)
+}
+
+#[derive(Debug)]
+enum ExecuteError {
+    Flush(io::Error),
+    Read(GuestMemoryError),
+    Seek(io::Error),
+    Write(GuestMemoryError),
+    Unsupported(u32),
+}
+
+impl ExecuteError {
+    fn status(&self) -> u8 {
+        match self {
+            &ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
+            &ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
+            &ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR,
+            &ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR,
+            &ExecuteError::Unsupported(_) => VIRTIO_BLK_S_UNSUPP,
+        }
+    }
+}
+
+struct Request {
+    request_type: RequestType,
+    sector: u64,
+    data_addr: GuestAddress,
+    data_len: u32,
+    status_addr: GuestAddress,
+}
+
+impl Request {
+    fn parse(avail_desc: &DescriptorChain,
+             mem: &GuestMemory)
+             -> result::Result<Request, ParseError> {
+        // The head contains the request type which MUST be readable.
+        if avail_desc.is_write_only() {
+            return Err(ParseError::UnexpectedWriteOnlyDescriptor);
+        }
+
+        let req_type = request_type(&mem, avail_desc.addr)?;
+        let sector = sector(&mem, avail_desc.addr)?;
+        let data_desc = avail_desc
+            .next_descriptor()
+            .ok_or(ParseError::DescriptorChainTooShort)?;
+        let status_desc = data_desc
+            .next_descriptor()
+            .ok_or(ParseError::DescriptorChainTooShort)?;
+
+        if data_desc.is_write_only() && req_type == RequestType::Out {
+            return Err(ParseError::UnexpectedWriteOnlyDescriptor);
+        }
+
+        if !data_desc.is_write_only() && req_type == RequestType::In {
+            return Err(ParseError::UnexpectedReadOnlyDescriptor);
+        }
+
+        // The status MUST always be writable
+        if !status_desc.is_write_only() {
+            return Err(ParseError::UnexpectedReadOnlyDescriptor);
+        }
+
+        if status_desc.len < 1 {
+            return Err(ParseError::DescriptorLengthTooSmall);
+        }
+
+        Ok(Request {
+               request_type: req_type,
+               sector: sector,
+               data_addr: data_desc.addr,
+               data_len: data_desc.len,
+               status_addr: status_desc.addr,
+           })
+    }
+
+    fn execute<T: Seek + Read + Write>(&self,
+                                       disk: &mut T,
+                                       mem: &GuestMemory)
+                                       -> result::Result<u32, ExecuteError> {
+        disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
+            .map_err(ExecuteError::Seek)?;
+        match self.request_type {
+            RequestType::In => {
+                mem.read_to_memory(self.data_addr, disk, self.data_len as usize)
+                    .map_err(ExecuteError::Read)?;
+                return Ok(self.data_len);
+            }
+            RequestType::Out => {
+                mem.write_from_memory(self.data_addr, disk, self.data_len as usize)
+                    .map_err(ExecuteError::Write)?;
+            }
+            RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?,
+            RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
+        };
+        Ok(0)
+    }
+}
+
+struct Worker {
+    queues: Vec<Queue>,
+    mem: GuestMemory,
+    disk_image: File,
+    interrupt_status: Arc<AtomicUsize>,
+    interrupt_evt: EventFd,
+}
+
+impl Worker {
+    fn process_queue(&mut self, queue_index: usize) -> bool {
+        let queue = &mut self.queues[queue_index];
+
+        let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
+        let mut used_count = 0;
+        for avail_desc in queue.iter(&self.mem) {
+            let len;
+            match Request::parse(&avail_desc, &self.mem) {
+                Ok(request) => {
+                    let status = match request.execute(&mut self.disk_image, &self.mem) {
+                        Ok(l) => {
+                            len = l;
+                            VIRTIO_BLK_S_OK
+                        }
+                        Err(e) => {
+                            error!("failed executing disk request: {:?}", e);
+                            len = 1; // 1 byte for the status
+                            e.status()
+                        }
+                    };
+                    // We use unwrap because the request parsing process already checked that the
+                    // status_addr was valid.
+                    self.mem
+                        .write_obj_at_addr(status, request.status_addr)
+                        .unwrap();
+                }
+                Err(e) => {
+                    error!("failed processing available descriptor chain: {:?}", e);
+                    len = 0;
+                }
+            }
+            used_desc_heads[used_count] = (avail_desc.index, len);
+            used_count += 1;
+        }
+
+        for &(desc_index, len) in &used_desc_heads[..used_count] {
+            queue.add_used(&self.mem, desc_index, len);
+        }
+        used_count > 0
+    }
+
+    fn signal_used_queue(&self) {
+        self.interrupt_status
+            .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
+        self.interrupt_evt.write(1).unwrap();
+    }
+
+    fn run(&mut self, queue_evt: EventFd, kill_evt: EventFd) {
+        const Q_AVAIL: u32 = 0;
+        const KILL: u32 = 1;
+
+        let mut poller = Poller::new(2);
+        'poll: loop {
+            let tokens = match poller.poll(&[(Q_AVAIL, &queue_evt), (KILL, &kill_evt)]) {
+                Ok(v) => v,
+                Err(e) => {
+                    error!("failed polling for events: {:?}", e);
+                    break;
+                }
+            };
+
+            let mut needs_interrupt = false;
+            for &token in tokens {
+                match token {
+                    Q_AVAIL => {
+                        if let Err(e) = queue_evt.read() {
+                            error!("failed reading queue EventFd: {:?}", e);
+                            break 'poll;
+                        }
+                        needs_interrupt |= self.process_queue(0);
+                    }
+                    KILL => break 'poll,
+                    _ => unreachable!(),
+                }
+            }
+            if needs_interrupt {
+                self.signal_used_queue();
+            }
+        }
+    }
+}
+
+/// Virtio device for exposing block level read/write operations on a host file.
+pub struct Block {
+    kill_evt: Option<EventFd>,
+    disk_image: Option<File>,
+    config_space: Vec<u8>,
+}
+
+fn build_config_space(disk_size: u64) -> Vec<u8> {
+    // We only support disk size, which uses the first two words of the configuration space.
+    // If the image is not a multiple of the sector size, the tail bits are not exposed.
+    // The config space is little endian.
+    let mut config = Vec::with_capacity(8);
+    let num_sectors = disk_size >> SECTOR_SHIFT;
+    for i in 0..8 {
+        config.push((num_sectors >> (8 * i)) as u8);
+    }
+    config
+}
+
+impl Block {
+    /// Create a new virtio block device that operates on the given file.
+    ///
+    /// The given file must be seekable and sizable.
+    pub fn new(mut disk_image: File) -> SysResult<Block> {
+        let disk_size = disk_image.seek(SeekFrom::End(0))? as u64;
+        if disk_size % SECTOR_SIZE != 0 {
+            warn!("Disk size {} is not a multiple of sector size {}; \
+                         the remainder will not be visible to the guest.",
+                  disk_size,
+                  SECTOR_SIZE);
+        }
+        Ok(Block {
+               kill_evt: None,
+               disk_image: Some(disk_image),
+               config_space: build_config_space(disk_size),
+           })
+    }
+}
+
+impl Drop for Block {
+    fn drop(&mut self) {
+        if let Some(kill_evt) = self.kill_evt.take() {
+            // Ignore the result because there is nothing we can do about it.
+            let _ = kill_evt.write(1);
+        }
+    }
+}
+
+impl VirtioDevice for Block {
+    fn keep_fds(&self) -> Vec<RawFd> {
+        let mut keep_fds = Vec::new();
+
+        if let Some(ref disk_image) = self.disk_image {
+            keep_fds.push(disk_image.as_raw_fd());
+        }
+
+        keep_fds
+    }
+
+    fn device_type(&self) -> u32 {
+        TYPE_BLOCK
+    }
+
+    fn queue_max_sizes(&self) -> &[u16] {
+        QUEUE_SIZES
+    }
+
+    fn read_config(&self, offset: u64, mut data: &mut [u8]) {
+        let config_len = self.config_space.len() as u64;
+        if offset >= config_len {
+            return;
+        }
+        if let Some(end) = offset.checked_add(data.len() as u64) {
+            // This write can't fail, offset and end are checked against config_len.
+            data.write(&self.config_space[offset as usize..cmp::min(end, config_len) as usize])
+                .unwrap();
+        }
+    }
+
+    fn activate(&mut self,
+                mem: GuestMemory,
+                interrupt_evt: EventFd,
+                status: Arc<AtomicUsize>,
+                queues: Vec<Queue>,
+                mut queue_evts: Vec<EventFd>) {
+        if queues.len() != 1 || queue_evts.len() != 1 {
+            return;
+        }
+
+        let (self_kill_evt, kill_evt) =
+            match EventFd::new().and_then(|e| Ok((e.try_clone()?, e))) {
+                Ok(v) => v,
+                Err(e) => {
+                    error!("failed creating kill EventFd pair: {:?}", e);
+                    return;
+                }
+            };
+        self.kill_evt = Some(self_kill_evt);
+
+        if let Some(disk_image) = self.disk_image.take() {
+            spawn(move || {
+                let mut worker = Worker {
+                    queues: queues,
+                    mem: mem,
+                    disk_image: disk_image,
+                    interrupt_status: status,
+                    interrupt_evt: interrupt_evt,
+                };
+                worker.run(queue_evts.remove(0), kill_evt);
+            });
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::path::PathBuf;
+    use sys_util::TempDir;
+
+    use super::*;
+
+    #[test]
+    fn read_size() {
+        let tempdir = TempDir::new("/tmp/block_read_test").unwrap();
+        let mut path = PathBuf::from(tempdir.as_path().unwrap());
+        path.push("disk_image");
+        let f = File::create(&path).unwrap();
+        f.set_len(0x1000).unwrap();
+
+        let b = Block::new(f).unwrap();
+        let mut num_sectors = [0u8; 4];
+        b.read_config(0, &mut num_sectors);
+        // size is 0x1000, so num_sectors is 8 (4096/512).
+        assert_eq!([0x08, 0x00, 0x00, 0x00], num_sectors);
+        let mut msw_sectors = [0u8; 4];
+        b.read_config(4, &mut msw_sectors);
+        // size is 0x1000, so msw_sectors is 0.
+        assert_eq!([0x00, 0x00, 0x00, 0x00], msw_sectors);
+    }
+}