diff --git a/Cargo.lock b/Cargo.lock index d22576ea3..a2582c811 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,6 +34,11 @@ name = "bitflags" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byteorder" version = "1.2.1" @@ -61,6 +66,26 @@ dependencies = [ "vmm 0.1.0", ] +[[package]] +name = "devices" +version = "0.1.0" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "epoll 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", + "vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)", + "vmm-sys-util 0.1.0 (git+https://github.com/sameo/vmm-sys-util)", +] + +[[package]] +name = "epoll" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "kvm-bindings" version = "0.1.1" @@ -147,6 +172,7 @@ name = "vmm" version = "0.1.0" dependencies = [ "arch 0.1.0", + "devices 0.1.0", "kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "kvm-ioctls 0.1.0 (git+https://github.com/rust-vmm/kvm-ioctls)", "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", @@ -186,8 +212,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" "checksum clap 2.27.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b8c532887f1a292d17de05ae858a8fe50a301e196f9ef0ddb7ccd0d1d00f180" +"checksum epoll 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3f0680f2a6f2a17fa7a8668a27c54e45e1ad1cf8a632f56a7c19b9e4e3bbe8a" "checksum kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c223e8703d2eb76d990c5f58e29c85b0f6f50e24b823babde927948e7c71fc03" "checksum kvm-ioctls 0.1.0 (git+https://github.com/rust-vmm/kvm-ioctls)" = "" "checksum libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)" = "e962c7641008ac010fa60a7dfdc1712449f29c44ef2d4702394aea943ee75047" diff --git a/devices/Cargo.toml b/devices/Cargo.toml new file mode 100644 index 000000000..ab81ad4f6 --- /dev/null +++ b/devices/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "devices" +version = "0.1.0" +authors = ["The Chromium OS Authors"] + +[dependencies] +byteorder = ">=1.2.1" +epoll = "=4.0.1" +libc = ">=0.2.39" +vm-memory = { git = "https://github.com/rust-vmm/vm-memory" } +vmm-sys-util = { git = "https://github.com/sameo/vmm-sys-util" } + +[dev-dependencies] +tempfile = ">=3.0.2" diff --git a/devices/src/bus.rs b/devices/src/bus.rs new file mode 100644 index 000000000..3b32856c4 --- /dev/null +++ b/devices/src/bus.rs @@ -0,0 +1,256 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Portions 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 THIRD-PARTY file. + +//! Handles routing to devices in an address space. + +use std::cmp::{Ord, Ordering, PartialEq, PartialOrd}; +use std::collections::btree_map::BTreeMap; +use std::result; +use std::sync::{Arc, Mutex}; + +/// Trait for devices that respond to reads or writes in an arbitrary address space. +/// +/// The device does not care where it exists in address space as each method is only given an offset +/// into its allocated portion of address space. +#[allow(unused_variables)] +pub trait BusDevice: Send { + /// Reads at `offset` from this device + fn read(&mut self, offset: u64, data: &mut [u8]) {} + /// Writes at `offset` into this device + fn write(&mut self, offset: u64, data: &[u8]) {} + /// Triggers the `irq_mask` interrupt on this device + fn interrupt(&self, irq_mask: u32) {} +} + +#[derive(Debug)] +pub enum Error { + /// The insertion failed because the new device overlapped with an old device. + Overlap, +} + +pub type Result = result::Result; + +#[derive(Debug, Copy, Clone)] +struct BusRange(u64, u64); + +impl Eq for BusRange {} + +impl PartialEq for BusRange { + fn eq(&self, other: &BusRange) -> bool { + self.0 == other.0 + } +} + +impl Ord for BusRange { + fn cmp(&self, other: &BusRange) -> Ordering { + self.0.cmp(&other.0) + } +} + +impl PartialOrd for BusRange { + fn partial_cmp(&self, other: &BusRange) -> Option { + self.0.partial_cmp(&other.0) + } +} + +/// A device container for routing reads and writes over some address space. +/// +/// This doesn't have any restrictions on what kind of device or address space this applies to. The +/// only restriction is that no two devices can overlap in this address space. +#[derive(Clone, Default)] +pub struct Bus { + devices: BTreeMap>>, +} + +impl Bus { + /// Constructs an a bus with an empty address space. + pub fn new() -> Bus { + Bus { + devices: BTreeMap::new(), + } + } + + fn first_before(&self, addr: u64) -> Option<(BusRange, &Mutex)> { + // for when we switch to rustc 1.17: self.devices.range(..addr).iter().rev().next() + for (range, dev) in self.devices.iter().rev() { + if range.0 <= addr { + return Some((*range, dev)); + } + } + None + } + + pub fn get_device(&self, addr: u64) -> Option<(u64, &Mutex)> { + if let Some((BusRange(start, len), dev)) = self.first_before(addr) { + let offset = addr - start; + if offset < len { + return Some((offset, dev)); + } + } + None + } + + /// Puts the given device at the given address space. + pub fn insert(&mut self, device: Arc>, base: u64, len: u64) -> Result<()> { + if len == 0 { + return Err(Error::Overlap); + } + + // Reject all cases where the new device's base is within an old device's range. + if self.get_device(base).is_some() { + return Err(Error::Overlap); + } + + // The above check will miss an overlap in which the new device's base address is before the + // range of another device. To catch that case, we search for a device with a range before + // the new device's range's end. If there is no existing device in that range that starts + // after the new device, then there will be no overlap. + if let Some((BusRange(start, _), _)) = self.first_before(base + len - 1) { + // Such a device only conflicts with the new device if it also starts after the new + // device because of our initial `get_device` check above. + if start >= base { + return Err(Error::Overlap); + } + } + + if self.devices.insert(BusRange(base, len), device).is_some() { + return Err(Error::Overlap); + } + + Ok(()) + } + + /// Reads data from the device that owns the range containing `addr` and puts it into `data`. + /// + /// Returns true on success, otherwise `data` is untouched. + pub fn read(&self, addr: u64, data: &mut [u8]) -> bool { + if let Some((offset, dev)) = self.get_device(addr) { + // OK to unwrap as lock() failing is a serious error condition and should panic. + dev.lock() + .expect("Failed to acquire device lock") + .read(offset, data); + true + } else { + false + } + } + + /// Writes `data` to the device that owns the range containing `addr`. + /// + /// Returns true on success, otherwise `data` is untouched. + pub fn write(&self, addr: u64, data: &[u8]) -> bool { + if let Some((offset, dev)) = self.get_device(addr) { + // OK to unwrap as lock() failing is a serious error condition and should panic. + dev.lock() + .expect("Failed to acquire device lock") + .write(offset, data); + true + } else { + false + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + struct DummyDevice; + impl BusDevice for DummyDevice {} + + struct ConstantDevice; + impl BusDevice for ConstantDevice { + fn read(&mut self, offset: u64, data: &mut [u8]) { + for (i, v) in data.iter_mut().enumerate() { + *v = (offset as u8) + (i as u8); + } + } + + fn write(&mut self, offset: u64, data: &[u8]) { + for (i, v) in data.iter().enumerate() { + assert_eq!(*v, (offset as u8) + (i as u8)) + } + } + } + + #[test] + fn bus_insert() { + let mut bus = Bus::new(); + let dummy = Arc::new(Mutex::new(DummyDevice)); + assert!(bus.insert(dummy.clone(), 0x10, 0).is_err()); + assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok()); + + let result = bus.insert(dummy.clone(), 0x0f, 0x10); + assert!(result.is_err()); + assert_eq!(format!("{:?}", result), "Err(Overlap)"); + + assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_err()); + assert!(bus.insert(dummy.clone(), 0x10, 0x15).is_err()); + assert!(bus.insert(dummy.clone(), 0x12, 0x15).is_err()); + assert!(bus.insert(dummy.clone(), 0x12, 0x01).is_err()); + assert!(bus.insert(dummy.clone(), 0x0, 0x20).is_err()); + assert!(bus.insert(dummy.clone(), 0x20, 0x05).is_ok()); + assert!(bus.insert(dummy.clone(), 0x25, 0x05).is_ok()); + assert!(bus.insert(dummy.clone(), 0x0, 0x10).is_ok()); + } + + #[test] + fn bus_read_write() { + let mut bus = Bus::new(); + let dummy = Arc::new(Mutex::new(DummyDevice)); + assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok()); + assert!(bus.read(0x10, &mut [0, 0, 0, 0])); + assert!(bus.write(0x10, &[0, 0, 0, 0])); + assert!(bus.read(0x11, &mut [0, 0, 0, 0])); + assert!(bus.write(0x11, &[0, 0, 0, 0])); + assert!(bus.read(0x16, &mut [0, 0, 0, 0])); + assert!(bus.write(0x16, &[0, 0, 0, 0])); + assert!(!bus.read(0x20, &mut [0, 0, 0, 0])); + assert!(!bus.write(0x20, &mut [0, 0, 0, 0])); + assert!(!bus.read(0x06, &mut [0, 0, 0, 0])); + assert!(!bus.write(0x06, &mut [0, 0, 0, 0])); + } + + #[test] + fn bus_read_write_values() { + let mut bus = Bus::new(); + let dummy = Arc::new(Mutex::new(ConstantDevice)); + assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok()); + + let mut values = [0, 1, 2, 3]; + assert!(bus.read(0x10, &mut values)); + assert_eq!(values, [0, 1, 2, 3]); + assert!(bus.write(0x10, &values)); + assert!(bus.read(0x15, &mut values)); + assert_eq!(values, [5, 6, 7, 8]); + assert!(bus.write(0x15, &values)); + } + + #[test] + fn busrange_cmp_and_clone() { + assert_eq!(BusRange(0x10, 2), BusRange(0x10, 3)); + assert_eq!(BusRange(0x10, 2), BusRange(0x10, 2)); + + assert!(BusRange(0x10, 2) < BusRange(0x12, 1)); + assert!(BusRange(0x10, 2) < BusRange(0x12, 3)); + + let bus_range = BusRange(0x10, 2); + assert_eq!(bus_range, bus_range.clone()); + + let mut bus = Bus::new(); + let mut data = [1, 2, 3, 4]; + assert!(bus + .insert(Arc::new(Mutex::new(DummyDevice)), 0x10, 0x10) + .is_ok()); + assert!(bus.write(0x10, &mut data)); + let bus_clone = bus.clone(); + assert!(bus.read(0x10, &mut data)); + assert_eq!(data, [1, 2, 3, 4]); + assert!(bus_clone.read(0x10, &mut data)); + assert_eq!(data, [1, 2, 3, 4]); + } +} diff --git a/devices/src/legacy/mod.rs b/devices/src/legacy/mod.rs new file mode 100644 index 000000000..bca23a0b5 --- /dev/null +++ b/devices/src/legacy/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Portions 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 THIRD-PARTY file. + +mod serial; + +pub use self::serial::Serial; diff --git a/devices/src/legacy/serial.rs b/devices/src/legacy/serial.rs new file mode 100644 index 000000000..2d7a26827 --- /dev/null +++ b/devices/src/legacy/serial.rs @@ -0,0 +1,384 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Portions 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 THIRD-PARTY file. + +use std::collections::VecDeque; +use std::{io, result}; + +use vmm_sys_util::{EventFd, Result}; + +use BusDevice; + +const LOOP_SIZE: usize = 0x40; + +const DATA: u8 = 0; +const IER: u8 = 1; +const IIR: u8 = 2; +const LCR: u8 = 3; +const MCR: u8 = 4; +const LSR: u8 = 5; +const MSR: u8 = 6; +const SCR: u8 = 7; + +const DLAB_LOW: u8 = 0; +const DLAB_HIGH: u8 = 1; + +const IER_RECV_BIT: u8 = 0x1; +const IER_THR_BIT: u8 = 0x2; +const IER_FIFO_BITS: u8 = 0x0f; + +const IIR_FIFO_BITS: u8 = 0xc0; +const IIR_NONE_BIT: u8 = 0x1; +const IIR_THR_BIT: u8 = 0x2; +const IIR_RECV_BIT: u8 = 0x4; + +const LCR_DLAB_BIT: u8 = 0x80; + +const LSR_DATA_BIT: u8 = 0x1; +const LSR_EMPTY_BIT: u8 = 0x20; +const LSR_IDLE_BIT: u8 = 0x40; + +const MCR_LOOP_BIT: u8 = 0x10; + +const DEFAULT_INTERRUPT_IDENTIFICATION: u8 = IIR_NONE_BIT; // no pending interrupt +const DEFAULT_LINE_STATUS: u8 = LSR_EMPTY_BIT | LSR_IDLE_BIT; // THR empty and line is idle +const DEFAULT_LINE_CONTROL: u8 = 0x3; // 8-bits per character +const DEFAULT_MODEM_CONTROL: u8 = 0x8; // Auxiliary output 2 +const DEFAULT_MODEM_STATUS: u8 = 0x20 | 0x10 | 0x80; // data ready, clear to send, carrier detect +const DEFAULT_BAUD_DIVISOR: u16 = 12; // 9600 bps + +/// Emulates serial COM ports commonly seen on x86 I/O ports 0x3f8/0x2f8/0x3e8/0x2e8. +/// +/// This can optionally write the guest's output to a Write trait object. To send input to the +/// guest, use `queue_input_bytes`. +pub struct Serial { + interrupt_enable: u8, + interrupt_identification: u8, + interrupt_evt: EventFd, + line_control: u8, + line_status: u8, + modem_control: u8, + modem_status: u8, + scratch: u8, + baud_divisor: u16, + in_buffer: VecDeque, + out: Option>, +} + +impl Serial { + fn new(interrupt_evt: EventFd, out: Option>) -> Serial { + Serial { + interrupt_enable: 0, + interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION, + interrupt_evt, + line_control: DEFAULT_LINE_CONTROL, + line_status: DEFAULT_LINE_STATUS, + modem_control: DEFAULT_MODEM_CONTROL, + modem_status: DEFAULT_MODEM_STATUS, + scratch: 0, + baud_divisor: DEFAULT_BAUD_DIVISOR, + in_buffer: VecDeque::new(), + out, + } + } + + /// Constructs a Serial port ready for output. + pub fn new_out(interrupt_evt: EventFd, out: Box) -> Serial { + Self::new(interrupt_evt, Some(out)) + } + + /// Constructs a Serial port with no connected output. + pub fn new_sink(interrupt_evt: EventFd) -> Serial { + Self::new(interrupt_evt, None) + } + + /// Queues raw bytes for the guest to read and signals the interrupt if the line status would + /// change. + pub fn queue_input_bytes(&mut self, c: &[u8]) -> Result<()> { + if !self.is_loop() { + self.in_buffer.extend(c); + self.recv_data()?; + } + Ok(()) + } + + fn is_dlab_set(&self) -> bool { + (self.line_control & LCR_DLAB_BIT) != 0 + } + + fn is_recv_intr_enabled(&self) -> bool { + (self.interrupt_enable & IER_RECV_BIT) != 0 + } + + fn is_thr_intr_enabled(&self) -> bool { + (self.interrupt_enable & IER_THR_BIT) != 0 + } + + fn is_loop(&self) -> bool { + (self.modem_control & MCR_LOOP_BIT) != 0 + } + + fn add_intr_bit(&mut self, bit: u8) { + self.interrupt_identification &= !IIR_NONE_BIT; + self.interrupt_identification |= bit; + } + + fn del_intr_bit(&mut self, bit: u8) { + self.interrupt_identification &= !bit; + if self.interrupt_identification == 0x0 { + self.interrupt_identification = IIR_NONE_BIT; + } + } + + fn thr_empty(&mut self) -> Result<()> { + if self.is_thr_intr_enabled() { + self.add_intr_bit(IIR_THR_BIT); + self.trigger_interrupt()? + } + Ok(()) + } + + fn recv_data(&mut self) -> Result<()> { + if self.is_recv_intr_enabled() { + self.add_intr_bit(IIR_RECV_BIT); + self.trigger_interrupt()? + } + self.line_status |= LSR_DATA_BIT; + Ok(()) + } + + fn trigger_interrupt(&mut self) -> result::Result<(), io::Error> { + self.interrupt_evt.write(1) + } + + fn iir_reset(&mut self) { + self.interrupt_identification = DEFAULT_INTERRUPT_IDENTIFICATION; + } + + fn handle_write(&mut self, offset: u8, v: u8) -> Result<()> { + match offset as u8 { + DLAB_LOW if self.is_dlab_set() => { + self.baud_divisor = (self.baud_divisor & 0xff00) | u16::from(v) + } + DLAB_HIGH if self.is_dlab_set() => { + self.baud_divisor = (self.baud_divisor & 0x00ff) | ((u16::from(v)) << 8) + } + DATA => { + if self.is_loop() { + if self.in_buffer.len() < LOOP_SIZE { + self.in_buffer.push_back(v); + self.recv_data()?; + } + } else { + if let Some(out) = self.out.as_mut() { + out.write_all(&[v])?; + out.flush()?; + } + self.thr_empty()?; + } + } + IER => self.interrupt_enable = v & IER_FIFO_BITS, + LCR => self.line_control = v, + MCR => self.modem_control = v, + SCR => self.scratch = v, + _ => {} + } + Ok(()) + } +} + +impl BusDevice for Serial { + fn read(&mut self, offset: u64, data: &mut [u8]) { + if data.len() != 1 { + return; + } + + data[0] = match offset as u8 { + DLAB_LOW if self.is_dlab_set() => self.baud_divisor as u8, + DLAB_HIGH if self.is_dlab_set() => (self.baud_divisor >> 8) as u8, + DATA => { + self.del_intr_bit(IIR_RECV_BIT); + if self.in_buffer.len() <= 1 { + self.line_status &= !LSR_DATA_BIT; + } + self.in_buffer.pop_front().unwrap_or_default() + } + IER => self.interrupt_enable, + IIR => { + let v = self.interrupt_identification | IIR_FIFO_BITS; + self.iir_reset(); + v + } + LCR => self.line_control, + MCR => self.modem_control, + LSR => self.line_status, + MSR => self.modem_status, + SCR => self.scratch, + _ => 0, + }; + } + + fn write(&mut self, offset: u64, data: &[u8]) { + if data.len() != 1 { + return; + } + + if let Err(_e) = self.handle_write(offset as u8, data[0]) {} + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io; + use std::sync::{Arc, Mutex}; + + #[derive(Clone)] + struct SharedBuffer { + buf: Arc>>, + } + + impl SharedBuffer { + fn new() -> SharedBuffer { + SharedBuffer { + buf: Arc::new(Mutex::new(Vec::new())), + } + } + } + + impl io::Write for SharedBuffer { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.buf.lock().unwrap().write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.buf.lock().unwrap().flush() + } + } + + #[test] + fn serial_output() { + let intr_evt = EventFd::new().unwrap(); + let serial_out = SharedBuffer::new(); + + let mut serial = Serial::new_out(intr_evt, Box::new(serial_out.clone())); + + serial.write(DATA as u64, &['x' as u8, 'y' as u8]); + serial.write(DATA as u64, &['a' as u8]); + serial.write(DATA as u64, &['b' as u8]); + serial.write(DATA as u64, &['c' as u8]); + assert_eq!( + serial_out.buf.lock().unwrap().as_slice(), + &['a' as u8, 'b' as u8, 'c' as u8] + ); + } + + #[test] + fn serial_input() { + let intr_evt = EventFd::new().unwrap(); + let serial_out = SharedBuffer::new(); + + let mut serial = + Serial::new_out(intr_evt.try_clone().unwrap(), Box::new(serial_out.clone())); + + // write 1 to the interrupt event fd, so that read doesn't block in case the event fd + // counter doesn't change (for 0 it blocks) + assert!(intr_evt.write(1).is_ok()); + serial.write(IER as u64, &[IER_RECV_BIT]); + serial + .queue_input_bytes(&['a' as u8, 'b' as u8, 'c' as u8]) + .unwrap(); + + assert_eq!(intr_evt.read().unwrap(), 2); + + // check if reading in a 2-length array doesn't have side effects + let mut data = [0u8, 0u8]; + serial.read(DATA as u64, &mut data[..]); + assert_eq!(data, [0u8, 0u8]); + + let mut data = [0u8]; + serial.read(LSR as u64, &mut data[..]); + assert_ne!(data[0] & LSR_DATA_BIT, 0); + serial.read(DATA as u64, &mut data[..]); + assert_eq!(data[0], 'a' as u8); + serial.read(DATA as u64, &mut data[..]); + assert_eq!(data[0], 'b' as u8); + serial.read(DATA as u64, &mut data[..]); + assert_eq!(data[0], 'c' as u8); + + // check if reading from the largest u8 offset returns 0 + serial.read(0xff, &mut data[..]); + assert_eq!(data[0], 0); + } + + #[test] + fn serial_thr() { + let intr_evt = EventFd::new().unwrap(); + let mut serial = Serial::new_sink(intr_evt.try_clone().unwrap()); + + // write 1 to the interrupt event fd, so that read doesn't block in case the event fd + // counter doesn't change (for 0 it blocks) + assert!(intr_evt.write(1).is_ok()); + serial.write(IER as u64, &[IER_THR_BIT]); + serial.write(DATA as u64, &['a' as u8]); + + assert_eq!(intr_evt.read().unwrap(), 2); + let mut data = [0u8]; + serial.read(IER as u64, &mut data[..]); + assert_eq!(data[0] & IER_FIFO_BITS, IER_THR_BIT); + serial.read(IIR as u64, &mut data[..]); + assert_ne!(data[0] & IIR_THR_BIT, 0); + } + + #[test] + fn serial_dlab() { + let mut serial = Serial::new_sink(EventFd::new().unwrap()); + + serial.write(LCR as u64, &[LCR_DLAB_BIT as u8]); + serial.write(DLAB_LOW as u64, &[0x12 as u8]); + serial.write(DLAB_HIGH as u64, &[0x34 as u8]); + + let mut data = [0u8]; + serial.read(LCR as u64, &mut data[..]); + assert_eq!(data[0], LCR_DLAB_BIT as u8); + serial.read(DLAB_LOW as u64, &mut data[..]); + assert_eq!(data[0], 0x12); + serial.read(DLAB_HIGH as u64, &mut data[..]); + assert_eq!(data[0], 0x34); + } + + #[test] + fn serial_modem() { + let mut serial = Serial::new_sink(EventFd::new().unwrap()); + + serial.write(MCR as u64, &[MCR_LOOP_BIT as u8]); + serial.write(DATA as u64, &['a' as u8]); + serial.write(DATA as u64, &['b' as u8]); + serial.write(DATA as u64, &['c' as u8]); + + let mut data = [0u8]; + serial.read(MSR as u64, &mut data[..]); + assert_eq!(data[0], DEFAULT_MODEM_STATUS as u8); + serial.read(MCR as u64, &mut data[..]); + assert_eq!(data[0], MCR_LOOP_BIT as u8); + serial.read(DATA as u64, &mut data[..]); + assert_eq!(data[0], 'a' as u8); + serial.read(DATA as u64, &mut data[..]); + assert_eq!(data[0], 'b' as u8); + serial.read(DATA as u64, &mut data[..]); + assert_eq!(data[0], 'c' as u8); + } + + #[test] + fn serial_scratch() { + let mut serial = Serial::new_sink(EventFd::new().unwrap()); + + serial.write(SCR as u64, &[0x12 as u8]); + + let mut data = [0u8]; + serial.read(SCR as u64, &mut data[..]); + assert_eq!(data[0], 0x12 as u8); + } +} diff --git a/devices/src/lib.rs b/devices/src/lib.rs new file mode 100644 index 000000000..9e1f976ff --- /dev/null +++ b/devices/src/lib.rs @@ -0,0 +1,60 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Portions 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 THIRD-PARTY file. + +//! Emulates virtual and hardware devices. +extern crate byteorder; +extern crate epoll; +extern crate libc; + +extern crate vm_memory; +extern crate vmm_sys_util; + +use std::fs::File; +use std::io; + +mod bus; +pub mod legacy; + +pub use self::bus::{Bus, BusDevice, Error as BusError}; + +pub type DeviceEventT = u16; + +/// The payload is used to handle events where the internal state of the VirtIO device +/// needs to be changed. +pub enum EpollHandlerPayload { + /// DrivePayload(disk_image) + DrivePayload(File), + /// Events that do not need a payload. + Empty, +} + +type Result = std::result::Result; + +pub trait EpollHandler: Send { + fn handle_event( + &mut self, + device_event: DeviceEventT, + event_flags: u32, + payload: EpollHandlerPayload, + ) -> Result<()>; +} + +#[derive(Debug)] +pub enum Error { + FailedReadingQueue { + event_type: &'static str, + underlying: io::Error, + }, + FailedReadTap, + FailedSignalingUsedQueue(io::Error), + PayloadExpected, + UnknownEvent { + device: &'static str, + event: DeviceEventT, + }, + IoError(io::Error), +} diff --git a/src/main.rs b/src/main.rs index 03903ea23..86d02afa5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,8 @@ use clap::{App, Arg}; use std::path::PathBuf; +use vmm::vm::*; + fn main() { let cmd_arguments = App::new("cloud-hypervisor") .version(crate_version!()) @@ -25,12 +27,16 @@ fn main() { ) .get_matches(); - let kernel_path = cmd_arguments + let kernel_arg = cmd_arguments .value_of("kernel") .map(PathBuf::from) .expect("Missing argument: kernel"); - println!("Booting {:?}...", kernel_path.as_path()); + let kernel_path = kernel_arg.as_path(); - vmm::boot_kernel(kernel_path.as_path()).unwrap(); + println!("Booting {:?}...", kernel_path); + + let vm_config = VmConfig::new(kernel_path).unwrap(); + + vmm::boot_kernel(vm_config).unwrap(); } diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index c05bd1c0f..9c56e570d 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] arch = { path = "../arch" } +devices = { path = "../devices" } kvm-bindings = "0.1" kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls" } libc = ">=0.2.39" diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 1431d2930..ee03a2488 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -6,10 +6,10 @@ extern crate kvm_ioctls; use kvm_ioctls::*; -use std::path::Path; + pub mod vm; -use self::vm::{Result, Vm}; +use self::vm::{Result, Vm, VmConfig}; struct Vmm { kvm: Kvm, @@ -22,9 +22,9 @@ impl Vmm { } } -pub fn boot_kernel(kernel: &Path) -> Result<()> { +pub fn boot_kernel(config: VmConfig) -> Result<()> { let vmm = Vmm::new()?; - let mut vm = Vm::new(&vmm.kvm, kernel)?; + let mut vm = Vm::new(&vmm.kvm, config)?; let entry = vm.load_kernel()?; vm.start(entry)?; diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 206acb16b..810c90670 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -4,6 +4,7 @@ // extern crate arch; +extern crate devices; extern crate kvm_ioctls; extern crate libc; extern crate linux_loader; @@ -12,21 +13,24 @@ extern crate vmm_sys_util; use kvm_bindings::{kvm_pit_config, kvm_userspace_memory_region, KVM_PIT_SPEAKER_DUMMY}; use kvm_ioctls::*; -use libc::{c_void, siginfo_t}; +use libc::{c_void, siginfo_t, EFD_NONBLOCK}; use linux_loader::cmdline; use linux_loader::loader::KernelLoader; use std::ffi::CString; use std::fs::File; +use std::io::{self, stdout}; use std::path::Path; -use std::sync::{Arc, Barrier}; -use std::{io, result, str, thread}; +use std::sync::{Arc, Barrier, Mutex}; +use std::{result, str, thread}; use vm_memory::{ Address, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion, GuestUsize, MmapError, }; use vmm_sys_util::signal::register_signal_handler; +use vmm_sys_util::EventFd; const VCPU_RTSIG_OFFSET: i32 = 0; +pub const DEFAULT_VCPUS: u8 = 1; const DEFAULT_CMDLINE: &str = "console=ttyS0 reboot=k panic=1 pci=off nomodules \ i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd"; const CMDLINE_OFFSET: GuestAddress = GuestAddress(0x20000); @@ -86,6 +90,15 @@ pub enum Error { /// The call to KVM_SET_CPUID2 failed. SetSupportedCpusFailed(io::Error), + + /// Cannot create EventFd. + EventFd(io::Error), + + /// Cannot create a device manager. + DeviceManager, + + /// Cannot add legacy device to Bus. + BusError(devices::BusError), } pub type Result = result::Result; @@ -145,7 +158,7 @@ impl Vcpu { } } -struct VmConfig<'a> { +pub struct VmConfig<'a> { kernel_path: &'a Path, cmdline: Option, cmdline_addr: GuestAddress, @@ -154,6 +167,15 @@ struct VmConfig<'a> { vcpu_count: u8, } +impl<'a> VmConfig<'a> { + pub fn new(kernel_path: &'a Path) -> Result { + Ok(VmConfig { + kernel_path, + ..Default::default() + }) + } +} + impl<'a> Default for VmConfig<'a> { fn default() -> Self { let line = String::from(DEFAULT_CMDLINE); @@ -170,27 +192,56 @@ impl<'a> Default for VmConfig<'a> { } } +struct DeviceManager { + io_bus: devices::Bus, + + // Serial port on 0x3f8 + serial: Arc>, + serial_evt: EventFd, +} + +impl DeviceManager { + fn new() -> Result { + let io_bus = devices::Bus::new(); + let serial_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFd)?; + let serial = Arc::new(Mutex::new(devices::legacy::Serial::new_out( + serial_evt.try_clone().map_err(Error::EventFd)?, + Box::new(stdout()), + ))); + + Ok(DeviceManager { + io_bus, + serial, + serial_evt, + }) + } + + /// Register legacy devices. + pub fn register_devices(&mut self) -> Result<()> { + self.io_bus + .insert(self.serial.clone(), 0x3f8, 0x8) + .map_err(Error::BusError)?; + Ok(()) + } +} + pub struct Vm<'a> { fd: VmFd, kernel: File, memory: GuestMemoryMmap, vcpus: Option>>, + devices: DeviceManager, cpuid: CpuId, config: VmConfig<'a>, } impl<'a> Vm<'a> { - pub fn new(kvm: &Kvm, kernel_path: &'a Path) -> Result { - let vm_config = VmConfig { - kernel_path, - ..Default::default() - }; - - let kernel = File::open(kernel_path).map_err(Error::KernelFile)?; + pub fn new(kvm: &Kvm, config: VmConfig<'a>) -> Result { + let kernel = File::open(&config.kernel_path).map_err(Error::KernelFile)?; let fd = kvm.create_vm().map_err(Error::VmCreate)?; // Init guest memory - let arch_mem_regions = arch::arch_memory_regions(vm_config.memory_size << 20); + let arch_mem_regions = arch::arch_memory_regions(config.memory_size << 20); let guest_memory = GuestMemoryMmap::new(&arch_mem_regions).map_err(Error::GuestMemory)?; guest_memory @@ -232,13 +283,16 @@ impl<'a> Vm<'a> { .get_supported_cpuid(MAX_KVM_CPUID_ENTRIES) .map_err(Error::VmSetup)?; + let device_manager = DeviceManager::new().map_err(|_| Error::DeviceManager)?; + Ok(Vm { fd, kernel, memory: guest_memory, vcpus: None, + devices: device_manager, cpuid, - config: vm_config, + config, }) } @@ -274,6 +328,8 @@ impl<'a> Vm<'a> { } pub fn start(&mut self, entry_addr: GuestAddress) -> Result<()> { + self.devices.register_devices()?; + let vcpu_count = self.config.vcpu_count; let mut vcpus: Vec> = Vec::with_capacity(vcpu_count as usize); @@ -281,6 +337,7 @@ impl<'a> Vm<'a> { for cpu_id in 0..vcpu_count { println!("Starting VCPU {:?}", cpu_id); + let io_bus = self.devices.io_bus.clone(); let mut vcpu = Vcpu::new(cpu_id, &self)?; vcpu.configure(entry_addr, &self)?; @@ -309,11 +366,11 @@ impl<'a> Vm<'a> { loop { match vcpu.run() { Ok(run) => match run { - VcpuExit::IoIn(_addr, _data) => {} + VcpuExit::IoIn(addr, data) => { + io_bus.read(u64::from(addr), data); + } VcpuExit::IoOut(addr, data) => { - if addr == 0x3f8 { - print!("{}", str::from_utf8(&data).unwrap()); - } + io_bus.write(u64::from(addr), data); } VcpuExit::MmioRead(addr, _data) => { println!("MMIO R -- addr: {:#x}", addr);