mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2024-12-22 13:45:20 +00:00
cloud-hypervisor: Add devices crate
Based on the Firecracker devices crate from commit 9cdb5b2. It is a trimmed down version compared to the Firecracker one, to remove a bunch of pulled dependencies (logger, metrics, rate limiter, etc...). Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
parent
7e2d1aca2d
commit
1853b350ee
28
Cargo.lock
generated
28
Cargo.lock
generated
@ -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)" = "<none>"
|
||||
"checksum libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)" = "e962c7641008ac010fa60a7dfdc1712449f29c44ef2d4702394aea943ee75047"
|
||||
|
14
devices/Cargo.toml
Normal file
14
devices/Cargo.toml
Normal file
@ -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"
|
256
devices/src/bus.rs
Normal file
256
devices/src/bus.rs
Normal file
@ -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<T> = result::Result<T, Error>;
|
||||
|
||||
#[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<Ordering> {
|
||||
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<BusRange, Arc<Mutex<BusDevice>>>,
|
||||
}
|
||||
|
||||
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<BusDevice>)> {
|
||||
// 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<BusDevice>)> {
|
||||
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<Mutex<BusDevice>>, 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]);
|
||||
}
|
||||
}
|
10
devices/src/legacy/mod.rs
Normal file
10
devices/src/legacy/mod.rs
Normal file
@ -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;
|
384
devices/src/legacy/serial.rs
Normal file
384
devices/src/legacy/serial.rs
Normal file
@ -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<u8>,
|
||||
out: Option<Box<io::Write + Send>>,
|
||||
}
|
||||
|
||||
impl Serial {
|
||||
fn new(interrupt_evt: EventFd, out: Option<Box<io::Write + Send>>) -> 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<io::Write + Send>) -> 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<Mutex<Vec<u8>>>,
|
||||
}
|
||||
|
||||
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<usize> {
|
||||
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);
|
||||
}
|
||||
}
|
60
devices/src/lib.rs
Normal file
60
devices/src/lib.rs
Normal file
@ -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<T> = std::result::Result<T, Error>;
|
||||
|
||||
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),
|
||||
}
|
12
src/main.rs
12
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();
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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)?;
|
||||
|
@ -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<T> = result::Result<T, Error>;
|
||||
|
||||
@ -145,7 +158,7 @@ impl Vcpu {
|
||||
}
|
||||
}
|
||||
|
||||
struct VmConfig<'a> {
|
||||
pub struct VmConfig<'a> {
|
||||
kernel_path: &'a Path,
|
||||
cmdline: Option<cmdline::Cmdline>,
|
||||
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<Self> {
|
||||
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<Mutex<devices::legacy::Serial>>,
|
||||
serial_evt: EventFd,
|
||||
}
|
||||
|
||||
impl DeviceManager {
|
||||
fn new() -> Result<Self> {
|
||||
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<Vec<thread::JoinHandle<()>>>,
|
||||
devices: DeviceManager,
|
||||
cpuid: CpuId,
|
||||
config: VmConfig<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Vm<'a> {
|
||||
pub fn new(kvm: &Kvm, kernel_path: &'a Path) -> Result<Self> {
|
||||
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<Self> {
|
||||
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<thread::JoinHandle<()>> = 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::IoOut(addr, data) => {
|
||||
if addr == 0x3f8 {
|
||||
print!("{}", str::from_utf8(&data).unwrap());
|
||||
VcpuExit::IoIn(addr, data) => {
|
||||
io_bus.read(u64::from(addr), data);
|
||||
}
|
||||
VcpuExit::IoOut(addr, data) => {
|
||||
io_bus.write(u64::from(addr), data);
|
||||
}
|
||||
VcpuExit::MmioRead(addr, _data) => {
|
||||
println!("MMIO R -- addr: {:#x}", addr);
|
||||
|
Loading…
Reference in New Issue
Block a user