mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
165 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7688e6e231 | ||
|
|
d784ac2982 | ||
|
|
8c2a9a75ec | ||
|
|
b8622b5c69 | ||
|
|
fe9398fe87 | ||
|
|
1f06c5907f | ||
|
|
5dd675710b | ||
|
|
037807f949 | ||
|
|
47ca277690 | ||
|
|
f59cad15a3 | ||
|
|
7ce0db90f0 | ||
|
|
9e764fc091 | ||
|
|
bb2e7bb942 | ||
|
|
40f9da524f | ||
|
|
8308e1bf25 | ||
|
|
ad128bf72d | ||
|
|
7205700c5f | ||
|
|
3af5619256 | ||
|
|
614eb68f16 | ||
|
|
5a187ee2c2 | ||
|
|
ae66a44d26 | ||
|
|
ebe8edd423 | ||
|
|
011496bda0 | ||
|
|
2610f4353d | ||
|
|
15387cd96a | ||
|
|
638bf0378c | ||
|
|
451502b50b | ||
|
|
98f81c36ec | ||
|
|
ee83c2d44e | ||
|
|
eea6f1dc9e | ||
|
|
3e99098bf3 | ||
|
|
e05de4514d | ||
|
|
6fb7c3bbc2 | ||
|
|
a44a903587 | ||
|
|
b4187a1b9d | ||
|
|
8718043dfc | ||
|
|
add0471120 | ||
|
|
772191b409 | ||
|
|
97699a521f | ||
|
|
a4ebcf486d | ||
|
|
cdfe576eb1 | ||
|
|
bc42420583 | ||
|
|
b7d3ad9063 | ||
|
|
56cad00f2e | ||
|
|
cc7a96e9d3 | ||
|
|
f21d54f6b0 | ||
|
|
584a2cccee | ||
|
|
633f51af9c | ||
|
|
51306555e7 | ||
|
|
ce60ff16c4 | ||
|
|
3dd329052c | ||
|
|
b2f85cbdc4 | ||
|
|
18a8bb0072 | ||
|
|
151637b647 | ||
|
|
c316c161a6 | ||
|
|
808fcaa43b | ||
|
|
bc87c9f19b | ||
|
|
66a7a94a12 | ||
|
|
dfb18ef14a | ||
|
|
0b8856d148 | ||
|
|
ec0b5567c8 | ||
|
|
f4d41d600b | ||
|
|
582fc7f989 | ||
|
|
44d8ab06ac | ||
|
|
5f52dd2d1e | ||
|
|
aface5bca2 | ||
|
|
dc31db478a | ||
|
|
b528e99b18 | ||
|
|
e84179994e | ||
|
|
1d9ad9e7b0 | ||
|
|
26a210a83a | ||
|
|
55f01b2180 | ||
|
|
49a129f3d3 | ||
|
|
1c23a09cc6 | ||
|
|
f0082fecb9 | ||
|
|
8b78e1221e | ||
|
|
f5a6e3c1ca | ||
|
|
d6e3b703ab | ||
|
|
71154d8362 | ||
|
|
7245cf7c5e | ||
|
|
7adb9d55ec | ||
|
|
760791abbe | ||
|
|
7b718f3029 | ||
|
|
08ed88c8d1 | ||
|
|
ab6a8f19f0 | ||
|
|
567eda45ec | ||
|
|
0affdd0df7 | ||
|
|
658c076eb2 | ||
|
|
c8364172a3 | ||
|
|
c52e276a5c | ||
|
|
48a9300667 | ||
|
|
10abfd4448 | ||
|
|
6678cbfb79 | ||
|
|
513d2fdcf6 | ||
|
|
76e3a30c31 | ||
|
|
0d53a5890d | ||
|
|
b3c809a78c | ||
|
|
af9a72eab6 | ||
|
|
2e0508cdc6 | ||
|
|
3c29c47783 | ||
|
|
74225ab5b3 | ||
|
|
f30ba069b7 | ||
|
|
e0fda0611c | ||
|
|
e2b38cc050 | ||
|
|
d97079d793 | ||
|
|
d180deb679 | ||
|
|
c6feb03dc0 | ||
|
|
ef2e8b6bc2 | ||
|
|
3645bf8d0f | ||
|
|
021e8d9e13 | ||
|
|
4b3fd906f7 | ||
|
|
bc5b72ff73 | ||
|
|
6c06420a11 | ||
|
|
b608671031 | ||
|
|
97c964891c | ||
|
|
df5058ec0a | ||
|
|
d9a355f85a | ||
|
|
f910476dd7 | ||
|
|
aa44726658 | ||
|
|
c0e2bbb23f | ||
|
|
4c9759e4fc | ||
|
|
b7ed71b012 | ||
|
|
8fcaf91d3b | ||
|
|
87195c9ccc | ||
|
|
fca911e5f3 | ||
|
|
91ce39e2a6 | ||
|
|
6cc3e88742 | ||
|
|
d1cd3c89c6 | ||
|
|
91c7f271b3 | ||
|
|
87f148cbff | ||
|
|
778c60f21d | ||
|
|
846505d360 | ||
|
|
9caad7394d | ||
|
|
ac950d9a97 | ||
|
|
a1f408a247 | ||
|
|
dfd44a6080 | ||
|
|
7d6bf75138 | ||
|
|
20f8d8d700 | ||
|
|
4ba1d2274e | ||
|
|
bd612b6e53 | ||
|
|
b713737f81 | ||
|
|
35a3b47554 | ||
|
|
f927d1a2d7 | ||
|
|
0c9547618a | ||
|
|
6abd50f4b1 | ||
|
|
c7f8498571 | ||
|
|
8a7cfe8ec4 | ||
|
|
49ef201cd1 | ||
|
|
a548a01423 | ||
|
|
baec27698e | ||
|
|
1a484a82f9 | ||
|
|
532f6a96f3 | ||
|
|
c0756c429d | ||
|
|
8c4c162109 | ||
|
|
d52684450f | ||
|
|
facc3b303a | ||
|
|
09aced9ed1 | ||
|
|
56c4b7000a | ||
|
|
d18c8d4c8c | ||
|
|
d217089b54 | ||
|
|
347f8a036b | ||
|
|
b6ae2ccda4 | ||
|
|
f86b9dd95e | ||
|
|
be199e5560 | ||
|
|
98d7955e34 |
@@ -9,9 +9,10 @@ before_script:
|
||||
|
||||
script:
|
||||
- cargo build --release
|
||||
- cargo build --release --no-default-features
|
||||
- cargo test
|
||||
- cargo clippy --all-targets --all-features -- -D warnings
|
||||
- find . -name "*.rs" | grep -v "vendor/" | xargs rustfmt --check
|
||||
- find . -name "*.rs" | xargs rustfmt --check
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
|
||||
596
Cargo.lock
generated
596
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
12
Cargo.toml
@@ -5,15 +5,17 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
clap = "=2.27.1"
|
||||
clap = "2.33.0"
|
||||
lazy_static = "1.4.0"
|
||||
log = { version = "0.4.8", features = ["std"] }
|
||||
vmm = { path = "vmm" }
|
||||
|
||||
[dev-dependencies]
|
||||
ssh2 = "=0.3.3"
|
||||
dirs = "2.0.0"
|
||||
ssh2 = "0.3.3"
|
||||
dirs = "2.0.2"
|
||||
credibility = "0.1.3"
|
||||
tempdir="0.3.7"
|
||||
lazy_static=">=1.1.0"
|
||||
tempdir= "0.3.7"
|
||||
lazy_static= "1.4.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
5
Jenkinsfile
vendored
5
Jenkinsfile
vendored
@@ -5,17 +5,16 @@ stage ("Builds") {
|
||||
}
|
||||
stage ('Install system packages') {
|
||||
sh "sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq build-essential mtools libssl-dev pkg-config"
|
||||
sh "sudo apt-get install -yq flex bison libelf-dev qemu-utils qemu-system"
|
||||
sh "sudo apt-get install -yq flex bison libelf-dev qemu-utils qemu-system libglib2.0-dev libpixman-1-dev libseccomp-dev"
|
||||
}
|
||||
stage ('Install Rust') {
|
||||
sh "nohup curl https://sh.rustup.rs -sSf | sh -s -- -y"
|
||||
}
|
||||
stage ('Run unit tests') {
|
||||
sh "sudo chmod a+rw /dev/kvm"
|
||||
sh "scripts/run_unit_tests.sh"
|
||||
}
|
||||
stage ('Run integration tests') {
|
||||
sh "sudo mount -t tmpfs tmpfs /tmp"
|
||||
sh "sudo mount -t tmpfs tmpfs /tmp"
|
||||
sh "scripts/run_integration_tests.sh"
|
||||
}
|
||||
}
|
||||
|
||||
9
acpi_tables/Cargo.toml
Normal file
9
acpi_tables/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "acpi_tables"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
|
||||
11
acpi_tables/src/lib.rs
Normal file
11
acpi_tables/src/lib.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
pub mod rsdp;
|
||||
pub mod sdt;
|
||||
|
||||
fn generate_checksum(data: &[u8]) -> u8 {
|
||||
(255 - data.iter().fold(0u8, |acc, x| acc.wrapping_add(*x))).wrapping_add(1)
|
||||
}
|
||||
67
acpi_tables/src/rsdp.rs
Normal file
67
acpi_tables/src/rsdp.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use vm_memory::ByteValued;
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct RSDP {
|
||||
pub signature: [u8; 8],
|
||||
pub checksum: u8,
|
||||
pub oem_id: [u8; 6],
|
||||
pub revision: u8,
|
||||
_rsdt_addr: u32,
|
||||
pub length: u32,
|
||||
pub xsdt_addr: u64,
|
||||
pub extended_checksum: u8,
|
||||
_reserved: [u8; 3],
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for RSDP {}
|
||||
|
||||
impl RSDP {
|
||||
pub fn new(oem_id: [u8; 6], xsdt_addr: u64) -> Self {
|
||||
let mut rsdp = RSDP {
|
||||
signature: *b"RSD PTR ",
|
||||
checksum: 0,
|
||||
oem_id,
|
||||
revision: 2,
|
||||
_rsdt_addr: 0,
|
||||
length: std::mem::size_of::<RSDP>() as u32,
|
||||
xsdt_addr,
|
||||
extended_checksum: 0,
|
||||
_reserved: [0; 3],
|
||||
};
|
||||
|
||||
rsdp.checksum = super::generate_checksum(&rsdp.as_slice()[0..19]);
|
||||
rsdp.extended_checksum = super::generate_checksum(&rsdp.as_slice());;
|
||||
rsdp
|
||||
}
|
||||
|
||||
pub fn len() -> usize {
|
||||
std::mem::size_of::<RSDP>()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::RSDP;
|
||||
use vm_memory::bytes::ByteValued;
|
||||
|
||||
#[test]
|
||||
fn test_rsdp() {
|
||||
let rsdp = RSDP::new(*b"CHYPER", 0xdead_beef);
|
||||
let sum = rsdp
|
||||
.as_slice()
|
||||
.iter()
|
||||
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||
assert_eq!(sum, 0);
|
||||
let sum: u8 = rsdp
|
||||
.as_slice()
|
||||
.iter()
|
||||
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||
assert_eq!(sum, 0);
|
||||
}
|
||||
}
|
||||
129
acpi_tables/src/sdt.rs
Normal file
129
acpi_tables/src/sdt.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct GenericAddress {
|
||||
pub address_space_id: u8,
|
||||
pub register_bit_width: u8,
|
||||
pub register_bit_offset: u8,
|
||||
pub access_size: u8,
|
||||
pub address: u64,
|
||||
}
|
||||
|
||||
impl GenericAddress {
|
||||
pub fn io_port_address(address: u16) -> Self {
|
||||
GenericAddress {
|
||||
address_space_id: 1,
|
||||
register_bit_width: 8,
|
||||
register_bit_offset: 0,
|
||||
access_size: 1,
|
||||
address: u64::from(address),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SDT {
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
impl SDT {
|
||||
pub fn new(
|
||||
signature: [u8; 4],
|
||||
length: u32,
|
||||
revision: u8,
|
||||
oem_id: [u8; 6],
|
||||
oem_table: [u8; 8],
|
||||
oem_revision: u32,
|
||||
) -> Self {
|
||||
assert!(length >= 36);
|
||||
|
||||
let mut data = Vec::with_capacity(length as usize);
|
||||
data.extend_from_slice(&signature);
|
||||
data.extend_from_slice(&length.to_le_bytes());
|
||||
data.push(revision);
|
||||
data.push(0); // checksum
|
||||
data.extend_from_slice(&oem_id);
|
||||
data.extend_from_slice(&oem_table);
|
||||
data.extend_from_slice(&oem_revision.to_le_bytes());
|
||||
data.extend_from_slice(b"CLDH");
|
||||
data.extend_from_slice(&0u32.to_le_bytes());
|
||||
assert_eq!(data.len(), 36);
|
||||
|
||||
data.resize(length as usize, 0);
|
||||
let mut sdt = SDT { data };
|
||||
|
||||
sdt.update_checksum();
|
||||
sdt
|
||||
}
|
||||
|
||||
pub fn update_checksum(&mut self) {
|
||||
self.data[9] = 0;
|
||||
let checksum = super::generate_checksum(self.data.as_slice());
|
||||
self.data[9] = checksum
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
&self.data.as_slice()
|
||||
}
|
||||
|
||||
pub fn append<T>(&mut self, value: T) {
|
||||
let orig_length = self.data.len();
|
||||
let new_length = orig_length + std::mem::size_of::<T>();
|
||||
self.data.resize(new_length, 0);
|
||||
self.write_u32(4, new_length as u32);
|
||||
self.write(orig_length, value);
|
||||
}
|
||||
|
||||
/// Write a value at the given offset
|
||||
pub fn write<T>(&mut self, offset: usize, value: T) {
|
||||
assert!((offset + (std::mem::size_of::<T>() - 1)) < self.data.len());
|
||||
unsafe {
|
||||
*(((self.data.as_mut_ptr() as usize) + offset) as *mut T) = value;
|
||||
}
|
||||
self.update_checksum();
|
||||
}
|
||||
|
||||
pub fn write_u8(&mut self, offset: usize, val: u8) {
|
||||
self.write(offset, val);
|
||||
}
|
||||
|
||||
pub fn write_u16(&mut self, offset: usize, val: u16) {
|
||||
self.write(offset, val);
|
||||
}
|
||||
|
||||
pub fn write_u32(&mut self, offset: usize, val: u32) {
|
||||
self.write(offset, val);
|
||||
}
|
||||
|
||||
pub fn write_u64(&mut self, offset: usize, val: u64) {
|
||||
self.write(offset, val);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SDT;
|
||||
|
||||
#[test]
|
||||
fn test_sdt() {
|
||||
let mut sdt = SDT::new(*b"TEST", 40, 1, *b"CLOUDH", *b"TESTTEST", 1);
|
||||
let sum: u8 = sdt
|
||||
.as_slice()
|
||||
.iter()
|
||||
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||
assert_eq!(sum, 0);
|
||||
sdt.write_u32(36, 0x12345678);
|
||||
let sum: u8 = sdt
|
||||
.as_slice()
|
||||
.iter()
|
||||
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||
assert_eq!(sum, 0);
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,17 @@ name = "arch"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "=1.2.1"
|
||||
kvm-bindings = "0.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = ">=0.2.39"
|
||||
[features]
|
||||
default = ["acpi"]
|
||||
acpi = ["acpi_tables"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3.2"
|
||||
kvm-bindings = "0.1.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = "0.2.60"
|
||||
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
arch_gen = { path = "../arch_gen" }
|
||||
|
||||
[dependencies.vm-memory]
|
||||
@@ -20,4 +25,4 @@ git = "https://github.com/rust-vmm/linux-loader"
|
||||
features = ["elf", "bzimage"]
|
||||
|
||||
[dev-dependencies]
|
||||
rand = ">=0.5.5"
|
||||
rand = "0.7.0"
|
||||
|
||||
@@ -16,6 +16,7 @@ pub fn configure_system(
|
||||
_cmdline_addr: GuestAddress,
|
||||
_cmdline_size: usize,
|
||||
_num_cpus: u8,
|
||||
_serial_enabled: bool,
|
||||
) -> super::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#![allow(
|
||||
clippy::unreadable_literal,
|
||||
clippy::const_static_lifetime,
|
||||
clippy::redundant_static_lifetimes,
|
||||
clippy::cast_lossless,
|
||||
clippy::transmute_ptr_to_ptr,
|
||||
clippy::cast_ptr_alignment
|
||||
@@ -13,6 +13,7 @@ extern crate byteorder;
|
||||
extern crate kvm_bindings;
|
||||
extern crate libc;
|
||||
|
||||
extern crate acpi_tables;
|
||||
extern crate arch_gen;
|
||||
extern crate kvm_ioctls;
|
||||
extern crate linux_loader;
|
||||
|
||||
315
arch/src/x86_64/acpi.rs
Normal file
315
arch/src/x86_64/acpi.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
use acpi_tables::{
|
||||
rsdp::RSDP,
|
||||
sdt::{GenericAddress, SDT},
|
||||
};
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
|
||||
use vm_memory::{Address, ByteValued, Bytes};
|
||||
|
||||
#[repr(packed)]
|
||||
struct LocalAPIC {
|
||||
pub r#type: u8,
|
||||
pub length: u8,
|
||||
pub processor_id: u8,
|
||||
pub apic_id: u8,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct IOAPIC {
|
||||
pub r#type: u8,
|
||||
pub length: u8,
|
||||
pub ioapic_id: u8,
|
||||
_reserved: u8,
|
||||
pub apic_address: u32,
|
||||
pub gsi_base: u32,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct InterruptSourceOverride {
|
||||
pub r#type: u8,
|
||||
pub length: u8,
|
||||
pub bus: u8,
|
||||
pub source: u8,
|
||||
pub gsi: u32,
|
||||
pub flags: u16,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct PCIRangeEntry {
|
||||
pub base_address: u64,
|
||||
pub segment: u16,
|
||||
pub start: u8,
|
||||
pub end: u8,
|
||||
_reserved: u32,
|
||||
}
|
||||
|
||||
pub fn create_dsdt_table(serial_enabled: bool) -> SDT {
|
||||
/*
|
||||
The hex tables in this file are generated from the ASL below with:
|
||||
"iasl -tc <dsdt.asl>"
|
||||
|
||||
As the output contains a table header that is not required the first 40 bytes
|
||||
should be disregarded.
|
||||
*/
|
||||
|
||||
/*
|
||||
Device (_SB.PCI0)
|
||||
{
|
||||
Name (_HID, EisaId ("PNP0A08") /* PCI Express Bus */) // _HID: Hardware ID
|
||||
Name (_CID, EisaId ("PNP0A03") /* PCI Bus */) // _CID: Compatible ID
|
||||
Name (_ADR, Zero) // _ADR: Address
|
||||
Name (_SEG, Zero) // _SEG: PCI Segment
|
||||
Name (_UID, Zero) // _UID: Unique ID
|
||||
Name (SUPP, Zero)
|
||||
}
|
||||
|
||||
Scope (_SB.PCI0)
|
||||
{
|
||||
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
|
||||
{
|
||||
WordBusNumber (ResourceProducer, MinFixed, MaxFixed, PosDecode,
|
||||
0x0000, // Granularity
|
||||
0x0000, // Range Minimum
|
||||
0x00FF, // Range Maximum
|
||||
0x0000, // Translation Offset
|
||||
0x0100, // Length
|
||||
,, )
|
||||
IO (Decode16,
|
||||
0x0CF8, // Range Minimum
|
||||
0x0CF8, // Range Maximum
|
||||
0x01, // Alignment
|
||||
0x08, // Length
|
||||
)
|
||||
WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange,
|
||||
0x0000, // Granularity
|
||||
0x0000, // Range Minimum
|
||||
0x0CF7, // Range Maximum
|
||||
0x0000, // Translation Offset
|
||||
0x0CF8, // Length
|
||||
,, , TypeStatic, DenseTranslation)
|
||||
WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange,
|
||||
0x0000, // Granularity
|
||||
0x0D00, // Range Minimum
|
||||
0xFFFF, // Range Maximum
|
||||
0x0000, // Translation Offset
|
||||
0xF300, // Length
|
||||
,, , TypeStatic, DenseTranslation)
|
||||
DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
|
||||
0x00000000, // Granularity
|
||||
0x000A0000, // Range Minimum
|
||||
0x000BFFFF, // Range Maximum
|
||||
0x00000000, // Translation Offset
|
||||
0x00020000, // Length
|
||||
,, , AddressRangeMemory, TypeStatic)
|
||||
DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, NonCacheable, ReadWrite,
|
||||
0x00000000, // Granularity
|
||||
0xC0000000, // Range Minimum
|
||||
0xFEC00000, // Range Maximum
|
||||
0x00000000, // Translation Offset
|
||||
0x3EC00001, // Length
|
||||
,, , AddressRangeMemory, TypeStatic)
|
||||
QWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
|
||||
0x0000000000000000, // Granularity
|
||||
0x0000000800000000, // Range Minimum
|
||||
0x0000000FFFFFFFFF, // Range Maximum
|
||||
0x0000000000000000, // Translation Offset
|
||||
0x0000000800000000, // Length
|
||||
,, , AddressRangeMemory, TypeStatic)
|
||||
})
|
||||
}
|
||||
*/
|
||||
let pci_dsdt_data = [
|
||||
0x5Bu8, 0x82, 0x36, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x50, 0x43, 0x49, 0x30, 0x08, 0x5F, 0x48,
|
||||
0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0A, 0x08, 0x08, 0x5F, 0x43, 0x49, 0x44, 0x0C, 0x41, 0xD0,
|
||||
0x0A, 0x03, 0x08, 0x5F, 0x41, 0x44, 0x52, 0x00, 0x08, 0x5F, 0x53, 0x45, 0x47, 0x00, 0x08,
|
||||
0x5F, 0x55, 0x49, 0x44, 0x00, 0x08, 0x53, 0x55, 0x50, 0x50, 0x00, 0x10, 0x41, 0x0B, 0x2E,
|
||||
0x5F, 0x53, 0x42, 0x5F, 0x50, 0x43, 0x49, 0x30, 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11, 0x40,
|
||||
0x0A, 0x0A, 0x9C, 0x88, 0x0D, 0x00, 0x02, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x47, 0x01, 0xF8, 0x0C, 0xF8, 0x0C, 0x01, 0x08, 0x88, 0x0D, 0x00,
|
||||
0x01, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00, 0xF7, 0x0C, 0x00, 0x00, 0xF8, 0x0C, 0x88, 0x0D,
|
||||
0x00, 0x01, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xF3, 0x87,
|
||||
0x17, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xFF, 0xFF,
|
||||
0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x87, 0x17, 0x00, 0x00, 0x0C,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0xFE, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0xC0, 0x3E, 0x8A, 0x2B, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x79, 0x00,
|
||||
];
|
||||
|
||||
/*
|
||||
Device (_SB.COM1)
|
||||
{
|
||||
Name (_HID, EisaId ("PNP0501") /* 16550A-compatible COM Serial Port */) // _HID: Hardware ID
|
||||
Name (_UID, Zero) // _UID: Unique ID
|
||||
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
|
||||
{
|
||||
Interrupt (ResourceConsumer, Edge, ActiveHigh, Exclusive, ,, )
|
||||
{
|
||||
0x00000004,
|
||||
}
|
||||
IO (Decode16,
|
||||
0x03F8, // Range Minimum
|
||||
0x03F8, // Range Maximum
|
||||
0x00, // Alignment
|
||||
0x08, // Length
|
||||
)
|
||||
})
|
||||
}
|
||||
*/
|
||||
let com1_dsdt_data = [
|
||||
0x5Bu8, 0x82, 0x36, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x43, 0x4F, 0x4D, 0x31, 0x08, 0x5F, 0x48,
|
||||
0x49, 0x44, 0x0C, 0x41, 0xD0, 0x05, 0x01, 0x08, 0x5F, 0x55, 0x49, 0x44, 0x00, 0x08, 0x5F,
|
||||
0x43, 0x52, 0x53, 0x11, 0x16, 0x0A, 0x13, 0x89, 0x06, 0x00, 0x03, 0x01, 0x04, 0x00, 0x00,
|
||||
0x00, 0x47, 0x01, 0xF8, 0x03, 0xF8, 0x03, 0x00, 0x08, 0x79, 0x00,
|
||||
];
|
||||
|
||||
/*
|
||||
Name (\_S5, Package (0x01) // _S5_: S5 System State
|
||||
{
|
||||
0x05
|
||||
})
|
||||
*/
|
||||
let s5_sleep_data = [0x08u8, 0x5F, 0x53, 0x35, 0x5F, 0x12, 0x04, 0x01, 0x0A, 0x05];
|
||||
|
||||
// DSDT
|
||||
let mut dsdt = SDT::new(*b"DSDT", 36, 6, *b"CLOUDH", *b"CHDSDT ", 1);
|
||||
dsdt.append(pci_dsdt_data);
|
||||
if serial_enabled {
|
||||
dsdt.append(com1_dsdt_data);
|
||||
}
|
||||
dsdt.append(s5_sleep_data);
|
||||
|
||||
dsdt
|
||||
}
|
||||
pub fn create_acpi_tables(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
num_cpus: u8,
|
||||
serial_enabled: bool,
|
||||
) -> GuestAddress {
|
||||
// RSDP is at the EBDA
|
||||
let rsdp_offset = super::EBDA_START;
|
||||
let mut tables: Vec<u64> = Vec::new();
|
||||
|
||||
// DSDT
|
||||
let dsdt = create_dsdt_table(serial_enabled);
|
||||
let dsdt_offset = rsdp_offset.checked_add(RSDP::len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(dsdt.as_slice(), dsdt_offset)
|
||||
.expect("Error writing DSDT table");
|
||||
|
||||
// FACP aka FADT
|
||||
// Revision 6 of the ACPI FADT table is 276 bytes long
|
||||
let mut facp = SDT::new(*b"FACP", 276, 6, *b"CLOUDH", *b"CHFACP ", 1);
|
||||
|
||||
// HW_REDUCED_ACPI and RESET_REG_SUP
|
||||
let fadt_flags: u32 = 1 << 20 | 1 << 10;
|
||||
facp.write(112, fadt_flags);
|
||||
|
||||
// RESET_REG
|
||||
facp.write(116, GenericAddress::io_port_address(0x3c0));
|
||||
// RESET_VALUE
|
||||
facp.write(128, 1u8);
|
||||
|
||||
facp.write(131, 3u8); // FADT minor version
|
||||
facp.write(140, dsdt_offset.0); // X_DSDT
|
||||
|
||||
// SLEEP_CONTROL_REG
|
||||
facp.write(244, GenericAddress::io_port_address(0x3c0));
|
||||
// SLEEP_STATUS_REG
|
||||
facp.write(256, GenericAddress::io_port_address(0x3c0));
|
||||
|
||||
facp.write(268, b"CLOUDHYP"); // Hypervisor Vendor Identity
|
||||
|
||||
facp.update_checksum();
|
||||
let facp_offset = dsdt_offset.checked_add(dsdt.len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(facp.as_slice(), facp_offset)
|
||||
.expect("Error writing FACP table");
|
||||
tables.push(facp_offset.0);
|
||||
|
||||
// MADT
|
||||
let mut madt = SDT::new(*b"APIC", 44, 5, *b"CLOUDH", *b"CHMADT ", 1);
|
||||
madt.write(36, super::mptable::APIC_DEFAULT_PHYS_BASE);
|
||||
|
||||
for cpu in 0..num_cpus {
|
||||
let lapic = LocalAPIC {
|
||||
r#type: 0,
|
||||
length: 8,
|
||||
processor_id: cpu,
|
||||
apic_id: cpu,
|
||||
flags: 1,
|
||||
};
|
||||
madt.append(lapic);
|
||||
}
|
||||
|
||||
madt.append(IOAPIC {
|
||||
r#type: 1,
|
||||
length: 12,
|
||||
ioapic_id: 0,
|
||||
apic_address: super::mptable::IO_APIC_DEFAULT_PHYS_BASE,
|
||||
gsi_base: 0,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
madt.append(InterruptSourceOverride {
|
||||
r#type: 2,
|
||||
length: 10,
|
||||
bus: 0,
|
||||
source: 4,
|
||||
gsi: 4,
|
||||
flags: 0,
|
||||
});
|
||||
|
||||
let madt_offset = facp_offset.checked_add(facp.len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(madt.as_slice(), madt_offset)
|
||||
.expect("Error writing MADT table");
|
||||
tables.push(madt_offset.0);
|
||||
|
||||
// MCFG
|
||||
let mut mcfg = SDT::new(*b"MCFG", 60, 1, *b"CLOUDH", *b"CHMCFG ", 1);
|
||||
|
||||
// 32-bit PCI enhanced configuration mechanism
|
||||
mcfg.append(PCIRangeEntry {
|
||||
base_address: super::MEM_32BIT_DEVICES_GAP_SIZE,
|
||||
segment: 0,
|
||||
start: 0,
|
||||
end: 0xff,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mcfg_offset = madt_offset.checked_add(madt.len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(mcfg.as_slice(), mcfg_offset)
|
||||
.expect("Error writing MCFG table");
|
||||
tables.push(mcfg_offset.0);
|
||||
|
||||
// XSDT
|
||||
let mut xsdt = SDT::new(*b"XSDT", 36, 1, *b"CLOUDH", *b"CHXSDT ", 1);
|
||||
for table in tables {
|
||||
xsdt.append(table);
|
||||
}
|
||||
xsdt.update_checksum();
|
||||
|
||||
let xsdt_offset = mcfg_offset.checked_add(mcfg.len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(xsdt.as_slice(), xsdt_offset)
|
||||
.expect("Error writing XSDT table");
|
||||
|
||||
// RSDP
|
||||
let rsdp = RSDP::new(*b"CLOUDH", xsdt_offset.0);
|
||||
guest_mem
|
||||
.write_slice(rsdp.as_slice(), rsdp_offset)
|
||||
.expect("Error writing RSDP");
|
||||
|
||||
rsdp_offset
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
mod acpi;
|
||||
mod gdt;
|
||||
pub mod interrupts;
|
||||
pub mod layout;
|
||||
@@ -12,12 +13,14 @@ mod mptable;
|
||||
pub mod regs;
|
||||
|
||||
use crate::RegionType;
|
||||
use linux_loader::loader::bootparam::{boot_params, setup_header, E820_RAM};
|
||||
use linux_loader::loader::bootparam::{boot_params, setup_header};
|
||||
use std::mem;
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
|
||||
};
|
||||
|
||||
const E820_RAM: u32 = 1;
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `DataInit`) where:
|
||||
// * the type that is implementing the trait is foreign or
|
||||
@@ -44,11 +47,11 @@ impl From<Error> for super::Error {
|
||||
}
|
||||
|
||||
// Where BIOS/VGA magic would live on a real PC.
|
||||
const EBDA_START: GuestAddress = GuestAddress(0x9fc00);
|
||||
const EBDA_START: GuestAddress = GuestAddress(0xa0000);
|
||||
const FIRST_ADDR_PAST_32BITS: GuestAddress = GuestAddress(1 << 32);
|
||||
|
||||
// Our 32-bit memory gap starts at 3G.
|
||||
const MEM_32BIT_GAP_START: GuestAddress = GuestAddress(0xc000_0000);
|
||||
pub const MEM_32BIT_GAP_START: GuestAddress = GuestAddress(0xc000_0000);
|
||||
|
||||
// Our 32-bit memory gap size is 1GB.
|
||||
const MEM_32BIT_GAP_SIZE: GuestUsize = (1024 << 20);
|
||||
@@ -124,6 +127,7 @@ pub fn configure_system(
|
||||
cmdline_size: usize,
|
||||
num_cpus: u8,
|
||||
setup_hdr: Option<setup_header>,
|
||||
serial_enabled: bool,
|
||||
) -> super::Result<()> {
|
||||
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
|
||||
const KERNEL_HDR_MAGIC: u32 = 0x53726448;
|
||||
@@ -179,6 +183,12 @@ pub fn configure_system(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
{
|
||||
let rsdp_addr = acpi::create_acpi_tables(guest_mem, num_cpus, serial_enabled);
|
||||
params.0.acpi_rsdp_addr = rsdp_addr.0;
|
||||
}
|
||||
|
||||
let zero_page_addr = layout::ZERO_PAGE_START;
|
||||
guest_mem
|
||||
.checked_offset(zero_page_addr, mem::size_of::<boot_params>())
|
||||
@@ -198,13 +208,13 @@ fn add_e820_entry(
|
||||
size: u64,
|
||||
mem_type: u32,
|
||||
) -> Result<(), Error> {
|
||||
if params.e820_entries >= params.e820_map.len() as u8 {
|
||||
if params.e820_entries >= params.e820_table.len() as u8 {
|
||||
return Err(Error::E820Configuration);
|
||||
}
|
||||
|
||||
params.e820_map[params.e820_entries as usize].addr = addr;
|
||||
params.e820_map[params.e820_entries as usize].size = size;
|
||||
params.e820_map[params.e820_entries as usize].type_ = mem_type;
|
||||
params.e820_table[params.e820_entries as usize].addr = addr;
|
||||
params.e820_table[params.e820_entries as usize].size = size;
|
||||
params.e820_table[params.e820_entries as usize].type_ = mem_type;
|
||||
params.e820_entries += 1;
|
||||
|
||||
Ok(())
|
||||
@@ -213,7 +223,7 @@ fn add_e820_entry(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use linux_loader::loader::bootparam::e820entry;
|
||||
use linux_loader::loader::bootparam::boot_e820_entry;
|
||||
|
||||
#[test]
|
||||
fn regions_lt_4gb() {
|
||||
@@ -245,7 +255,7 @@ mod tests {
|
||||
fn test_system_configuration() {
|
||||
let no_vcpus = 4;
|
||||
let gm = GuestMemoryMmap::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let config_err = configure_system(&gm, GuestAddress(0), 0, 1, None);
|
||||
let config_err = configure_system(&gm, GuestAddress(0), 0, 1, None, false);
|
||||
assert!(config_err.is_err());
|
||||
assert_eq!(
|
||||
config_err.unwrap_err(),
|
||||
@@ -263,7 +273,7 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::new(&ram_regions).unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None).unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, false).unwrap();
|
||||
|
||||
// Now assigning some memory that is equal to the start of the 32bit memory hole.
|
||||
let mem_size = 3328 << 20;
|
||||
@@ -274,7 +284,7 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::new(&ram_regions).unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None).unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, false).unwrap();
|
||||
|
||||
// Now assigning some memory that falls after the 32bit memory hole.
|
||||
let mem_size = 3330 << 20;
|
||||
@@ -285,19 +295,19 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::new(&ram_regions).unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None).unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, false).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_e820_entry() {
|
||||
let e820_map = [(e820entry {
|
||||
let e820_table = [(boot_e820_entry {
|
||||
addr: 0x1,
|
||||
size: 4,
|
||||
type_: 1,
|
||||
}); 128];
|
||||
|
||||
let expected_params = boot_params {
|
||||
e820_map,
|
||||
e820_table,
|
||||
e820_entries: 1,
|
||||
..Default::default()
|
||||
};
|
||||
@@ -305,25 +315,25 @@ mod tests {
|
||||
let mut params: boot_params = Default::default();
|
||||
add_e820_entry(
|
||||
&mut params,
|
||||
e820_map[0].addr,
|
||||
e820_map[0].size,
|
||||
e820_map[0].type_,
|
||||
e820_table[0].addr,
|
||||
e820_table[0].size,
|
||||
e820_table[0].type_,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
format!("{:?}", params.e820_map[0]),
|
||||
format!("{:?}", expected_params.e820_map[0])
|
||||
format!("{:?}", params.e820_table[0]),
|
||||
format!("{:?}", expected_params.e820_table[0])
|
||||
);
|
||||
assert_eq!(params.e820_entries, expected_params.e820_entries);
|
||||
|
||||
// Exercise the scenario where the field storing the length of the e820 entry table is
|
||||
// is bigger than the allocated memory.
|
||||
params.e820_entries = params.e820_map.len() as u8 + 1;
|
||||
params.e820_entries = params.e820_table.len() as u8 + 1;
|
||||
assert!(add_e820_entry(
|
||||
&mut params,
|
||||
e820_map[0].addr,
|
||||
e820_map[0].size,
|
||||
e820_map[0].type_
|
||||
e820_table[0].addr,
|
||||
e820_table[0].size,
|
||||
e820_table[0].type_
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
@@ -92,8 +92,8 @@ const MPC_SPEC: i8 = 4;
|
||||
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'F', 'C', ' ', ' ', ' ', ' ', ' ', ' ');
|
||||
const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
|
||||
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
|
||||
const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000; // source: linux/arch/x86/include/asm/apicdef.h
|
||||
const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee00000; // source: linux/arch/x86/include/asm/apicdef.h
|
||||
pub const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000; // source: linux/arch/x86/include/asm/apicdef.h
|
||||
pub const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee00000; // source: linux/arch/x86/include/asm/apicdef.h
|
||||
const APIC_VERSION: u8 = 0x14;
|
||||
const CPU_STEPPING: u32 = 0x600;
|
||||
const CPU_FEATURE_APIC: u32 = 0x200;
|
||||
|
||||
@@ -18,6 +18,10 @@ const PML4_START: GuestAddress = GuestAddress(0x9000);
|
||||
const PDPTE_START: GuestAddress = GuestAddress(0xa000);
|
||||
const PDE_START: GuestAddress = GuestAddress(0xb000);
|
||||
|
||||
// MTRR constants
|
||||
const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11
|
||||
const MTRR_MEM_TYPE_WB: u64 = 0x6;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to get SREGs for this CPU.
|
||||
@@ -269,6 +273,11 @@ fn create_msr_entries() -> Vec<kvm_msr_entry> {
|
||||
data: msr_index::MSR_IA32_MISC_ENABLE_FAST_STRING as u64,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_MTRRdefType,
|
||||
data: MTRR_ENABLE | MTRR_MEM_TYPE_WB,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
entries
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(
|
||||
clippy::unreadable_literal,
|
||||
clippy::const_static_lifetime,
|
||||
clippy::redundant_static_lifetimes,
|
||||
clippy::trivially_copy_pass_by_ref,
|
||||
clippy::useless_transmute,
|
||||
clippy::should_implement_trait,
|
||||
@@ -19,8 +19,8 @@
|
||||
pub mod bootparam;
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(clippy::unreadable_literal, clippy::const_static_lifetime)]
|
||||
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]
|
||||
pub mod mpspec;
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(clippy::unreadable_literal, clippy::const_static_lifetime)]
|
||||
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]
|
||||
pub mod msr_index;
|
||||
|
||||
@@ -4,14 +4,18 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = ">=1.2.1"
|
||||
epoll = "=4.0.1"
|
||||
kvm-bindings = "0.1"
|
||||
byteorder = "1.3.2"
|
||||
epoll = "4.1.0"
|
||||
kvm-bindings = "0.1.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = ">=0.2.39"
|
||||
log = "*"
|
||||
libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = ">=3.0.2"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[features]
|
||||
default = ["acpi"]
|
||||
acpi = []
|
||||
52
devices/src/acpi.rs
Normal file
52
devices/src/acpi.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use BusDevice;
|
||||
|
||||
/// A device for handling ACPI shutdown and reboot
|
||||
pub struct AcpiShutdownDevice {
|
||||
exit_evt: EventFd,
|
||||
reset_evt: EventFd,
|
||||
}
|
||||
|
||||
impl AcpiShutdownDevice {
|
||||
/// Constructs a device that will signal the given event when the guest requests it.
|
||||
pub fn new(exit_evt: EventFd, reset_evt: EventFd) -> AcpiShutdownDevice {
|
||||
AcpiShutdownDevice {
|
||||
exit_evt,
|
||||
reset_evt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Same I/O port used for shutdown and reboot
|
||||
impl BusDevice for AcpiShutdownDevice {
|
||||
// Spec has all fields as zero
|
||||
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||
for i in data.iter_mut() {
|
||||
*i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) {
|
||||
if data[0] == 1 {
|
||||
debug!("ACPI Reboot signalled");
|
||||
if let Err(e) = self.reset_evt.write(1) {
|
||||
error!("Error triggering ACPI reset event: {}", e);
|
||||
}
|
||||
}
|
||||
// The ACPI DSDT table specifies the S5 sleep state (shutdown) as value 5
|
||||
const S5_SLEEP_VALUE: u8 = 5;
|
||||
const SLEEP_STATUS_EN_BIT: u8 = 5;
|
||||
const SLEEP_VALUE_BIT: u8 = 2;
|
||||
if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) {
|
||||
debug!("ACPI Shutdown signalled");
|
||||
if let Err(e) = self.exit_evt.write(1) {
|
||||
error!("Error triggering ACPI shutdown event: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ impl PartialOrd for BusRange {
|
||||
/// 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>>>,
|
||||
devices: BTreeMap<BusRange, Arc<Mutex<dyn BusDevice>>>,
|
||||
}
|
||||
|
||||
impl Bus {
|
||||
@@ -88,7 +88,7 @@ impl Bus {
|
||||
}
|
||||
}
|
||||
|
||||
fn first_before(&self, addr: u64) -> Option<(BusRange, &Mutex<BusDevice>)> {
|
||||
fn first_before(&self, addr: u64) -> Option<(BusRange, &Mutex<dyn BusDevice>)> {
|
||||
let (range, dev) = self
|
||||
.devices
|
||||
.range(..=BusRange { base: addr, len: 1 })
|
||||
@@ -97,7 +97,7 @@ impl Bus {
|
||||
Some((*range, dev))
|
||||
}
|
||||
|
||||
pub fn resolve(&self, addr: u64) -> Option<(u64, u64, &Mutex<BusDevice>)> {
|
||||
pub fn resolve(&self, addr: u64) -> Option<(u64, u64, &Mutex<dyn BusDevice>)> {
|
||||
if let Some((range, dev)) = self.first_before(addr) {
|
||||
let offset = addr - range.base;
|
||||
if offset < range.len {
|
||||
@@ -108,7 +108,7 @@ impl Bus {
|
||||
}
|
||||
|
||||
/// Puts the given device at the given address space.
|
||||
pub fn insert(&mut self, device: Arc<Mutex<BusDevice>>, base: u64, len: u64) -> Result<()> {
|
||||
pub fn insert(&mut self, device: Arc<Mutex<dyn BusDevice>>, base: u64, len: u64) -> Result<()> {
|
||||
if len == 0 {
|
||||
return Err(Error::Overlap);
|
||||
}
|
||||
|
||||
@@ -188,7 +188,6 @@ impl BusDevice for Ioapic {
|
||||
IOWIN_OFF => self.ioapic_write(value),
|
||||
_ => {
|
||||
error!("IOAPIC: failed writing at offset {}", offset);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use BusDevice;
|
||||
|
||||
@@ -34,8 +34,9 @@ impl BusDevice for I8042Device {
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
if data.len() == 1 && data[0] == 0xfe && offset == 3 {
|
||||
debug!("i8042 reset signalled");
|
||||
if let Err(e) = self.reset_evt.write(1) {
|
||||
println!("Error triggering i8042 reset event: {}", e);
|
||||
error!("Error triggering i8042 reset event: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
use crate::{BusDevice, Interrupt};
|
||||
use std::collections::VecDeque;
|
||||
use std::{io, result};
|
||||
use vmm_sys_util::Result;
|
||||
use vmm_sys_util::errno::Result;
|
||||
|
||||
const LOOP_SIZE: usize = 0x40;
|
||||
|
||||
@@ -55,7 +55,7 @@ const DEFAULT_BAUD_DIVISOR: u16 = 12; // 9600 bps
|
||||
pub struct Serial {
|
||||
interrupt_enable: u8,
|
||||
interrupt_identification: u8,
|
||||
interrupt: Box<Interrupt>,
|
||||
interrupt: Box<dyn Interrupt>,
|
||||
line_control: u8,
|
||||
line_status: u8,
|
||||
modem_control: u8,
|
||||
@@ -63,11 +63,11 @@ pub struct Serial {
|
||||
scratch: u8,
|
||||
baud_divisor: u16,
|
||||
in_buffer: VecDeque<u8>,
|
||||
out: Option<Box<io::Write + Send>>,
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
}
|
||||
|
||||
impl Serial {
|
||||
fn new(interrupt: Box<Interrupt>, out: Option<Box<io::Write + Send>>) -> Serial {
|
||||
pub fn new(interrupt: Box<dyn Interrupt>, out: Option<Box<dyn io::Write + Send>>) -> Serial {
|
||||
Serial {
|
||||
interrupt_enable: 0,
|
||||
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
|
||||
@@ -84,12 +84,12 @@ impl Serial {
|
||||
}
|
||||
|
||||
/// Constructs a Serial port ready for output.
|
||||
pub fn new_out(interrupt: Box<Interrupt>, out: Box<io::Write + Send>) -> Serial {
|
||||
pub fn new_out(interrupt: Box<dyn Interrupt>, out: Box<dyn io::Write + Send>) -> Serial {
|
||||
Self::new(interrupt, Some(out))
|
||||
}
|
||||
|
||||
/// Constructs a Serial port with no connected output.
|
||||
pub fn new_sink(interrupt: Box<Interrupt>) -> Serial {
|
||||
pub fn new_sink(interrupt: Box<dyn Interrupt>) -> Serial {
|
||||
Self::new(interrupt, None)
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ mod tests {
|
||||
use super::*;
|
||||
use std::io;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
struct TestInterrupt {
|
||||
event_fd: EventFd,
|
||||
|
||||
@@ -19,10 +19,14 @@ extern crate vmm_sys_util;
|
||||
use std::fs::File;
|
||||
use std::{io, result};
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
mod acpi;
|
||||
mod bus;
|
||||
pub mod ioapic;
|
||||
pub mod legacy;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
pub use self::acpi::AcpiShutdownDevice;
|
||||
pub use self::bus::{Bus, BusDevice, Error as BusError};
|
||||
|
||||
pub type DeviceEventT = u16;
|
||||
|
||||
61
docs/debug-port.md
Normal file
61
docs/debug-port.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# `cloud-hypervisor` debug IO port
|
||||
|
||||
`cloud-hypervisor` uses the [`0x80`](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
|
||||
I/O port to trace user defined guest events.
|
||||
|
||||
Whenever the guest write one byte between `0x0` and `0xF` on this particular
|
||||
I/O port, `cloud-hypervisor` will log and timestamp that event at the `debug`
|
||||
log level.
|
||||
|
||||
It is up to the guest stack to decide when and what to write to the 0x80 port
|
||||
in order to signal the host about specific events and have `cloud-hypervisor`
|
||||
log it.
|
||||
|
||||
`cloud-hypervisor` defines several debug port code ranges that should be used
|
||||
for debugging specific components of the guest software stack. When logging a
|
||||
write of one of those codes to the debug port, `cloud-hypervisor` adds a
|
||||
pre-defined string to the logs.
|
||||
|
||||
| Code Range | Component | Log string |
|
||||
| ---------------- | ----------- | ------------ |
|
||||
| `0x00` to `0x1f` | Firmware | `Firmware` |
|
||||
| `0x20` to `0x3f` | Bootloader | `Bootloader` |
|
||||
| `0x40` to `0x5f` | Kernel | `Kernel` |
|
||||
| `0x60` to `0x7f` | Userspace | `Userspace` |
|
||||
| `0x80` to `0xff` | Custom | `Custom` |
|
||||
|
||||
One typical use case is guest boot time measurement and tracing. By writing
|
||||
different values to the debug I/O port at different boot process steps, the
|
||||
guest will have `cloud-hypervisor` generate timestamped logs of all those steps.
|
||||
That provides a basic but convenient way of measuring not only the overall guest
|
||||
boot time but all intermediate steps as well.
|
||||
|
||||
## Logging
|
||||
|
||||
Assuming parts of the guest software stack have been instrumented to use the
|
||||
`cloud-hypervisor` debug I/O port, we may want to gather the related logs.
|
||||
|
||||
To do so we need to start `cloud-hypervisor` with the right debug level
|
||||
(`-vvv`). It is also recommended to have it log into a dedicated file in order
|
||||
to easily grep for the tracing logs (e.g.
|
||||
`--log-file /tmp/cloud-hypervisor.log`):
|
||||
|
||||
```
|
||||
./target/debug/cloud-hypervisor \
|
||||
--kernel ~/rust-hypervisor-firmware/target/target/release/hypervisor-fw \
|
||||
--disk ~/hypervisor/images/clear-30080-kvm.img \
|
||||
--cpus 4 \
|
||||
--memory size=1024M \
|
||||
--rng \
|
||||
--log-file /tmp/ch-fw.log \
|
||||
-vvv
|
||||
```
|
||||
|
||||
After booting the guest, we then have to grep for the debug I/O port traces in
|
||||
the log file:
|
||||
|
||||
```Shell
|
||||
$ grep "Debug I/O port" /tmp/ch-fw.log
|
||||
cloud-hypervisor: 19.762449ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firmware code 0x0] 0.019004 seconds
|
||||
cloud-hypervisor: 403.499628ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firmware code 0x1] 0.402744 seconds
|
||||
```
|
||||
175
docs/networking.md
Normal file
175
docs/networking.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# How to use networking
|
||||
|
||||
cloud-hypervisor can emulate one or more virtual network interfaces, represented at the hypervisor host by [tap devices](https://www.kernel.org/doc/Documentation/networking/tuntap.txt"). This guide briefly describes, in a manual and distribution neutral way, how to setup and use networking with cloud-hypevisor.
|
||||
|
||||
## Start cloud-hypervisor with net devices
|
||||
|
||||
Use one `--net` command-line argument from cloud-hypervisor to specify the emulation of one or more virtual NIC's. The example below instructs cloud-hypervisor to emulate for instance 2 virtual NIC's:
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus 4 \
|
||||
--memory "size=512M" \
|
||||
--disk my-root-disk.img \
|
||||
--kernel my-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
|
||||
--net tap=ich0,mac=a4:a1:c2:00:00:01,ip=192.168.4.2,mask=255.255.255.0 \
|
||||
tap=ich1,mac=a4:a1:c2:00:00:02,ip=10.0.1.2,mask=255.255.255.0
|
||||
```
|
||||
|
||||
The `--net` argument takes 1 or more space-separated strings of key value pairs containing the following 4 keys or fields:
|
||||
|
||||
| Name | Purpose | Optional |
|
||||
| -------- |----------------------------| ----------|
|
||||
| tap | tap device name | Yes |
|
||||
| mac | vNIC mac address | Yes |
|
||||
| ip | tap IP IP address | yes |
|
||||
| mask | tap IP netmask | Yes |
|
||||
|
||||
## Configure the tap devices
|
||||
|
||||
After starting cloud-hypervisor as shown above, 2 tap devices with state down will become available at the host:
|
||||
|
||||
```bash
|
||||
root@host:~# ip link show ich0
|
||||
78: ich0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 72:54:12:ff:ce:6f brd ff:ff:ff:ff:ff:ff
|
||||
root@host:~# ip link show ich1
|
||||
79: ich1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 06:7a:fc:1b:9a:67 brd ff:ff:ff:ff:ff:ff
|
||||
```
|
||||
|
||||
Set the tap devices to up state:
|
||||
|
||||
```bash
|
||||
root@host:~# ip link set up ich0
|
||||
root@host:~# ip link set up ich1
|
||||
|
||||
root@host:~# ip link show ich0
|
||||
78: ich0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 72:54:12:ff:ce:6f brd ff:ff:ff:ff:ff:ff
|
||||
root@host:~# ip link show ich1
|
||||
79: ich1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 06:7a:fc:1b:9a:67 brd ff:ff:ff:ff:ff:ff
|
||||
```
|
||||
|
||||
## Connect tap devices
|
||||
|
||||
Different networking models can be used to provide external connectivity. In this example we will
|
||||
use 2 linux bridges emulating 2 different networks. The integration bridge (ich-int) in this example will also be used
|
||||
for external connectivity.
|
||||
|
||||
Create the bridges and connect the cloud-hypervisor tap devices to the bridges:
|
||||
|
||||
```bash
|
||||
root@host:~# brctl addbr ich-int
|
||||
root@host:~# brctl addbr ich-dpl
|
||||
root@host:~# ip link set up ich-int
|
||||
root@host:~# ip link set up ich-dpl
|
||||
root@host:~# brctl addif ich-int ich0
|
||||
root@host:~# brctl addif ich-dpl ich1
|
||||
root@host:~# brctl show
|
||||
bridge name bridge id STP enabled interfaces
|
||||
ich-dpl 8000.067afc1b9a67 no ich1
|
||||
ich-int 8000.725412ffce6f no ich0
|
||||
```
|
||||
This completes the layer 2 wiring: The cloud-hypervisor is now connected to the hypervisor host via the 2 linux bridges.
|
||||
|
||||
## IP (Layer 3) provisioning
|
||||
|
||||
### Hypervisor host
|
||||
|
||||
On the hypervisor host add the network gateway IP address of each network to the 2 linux bridges:
|
||||
|
||||
```bash
|
||||
root@host:~# ip addr add 192.168.4.1/24 dev ich-int
|
||||
root@host:~# ip addr add 10.0.1.1/24 dev ich-dpl
|
||||
```
|
||||
The routing table of the hypervisor host should now also have corresponding routing entries:
|
||||
|
||||
```bash
|
||||
root@host:~# route -n
|
||||
Kernel IP routing table
|
||||
Destination Gateway Genmask Flags Metric Ref Use Iface
|
||||
0.0.0.0 192.168.178.1 0.0.0.0 UG 600 0 0 wlan1
|
||||
10.0.1.0 0.0.0.0 255.255.255.0 U 0 0 0 ich-dpl
|
||||
192.168.4.0 0.0.0.0 255.255.255.0 U 0 0 0 ich-int
|
||||
192.168.178.0 0.0.0.0 255.255.255.0 U 600 0 0 wlan1
|
||||
```
|
||||
### Virtual Machine
|
||||
|
||||
Within the virtual machine set the vNIC's to up state and provision the corresponding IP addresses on the 2 vNIC's. The steps outlined below use the ip command as an example. Alternative distribution specific procedures can also apply.
|
||||
|
||||
```bash
|
||||
root@guest:~# ip link set up enp0s2
|
||||
root@guest:~# ip link set up enp0s3
|
||||
root@guest:~# ip addr add 192.168.4.2/24 dev enp0s2
|
||||
root@guest:~# ip addr add 10.0.1.2/24 dev enp0s3
|
||||
```
|
||||
|
||||
IP connectivity between the virtual machine and the hypervisor-host can be verified by sending
|
||||
ICMP requests to the hypervisor-host for the gateway IP address from within the virtual machine:
|
||||
|
||||
```bash
|
||||
root@guest:~# ping 192.168.4.1
|
||||
PING 192.168.4.1 (192.168.4.1) 56(84) bytes of data.
|
||||
64 bytes from 192.168.4.1: icmp_seq=1 ttl=64 time=0.456 ms
|
||||
64 bytes from 192.168.4.1: icmp_seq=2 ttl=64 time=0.226 ms
|
||||
root@guest:~# ping 10.0.1.1
|
||||
PING 10.0.1.1 (10.0.1.1) 56(84) bytes of data.
|
||||
64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.449 ms
|
||||
64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=0.393 ms
|
||||
```
|
||||
|
||||
The connection can now be used for instance to log into the virtual machine with
|
||||
ssh under the precondition that the machine has an ssh daemon provisioned:
|
||||
|
||||
```bash
|
||||
root@host:~# ssh root@192.168.4.2
|
||||
The authenticity of host '192.168.4.2 (192.168.4.2)' can't be established.
|
||||
ECDSA key fingerprint is SHA256:qNAUmTtDMW9pNuZARkpLQhfw+Yc1tqUDBrQp7aZGSjw.
|
||||
Are you sure you want to continue connecting (yes/no)? yes
|
||||
Warning: Permanently added '192.168.4.2' (ECDSA) to the list of known hosts.
|
||||
root@192.168.4.2's password:
|
||||
Linux cloud-hypervisor 5.2.0 #2 SMP Thu Jul 11 08:08:16 CEST 2019 x86_64
|
||||
|
||||
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
|
||||
permitted by applicable law.
|
||||
|
||||
|
||||
Last login: Fri Jul 12 13:27:56 2019 from 192.168.4.1
|
||||
root@guest:~#
|
||||
```
|
||||
|
||||
## Internet connectivity
|
||||
|
||||
To enable internet connectivity a default gw and a nameserver has to be set within
|
||||
the virtual machine:
|
||||
|
||||
```bash
|
||||
root@guest:~# ip route add default via 192.168.4.1
|
||||
root@guest:~# cat /etc/resolv.conf
|
||||
options timeout:2
|
||||
domain vallis.nl
|
||||
search vallis.nl
|
||||
nameserver 192.168.178.1
|
||||
```
|
||||
|
||||
make sure that the default gateway of the hypervisor host (in this example host 192.168.178.1 which is an adsl router) has an entry in the routing table for the 192.168.4.0/24 network otherwise IP connectivity will not work.
|
||||
|
||||
```bash
|
||||
root@guest:~# nslookup ftp.nl.debian.org
|
||||
Server: 192.168.178.1
|
||||
Address: 192.168.178.1#53
|
||||
|
||||
Non-authoritative answer:
|
||||
cdn-fastly.deb.debian.org canonical name = prod.debian.map.fastly.net.
|
||||
Name: prod.debian.map.fastly.net
|
||||
Address: 151.101.36.204
|
||||
|
||||
root@guest:~# apt-get update
|
||||
Ign:1 http://cdn-fastly.deb.debian.org/debian stretch InRelease
|
||||
Get:2 http://cdn-fastly.deb.debian.org/debian stretch Release [118 kB]
|
||||
Get:3 http://cdn-fastly.deb.debian.org/debian stretch Release.gpg [2434 B]
|
||||
Fetched 120 kB in 1s (110 kB/s)
|
||||
```
|
||||
@@ -4,14 +4,14 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
libc = ">=0.2.39"
|
||||
rand = ">=0.6.5"
|
||||
serde = ">=1.0.27"
|
||||
libc = "0.2.60"
|
||||
rand = "0.7.0"
|
||||
serde = "1.0.98"
|
||||
|
||||
net_gen = { path = "../net_gen" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = ">=1.1.0"
|
||||
pnet = "=0.22.0"
|
||||
serde_json = ">=1.0.9"
|
||||
lazy_static = "1.3.0"
|
||||
pnet = "0.22.0"
|
||||
serde_json = "1.0.40"
|
||||
|
||||
@@ -396,7 +396,9 @@ mod tests {
|
||||
// For a given interface name, this returns a tuple that contains the MAC address of the
|
||||
// interface, an object that can be used to send Ethernet frames, and a receiver of
|
||||
// Ethernet frames arriving at the specified interface.
|
||||
fn pnet_get_mac_tx_rx(ifname: String) -> (MacAddr, Box<DataLinkSender>, Box<DataLinkReceiver>) {
|
||||
fn pnet_get_mac_tx_rx(
|
||||
ifname: String,
|
||||
) -> (MacAddr, Box<dyn DataLinkSender>, Box<dyn DataLinkReceiver>) {
|
||||
let interface_name_matches = |iface: &NetworkInterface| iface.name == ifname;
|
||||
|
||||
// Find the network interface with the provided name.
|
||||
|
||||
@@ -6,11 +6,11 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
byteorder = "*"
|
||||
byteorder = "1.3.2"
|
||||
devices = { path = "../devices" }
|
||||
kvm-bindings = "0.1"
|
||||
kvm-bindings = "0.1.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = ">=0.2.39"
|
||||
log = "*"
|
||||
libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
||||
@@ -125,7 +125,7 @@ impl PciConfigIo {
|
||||
return 0xffff_ffff;
|
||||
}
|
||||
|
||||
let (bus, device, _function, register) =
|
||||
let (bus, device, function, register) =
|
||||
parse_config_address(self.config_address & !0x8000_0000);
|
||||
|
||||
// Only support one bus.
|
||||
@@ -133,6 +133,11 @@ impl PciConfigIo {
|
||||
return 0xffff_ffff;
|
||||
}
|
||||
|
||||
// Don't support multi-function devices.
|
||||
if function > 0 {
|
||||
return 0xffff_ffff;
|
||||
}
|
||||
|
||||
self.devices.get(device).map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
})
|
||||
@@ -187,8 +192,8 @@ impl BusDevice for PciConfigIo {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
// `offset` is relative to 0xcf8
|
||||
let value = match offset {
|
||||
0...3 => self.config_address,
|
||||
4...7 => self.config_space_read(),
|
||||
0..=3 => self.config_address,
|
||||
4..=7 => self.config_space_read(),
|
||||
_ => 0xffff_ffff,
|
||||
};
|
||||
|
||||
@@ -209,8 +214,8 @@ impl BusDevice for PciConfigIo {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
// `offset` is relative to 0xcf8
|
||||
match offset {
|
||||
o @ 0...3 => self.set_config_address(o, data),
|
||||
o @ 4...7 => self.config_space_write(o - 4, data),
|
||||
o @ 0..=3 => self.set_config_address(o, data),
|
||||
o @ 4..=7 => self.config_space_write(o - 4, data),
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@ const NUM_CONFIGURATION_REGISTERS: usize = 64;
|
||||
const STATUS_REG: usize = 1;
|
||||
const STATUS_REG_CAPABILITIES_USED_MASK: u32 = 0x0010_0000;
|
||||
const BAR0_REG: usize = 4;
|
||||
const ROM_BAR_REG: usize = 12;
|
||||
const BAR_IO_ADDR_MASK: u32 = 0xffff_fffc;
|
||||
const BAR_MEM_ADDR_MASK: u32 = 0xffff_fff0;
|
||||
const ROM_BAR_ADDR_MASK: u32 = 0xffff_f800;
|
||||
const NUM_BAR_REGS: usize = 6;
|
||||
const CAPABILITY_LIST_HEAD_OFFSET: usize = 0x34;
|
||||
const FIRST_CAPABILITY_OFFSET: usize = 0x40;
|
||||
@@ -249,6 +251,8 @@ pub struct PciConfiguration {
|
||||
writable_bits: [u32; NUM_CONFIGURATION_REGISTERS], // writable bits for each register.
|
||||
bar_size: [u32; NUM_BAR_REGS],
|
||||
bar_used: [bool; NUM_BAR_REGS],
|
||||
rom_bar_size: u32,
|
||||
rom_bar_used: bool,
|
||||
// Contains the byte offset and size of the last capability.
|
||||
last_capability: Option<(usize, usize)>,
|
||||
msix_cap_reg_idx: Option<usize>,
|
||||
@@ -289,6 +293,10 @@ pub enum Error {
|
||||
CapabilityEmpty,
|
||||
CapabilityLengthInvalid(usize),
|
||||
CapabilitySpaceFull(usize),
|
||||
RomBarAddressInvalid(u64, u64),
|
||||
RomBarInUse(usize),
|
||||
RomBarInvalid(usize),
|
||||
RomBarSizeInvalid(u64),
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
@@ -312,6 +320,10 @@ impl Display for Error {
|
||||
CapabilityEmpty => write!(f, "empty capabilities are invalid"),
|
||||
CapabilityLengthInvalid(l) => write!(f, "Invalid capability length {}", l),
|
||||
CapabilitySpaceFull(s) => write!(f, "capability of size {} doesn't fit", s),
|
||||
RomBarAddressInvalid(a, s) => write!(f, "address {} size {} too big", a, s),
|
||||
RomBarInUse(b) => write!(f, "rom bar {} already used", b),
|
||||
RomBarInvalid(b) => write!(f, "rom bar {} invalid, max {}", b, NUM_BAR_REGS - 1),
|
||||
RomBarSizeInvalid(s) => write!(f, "rom bar address {} not a power of two", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,6 +374,8 @@ impl PciConfiguration {
|
||||
writable_bits,
|
||||
bar_size,
|
||||
bar_used: [false; NUM_BAR_REGS],
|
||||
rom_bar_size: 0,
|
||||
rom_bar_used: false,
|
||||
last_capability: None,
|
||||
msix_cap_reg_idx: None,
|
||||
msix_config,
|
||||
@@ -376,12 +390,15 @@ impl PciConfiguration {
|
||||
/// Writes a 32bit register to `reg_idx` in the register map.
|
||||
pub fn write_reg(&mut self, reg_idx: usize, value: u32) {
|
||||
let mut mask = self.writable_bits[reg_idx];
|
||||
if reg_idx >= BAR0_REG && reg_idx < BAR0_REG + NUM_BAR_REGS {
|
||||
if reg_idx >= BAR0_REG
|
||||
&& reg_idx < BAR0_REG + NUM_BAR_REGS
|
||||
&& (value & BAR_MEM_ADDR_MASK) == BAR_MEM_ADDR_MASK
|
||||
{
|
||||
// Handle very specific case where the BAR is being written with
|
||||
// all 1's to retrieve the BAR size on next BAR reading.
|
||||
if value == 0xffff_ffff {
|
||||
mask = self.bar_size[reg_idx - 4];
|
||||
}
|
||||
mask = self.bar_size[reg_idx - 4];
|
||||
} else if reg_idx == ROM_BAR_REG && (value & ROM_BAR_ADDR_MASK) == ROM_BAR_ADDR_MASK {
|
||||
mask = self.rom_bar_size;
|
||||
}
|
||||
|
||||
if let Some(r) = self.registers.get_mut(reg_idx) {
|
||||
@@ -500,6 +517,36 @@ impl PciConfiguration {
|
||||
Ok(config.reg_idx)
|
||||
}
|
||||
|
||||
/// Adds rom expansion BAR.
|
||||
pub fn add_pci_rom_bar(&mut self, config: &PciBarConfiguration, active: u32) -> Result<usize> {
|
||||
if self.rom_bar_used {
|
||||
return Err(Error::RomBarInUse(config.reg_idx));
|
||||
}
|
||||
|
||||
if config.size.count_ones() != 1 {
|
||||
return Err(Error::RomBarSizeInvalid(config.size));
|
||||
}
|
||||
|
||||
if config.reg_idx != ROM_BAR_REG {
|
||||
return Err(Error::RomBarInvalid(config.reg_idx));
|
||||
}
|
||||
|
||||
let end_addr = config
|
||||
.addr
|
||||
.checked_add(config.size - 1)
|
||||
.ok_or_else(|| Error::RomBarAddressInvalid(config.addr, config.size))?;
|
||||
|
||||
if end_addr > u64::from(u32::max_value()) {
|
||||
return Err(Error::RomBarAddressInvalid(config.addr, config.size));
|
||||
}
|
||||
|
||||
self.registers[config.reg_idx] = (config.addr as u32) | active;
|
||||
self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK;
|
||||
self.rom_bar_size = config.size as u32;
|
||||
self.rom_bar_used = true;
|
||||
Ok(config.reg_idx)
|
||||
}
|
||||
|
||||
/// Returns the address of the given 32 bits BAR region.
|
||||
pub fn get_bar32_addr(&self, bar_num: usize) -> u32 {
|
||||
let bar_idx = BAR0_REG + bar_num;
|
||||
@@ -663,8 +710,6 @@ mod tests {
|
||||
#[derive(Clone, Copy, Default)]
|
||||
#[allow(dead_code)]
|
||||
struct TestCap {
|
||||
_vndr: u8,
|
||||
_next: u8,
|
||||
len: u8,
|
||||
foo: u8,
|
||||
}
|
||||
@@ -683,7 +728,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn add_capability() {
|
||||
let mut cfg = PciConfiguration::new(
|
||||
0x1234,
|
||||
@@ -698,18 +742,11 @@ mod tests {
|
||||
);
|
||||
|
||||
// Add two capabilities with different contents.
|
||||
let cap1 = TestCap {
|
||||
_vndr: 0,
|
||||
_next: 0,
|
||||
len: 4,
|
||||
foo: 0xAA,
|
||||
};
|
||||
let cap1 = TestCap { len: 4, foo: 0xAA };
|
||||
let cap1_offset = cfg.add_capability(&cap1).unwrap();
|
||||
assert_eq!(cap1_offset % 4, 0);
|
||||
|
||||
let cap2 = TestCap {
|
||||
_vndr: 0,
|
||||
_next: 0,
|
||||
len: 0x04,
|
||||
foo: 0x55,
|
||||
};
|
||||
|
||||
@@ -11,14 +11,14 @@ use std::fmt::{self, Display};
|
||||
use std::sync::Arc;
|
||||
use vm_allocator::SystemAllocator;
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct InterruptParameters<'a> {
|
||||
pub msix: Option<&'a MsixTableEntry>,
|
||||
}
|
||||
|
||||
pub type InterruptDelivery =
|
||||
Box<Fn(InterruptParameters) -> std::result::Result<(), std::io::Error> + Send + Sync>;
|
||||
Box<dyn Fn(InterruptParameters) -> std::result::Result<(), std::io::Error> + Send + Sync>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -70,11 +70,6 @@ pub trait PciDevice: BusDevice {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
/// Register any capabilties specified by the device.
|
||||
fn register_device_capabilities(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets a list of ioeventfds that should be registered with the running VM. The list is
|
||||
/// returned as a Vec of (eventfd, addr, datamatch) tuples.
|
||||
fn ioeventfds(&self) -> Vec<(&EventFd, u64, u64)> {
|
||||
@@ -95,6 +90,4 @@ pub trait PciDevice: BusDevice {
|
||||
/// * `addr` - The guest address inside the BAR.
|
||||
/// * `data` - The data to write.
|
||||
fn write_bar(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
|
||||
/// Invoked when the device is sandboxed.
|
||||
fn on_device_sandboxed(&mut self) {}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ pub struct MsixConfig {
|
||||
pub pba_entries: Vec<u64>,
|
||||
interrupt_cb: Option<Arc<InterruptDelivery>>,
|
||||
masked: bool,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl MsixConfig {
|
||||
@@ -70,6 +71,7 @@ impl MsixConfig {
|
||||
pba_entries,
|
||||
interrupt_cb: None,
|
||||
masked: false,
|
||||
enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,10 +83,15 @@ impl MsixConfig {
|
||||
self.masked
|
||||
}
|
||||
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
pub fn set_msg_ctl(&mut self, reg: u16) {
|
||||
let old_masked = self.masked;
|
||||
|
||||
self.masked = ((reg >> FUNCTION_MASK_BIT) & 1u16) == 1u16;
|
||||
self.enabled = ((reg >> MSIX_ENABLE_BIT) & 1u16) == 1u16;
|
||||
|
||||
// If the Function Mask bit was set, and has just been cleared, it's
|
||||
// important to go through the entire PBA to check if there was any
|
||||
@@ -111,7 +118,7 @@ impl MsixConfig {
|
||||
0x0 => self.table_entries[index].msg_addr_lo,
|
||||
0x4 => self.table_entries[index].msg_addr_hi,
|
||||
0x8 => self.table_entries[index].msg_data,
|
||||
0x10 => self.table_entries[index].vector_ctl,
|
||||
0xc => self.table_entries[index].vector_ctl,
|
||||
_ => {
|
||||
error!("invalid offset");
|
||||
0
|
||||
@@ -162,7 +169,7 @@ impl MsixConfig {
|
||||
0x0 => self.table_entries[index].msg_addr_lo = value,
|
||||
0x4 => self.table_entries[index].msg_addr_hi = value,
|
||||
0x8 => self.table_entries[index].msg_data = value,
|
||||
0x10 => {
|
||||
0xc => {
|
||||
old_entry = Some(self.table_entries[index].clone());
|
||||
self.table_entries[index].vector_ctl = value;
|
||||
}
|
||||
@@ -359,11 +366,11 @@ impl MsixCap {
|
||||
}
|
||||
|
||||
pub fn table_offset(&self) -> u32 {
|
||||
self.table >> 3
|
||||
self.table & 0xffff_fff8
|
||||
}
|
||||
|
||||
pub fn pba_offset(&self) -> u32 {
|
||||
self.pba >> 3
|
||||
self.pba & 0xffff_fff8
|
||||
}
|
||||
|
||||
pub fn table_bir(&self) -> u32 {
|
||||
|
||||
@@ -9,11 +9,11 @@ license = "BSD-3-Clause"
|
||||
path = "src/qcow.rs"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "*"
|
||||
libc = "*"
|
||||
log = "*"
|
||||
remain = "*"
|
||||
byteorder = "1.3.2"
|
||||
libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
remain = "0.1.3"
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "*"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
220
qcow/src/qcow.rs
220
qcow/src/qcow.rs
@@ -12,9 +12,12 @@ mod vec_cache;
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use libc::{EINVAL, ENOSPC, ENOTSUP};
|
||||
use remain::sorted;
|
||||
use vmm_sys_util::{FileSetLen, FileSync, PunchHole, SeekHole, WriteZeroes};
|
||||
use vmm_sys_util::{
|
||||
file_traits::FileSetLen, file_traits::FileSync, seek_hole::SeekHole, write_zeroes::PunchHole,
|
||||
write_zeroes::WriteZeroes,
|
||||
};
|
||||
|
||||
use std::cmp::min;
|
||||
use std::cmp::{max, min};
|
||||
use std::fmt::{self, Display};
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
@@ -31,18 +34,21 @@ pub enum Error {
|
||||
BackingFilesNotSupported,
|
||||
CompressedBlocksNotSupported,
|
||||
EvictingCache(io::Error),
|
||||
FileTooBig(u64),
|
||||
GettingFileSize(io::Error),
|
||||
GettingRefcount(refcount::Error),
|
||||
InvalidClusterIndex,
|
||||
InvalidClusterSize,
|
||||
InvalidIndex,
|
||||
InvalidL1TableOffset,
|
||||
InvalidL1TableSize(u32),
|
||||
InvalidMagic,
|
||||
InvalidOffset(u64),
|
||||
InvalidRefcountTableOffset,
|
||||
InvalidRefcountTableSize,
|
||||
InvalidRefcountTableSize(u64),
|
||||
NoFreeClusters,
|
||||
NoRefcountClusters,
|
||||
NotEnoughSpaceForRefcounts,
|
||||
OpeningFile(io::Error),
|
||||
ReadingData(io::Error),
|
||||
ReadingHeader(io::Error),
|
||||
@@ -50,10 +56,14 @@ pub enum Error {
|
||||
ReadingRefCountBlock(refcount::Error),
|
||||
ReadingRefCounts(io::Error),
|
||||
RebuildingRefCounts(io::Error),
|
||||
RefcountTableOffEnd,
|
||||
RefcountTableTooLarge,
|
||||
SeekingFile(io::Error),
|
||||
SettingFileSize(io::Error),
|
||||
SettingRefcountRefcount(io::Error),
|
||||
SizeTooSmallForNumberOfClusters,
|
||||
TooManyL1Entries(u64),
|
||||
TooManyRefcounts(u64),
|
||||
UnsupportedRefcountOrder,
|
||||
UnsupportedVersion(u32),
|
||||
WritingData(io::Error),
|
||||
@@ -72,18 +82,25 @@ impl Display for Error {
|
||||
BackingFilesNotSupported => write!(f, "backing files not supported"),
|
||||
CompressedBlocksNotSupported => write!(f, "compressed blocks not supported"),
|
||||
EvictingCache(e) => write!(f, "failed to evict cache: {}", e),
|
||||
FileTooBig(size) => write!(
|
||||
f,
|
||||
"file larger than max of {}: {}",
|
||||
MAX_QCOW_FILE_SIZE, size
|
||||
),
|
||||
GettingFileSize(e) => write!(f, "failed to get file size: {}", e),
|
||||
GettingRefcount(e) => write!(f, "failed to get refcount: {}", e),
|
||||
InvalidClusterIndex => write!(f, "invalid cluster index"),
|
||||
InvalidClusterSize => write!(f, "invalid cluster size"),
|
||||
InvalidIndex => write!(f, "invalid index"),
|
||||
InvalidL1TableOffset => write!(f, "invalid L1 table offset"),
|
||||
InvalidL1TableSize(size) => write!(f, "invalid L1 table size {}", size),
|
||||
InvalidMagic => write!(f, "invalid magic"),
|
||||
InvalidOffset(_) => write!(f, "invalid offset"),
|
||||
InvalidRefcountTableOffset => write!(f, "invalid refcount table offset"),
|
||||
InvalidRefcountTableSize => write!(f, "invalid refcount table size"),
|
||||
InvalidRefcountTableSize(size) => write!(f, "invalid refcount table size: {}", size),
|
||||
NoFreeClusters => write!(f, "no free clusters"),
|
||||
NoRefcountClusters => write!(f, "no refcount clusters"),
|
||||
NotEnoughSpaceForRefcounts => write!(f, "not enough space for refcounts"),
|
||||
OpeningFile(e) => write!(f, "failed to open file: {}", e),
|
||||
ReadingData(e) => write!(f, "failed to read data: {}", e),
|
||||
ReadingHeader(e) => write!(f, "failed to read header: {}", e),
|
||||
@@ -91,10 +108,14 @@ impl Display for Error {
|
||||
ReadingRefCountBlock(e) => write!(f, "failed to read ref count block: {}", e),
|
||||
ReadingRefCounts(e) => write!(f, "failed to read ref counts: {}", e),
|
||||
RebuildingRefCounts(e) => write!(f, "failed to rebuild ref counts: {}", e),
|
||||
RefcountTableOffEnd => write!(f, "refcount table offset past file end"),
|
||||
RefcountTableTooLarge => write!(f, "too many clusters specified for refcount table"),
|
||||
SeekingFile(e) => write!(f, "failed to seek file: {}", e),
|
||||
SettingFileSize(e) => write!(f, "failed to set file size: {}", e),
|
||||
SettingRefcountRefcount(e) => write!(f, "failed to set refcount refcount: {}", e),
|
||||
SizeTooSmallForNumberOfClusters => write!(f, "size too small for number of clusters"),
|
||||
TooManyL1Entries(count) => write!(f, "l1 entry table too large: {}", count),
|
||||
TooManyRefcounts(count) => write!(f, "ref count table too large: {}", count),
|
||||
UnsupportedRefcountOrder => write!(f, "unsupported refcount order"),
|
||||
UnsupportedVersion(v) => write!(f, "unsupported version: {}", v),
|
||||
WritingData(e) => write!(f, "failed to write data: {}", e),
|
||||
@@ -108,11 +129,21 @@ pub enum ImageType {
|
||||
Qcow2,
|
||||
}
|
||||
|
||||
// Maximum data size supported.
|
||||
const MAX_QCOW_FILE_SIZE: u64 = 0x01 << 44; // 16 TB.
|
||||
|
||||
// QCOW magic constant that starts the header.
|
||||
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
||||
// Default to a cluster size of 2^DEFAULT_CLUSTER_BITS
|
||||
const DEFAULT_CLUSTER_BITS: u32 = 16;
|
||||
const MAX_CLUSTER_BITS: u32 = 30;
|
||||
// Limit clusters to reasonable sizes. Choose the same limits as qemu. Making the clusters smaller
|
||||
// increases the amount of overhead for book keeping.
|
||||
const MIN_CLUSTER_BITS: u32 = 9;
|
||||
const MAX_CLUSTER_BITS: u32 = 21;
|
||||
// The L1 and RefCount table are kept in RAM, only handle files that require less than 35M entries.
|
||||
// This easily covers 1 TB files. When support for bigger files is needed the assumptions made to
|
||||
// keep these tables in RAM needs to be thrown out.
|
||||
const MAX_RAM_POINTER_TABLE_SIZE: u64 = 35_000_000;
|
||||
// Only support 2 byte refcounts, 2^refcount_order bits.
|
||||
const DEFAULT_REFCOUNT_ORDER: u32 = 4;
|
||||
|
||||
@@ -319,11 +350,15 @@ impl QcowHeader {
|
||||
}
|
||||
}
|
||||
|
||||
fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u32) -> usize {
|
||||
let refcount_bytes = (0x01u32 << refcount_order) / 8;
|
||||
let for_data = div_round_up_u32(num_clusters * refcount_bytes, cluster_size);
|
||||
let for_refcounts = div_round_up_u32(for_data * refcount_bytes, cluster_size);
|
||||
for_data as usize + for_refcounts as usize
|
||||
fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u32) -> u64 {
|
||||
// Use u64 as the product of the u32 inputs can overflow.
|
||||
let refcount_bytes = (0x01 << u64::from(refcount_order)) / 8;
|
||||
let for_data = div_round_up_u64(
|
||||
u64::from(num_clusters) * refcount_bytes,
|
||||
u64::from(cluster_size),
|
||||
);
|
||||
let for_refcounts = div_round_up_u64(for_data * refcount_bytes, u64::from(cluster_size));
|
||||
for_data + for_refcounts
|
||||
}
|
||||
|
||||
/// Represents a qcow2 file. This is a sparse file format maintained by the qemu project.
|
||||
@@ -368,14 +403,20 @@ impl QcowFile {
|
||||
return Err(Error::UnsupportedVersion(header.version));
|
||||
}
|
||||
|
||||
// Make sure that the L1 table fits in RAM.
|
||||
if u64::from(header.l1_size) > MAX_RAM_POINTER_TABLE_SIZE {
|
||||
return Err(Error::InvalidL1TableSize(header.l1_size));
|
||||
}
|
||||
|
||||
let cluster_bits: u32 = header.cluster_bits;
|
||||
if cluster_bits > MAX_CLUSTER_BITS {
|
||||
if cluster_bits < MIN_CLUSTER_BITS || cluster_bits > MAX_CLUSTER_BITS {
|
||||
return Err(Error::InvalidClusterSize);
|
||||
}
|
||||
let cluster_size = 0x01u64 << cluster_bits;
|
||||
if cluster_size < size_of::<u64>() as u64 {
|
||||
// Can't fit an offset in a cluster, nothing is going to work.
|
||||
return Err(Error::InvalidClusterSize);
|
||||
|
||||
// Limit the total size of the disk.
|
||||
if header.size > MAX_QCOW_FILE_SIZE {
|
||||
return Err(Error::FileTooBig(header.size));
|
||||
}
|
||||
|
||||
// No current support for backing files.
|
||||
@@ -398,8 +439,13 @@ impl QcowFile {
|
||||
}
|
||||
offset_is_cluster_boundary(header.backing_file_offset, header.cluster_bits)?;
|
||||
offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)?;
|
||||
offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)?;
|
||||
offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)?;
|
||||
// refcount table must be a cluster boundary, and within the file's virtual or actual size.
|
||||
offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)?;
|
||||
let file_size = file.metadata().map_err(Error::GettingFileSize)?.len();
|
||||
if header.refcount_table_offset > max(file_size, header.size) {
|
||||
return Err(Error::RefcountTableOffEnd);
|
||||
}
|
||||
|
||||
// The first cluster should always have a non-zero refcount, so if it is 0,
|
||||
// this is an old file with broken refcounts, which requires a rebuild.
|
||||
@@ -432,6 +478,9 @@ impl QcowFile {
|
||||
let num_l2_clusters = div_round_up_u64(num_clusters, l2_size);
|
||||
let l1_clusters = div_round_up_u64(num_l2_clusters, cluster_size);
|
||||
let header_clusters = div_round_up_u64(size_of::<QcowHeader>() as u64, cluster_size);
|
||||
if num_l2_clusters > MAX_RAM_POINTER_TABLE_SIZE {
|
||||
return Err(Error::TooManyL1Entries(num_l2_clusters));
|
||||
}
|
||||
let l1_table = VecCache::from_vec(
|
||||
raw_file
|
||||
.read_pointer_table(
|
||||
@@ -447,7 +496,14 @@ impl QcowFile {
|
||||
header.refcount_order,
|
||||
cluster_size as u32,
|
||||
(num_clusters + l1_clusters + num_l2_clusters + header_clusters) as u32,
|
||||
) as u64;
|
||||
);
|
||||
// Check that the given header doesn't have a suspiciously sized refcount table.
|
||||
if u64::from(header.refcount_table_clusters) > 2 * refcount_clusters {
|
||||
return Err(Error::RefcountTableTooLarge);
|
||||
}
|
||||
if l1_clusters + refcount_clusters > MAX_RAM_POINTER_TABLE_SIZE {
|
||||
return Err(Error::TooManyRefcounts(refcount_clusters));
|
||||
}
|
||||
let refcount_block_entries = cluster_size / refcount_bytes;
|
||||
let refcounts = RefCount::new(
|
||||
&mut raw_file,
|
||||
@@ -716,7 +772,7 @@ impl QcowFile {
|
||||
while refcounts[first_free_cluster as usize] != 0 {
|
||||
first_free_cluster += 1;
|
||||
if first_free_cluster >= refcounts.len() as u64 {
|
||||
return Err(Error::InvalidRefcountTableSize);
|
||||
return Err(Error::NotEnoughSpaceForRefcounts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,13 +872,13 @@ impl QcowFile {
|
||||
max_valid_cluster_index += refblock_clusters + reftable_clusters;
|
||||
max_valid_cluster_index += refblocks_for_refs + reftable_clusters_for_refs;
|
||||
|
||||
if max_valid_cluster_index > usize::max_value() as u64 {
|
||||
return Err(Error::InvalidRefcountTableSize);
|
||||
if max_valid_cluster_index > MAX_RAM_POINTER_TABLE_SIZE {
|
||||
return Err(Error::InvalidRefcountTableSize(max_valid_cluster_index));
|
||||
}
|
||||
|
||||
let max_valid_cluster_offset = max_valid_cluster_index * cluster_size;
|
||||
if max_valid_cluster_offset < file_size - cluster_size {
|
||||
return Err(Error::InvalidRefcountTableSize);
|
||||
return Err(Error::InvalidRefcountTableSize(max_valid_cluster_offset));
|
||||
}
|
||||
|
||||
let mut refcounts = vec![0; max_valid_cluster_index as usize];
|
||||
@@ -1025,20 +1081,16 @@ impl QcowFile {
|
||||
fn get_new_cluster(&mut self) -> std::io::Result<u64> {
|
||||
// First use a pre allocated cluster if one is available.
|
||||
if let Some(free_cluster) = self.avail_clusters.pop() {
|
||||
let cluster_size = self.raw_file.cluster_size() as usize;
|
||||
self.raw_file
|
||||
.file_mut()
|
||||
.seek(SeekFrom::Start(free_cluster))?;
|
||||
self.raw_file.file_mut().write_zeroes(cluster_size)?;
|
||||
self.raw_file.zero_cluster(free_cluster)?;
|
||||
return Ok(free_cluster);
|
||||
}
|
||||
|
||||
let max_valid_cluster_offset = self.refcounts.max_valid_cluster_offset();
|
||||
if let Some(new_cluster) = self.raw_file.add_cluster_end(max_valid_cluster_offset)? {
|
||||
return Ok(new_cluster);
|
||||
Ok(new_cluster)
|
||||
} else {
|
||||
error!("No free clusters in get_new_cluster()");
|
||||
return Err(std::io::Error::from_raw_os_error(ENOSPC));
|
||||
Err(std::io::Error::from_raw_os_error(ENOSPC))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1514,12 +1566,12 @@ fn offset_is_cluster_boundary(offset: u64, cluster_bits: u32) -> Result<()> {
|
||||
|
||||
// Ceiling of the division of `dividend`/`divisor`.
|
||||
fn div_round_up_u64(dividend: u64, divisor: u64) -> u64 {
|
||||
(dividend + divisor - 1) / divisor
|
||||
dividend / divisor + if dividend % divisor != 0 { 1 } else { 0 }
|
||||
}
|
||||
|
||||
// Ceiling of the division of `dividend`/`divisor`.
|
||||
fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 {
|
||||
(dividend + divisor - 1) / divisor
|
||||
dividend / divisor + if dividend % divisor != 0 { 1 } else { 0 }
|
||||
}
|
||||
|
||||
fn convert_copy<R, W>(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()>
|
||||
@@ -1693,13 +1745,37 @@ mod tests {
|
||||
]
|
||||
}
|
||||
|
||||
// Test case found by clusterfuzz to allocate excessive memory.
|
||||
fn test_huge_header() -> Vec<u8> {
|
||||
vec![
|
||||
0x51, 0x46, 0x49, 0xfb, // magic
|
||||
0x00, 0x00, 0x00, 0x03, // version
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // backing file offset
|
||||
0x00, 0x00, 0x00, 0x00, // backing file size
|
||||
0x00, 0x00, 0x00, 0x09, // cluster_bits
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, // size
|
||||
0x00, 0x00, 0x00, 0x00, // crypt method
|
||||
0x00, 0x00, 0x01, 0x00, // L1 size
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // L1 table offset
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // refcount table offset
|
||||
0x00, 0x00, 0x00, 0x03, // refcount table clusters
|
||||
0x00, 0x00, 0x00, 0x00, // nb snapshots
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // snapshots offset
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // incompatible_features
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compatible_features
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // autoclear_features
|
||||
0x00, 0x00, 0x00, 0x04, // refcount_order
|
||||
0x00, 0x00, 0x00, 0x68, // header_length
|
||||
]
|
||||
}
|
||||
|
||||
fn with_basic_file<F>(header: &[u8], mut testfn: F)
|
||||
where
|
||||
F: FnMut(File),
|
||||
{
|
||||
let mut disk_file: File = tempfile().unwrap();
|
||||
disk_file.write_all(&header).unwrap();
|
||||
disk_file.set_len(0x5_0000).unwrap();
|
||||
disk_file.set_len(0x1_0000_0000).unwrap();
|
||||
disk_file.seek(SeekFrom::Start(0)).unwrap();
|
||||
|
||||
testfn(disk_file); // File closed when the function exits.
|
||||
@@ -1770,6 +1846,90 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_cluster_bits() {
|
||||
let mut header = valid_header_v3();
|
||||
header[23] = 3;
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_huge_file() {
|
||||
let header = test_huge_header();
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_crazy_file_size_rejected() {
|
||||
let mut header = valid_header_v3();
|
||||
&mut header[24..32].copy_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1e]);
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_huge_l1_table() {
|
||||
let mut header = valid_header_v3();
|
||||
header[36] = 0x12;
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_1_tb_file_min_cluster() {
|
||||
let mut header = test_huge_header();
|
||||
header[24] = 0;
|
||||
header[26] = 1;
|
||||
header[31] = 0;
|
||||
// 1 TB with the min cluster size makes the arrays too big, it should fail.
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_1_tb_file() {
|
||||
let mut header = test_huge_header();
|
||||
// reset to 1 TB size.
|
||||
header[24] = 0;
|
||||
header[26] = 1;
|
||||
header[31] = 0;
|
||||
// set cluster_bits
|
||||
header[23] = 16;
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
let mut qcow = QcowFile::from(disk_file).expect("Failed to create file.");
|
||||
qcow.seek(SeekFrom::Start(0x100_0000_0000 - 8))
|
||||
.expect("Failed to seek.");
|
||||
let value = 0x0000_0040_3f00_ffffu64;
|
||||
qcow.write_all(&value.to_le_bytes())
|
||||
.expect("failed to write data");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_huge_num_refcounts() {
|
||||
let mut header = valid_header_v3();
|
||||
&mut header[56..60].copy_from_slice(&[0x02, 0x00, 0xe8, 0xff]);
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount clusters");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_huge_refcount_offset() {
|
||||
let mut header = valid_header_v3();
|
||||
&mut header[48..56].copy_from_slice(&[0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00]);
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount offset");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_read_start() {
|
||||
with_basic_file(&valid_header_v3(), |disk_file: File| {
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::io::{self, BufWriter, Seek, SeekFrom};
|
||||
use std::mem::size_of;
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use vmm_sys_util::write_zeroes::WriteZeroes;
|
||||
|
||||
/// A qcow file. Allows reading/writing clusters and appending clusters.
|
||||
#[derive(Debug)]
|
||||
@@ -133,6 +134,14 @@ impl QcowRawFile {
|
||||
pub fn cluster_offset(&self, address: u64) -> u64 {
|
||||
address & self.cluster_mask
|
||||
}
|
||||
|
||||
/// Zeros out a cluster in the file.
|
||||
pub fn zero_cluster(&mut self, address: u64) -> io::Result<()> {
|
||||
let cluster_size = self.cluster_size as usize;
|
||||
self.file.seek(SeekFrom::Start(address))?;
|
||||
self.file.write_zeroes(cluster_size)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for QcowRawFile {
|
||||
|
||||
@@ -1,3 +1,74 @@
|
||||
- [v0.2.0](#v020)
|
||||
- [Network device offloading](#network-device-offloading)
|
||||
- [Minimal hardware-reduced ACPI](#minimal-hardware-reduced-acpi)
|
||||
- [Debug I/O port](#debug-io-port)
|
||||
- [Improved direct device assignment](#improved-direct-device-assignment)
|
||||
- [Improved shared filesystem](#improved-shared-filesystem)
|
||||
- [Ubuntu bionic based CI](#ubuntu-bionic-based-ci)
|
||||
- [v0.1.0](#v010)
|
||||
- [Shared filesystem](#shared-filesystem)
|
||||
- [Initial direct device assignment support](#initial-direct-device-assignment-support)
|
||||
- [Userspace IOAPIC](#userspace-ioapic)
|
||||
- [Virtual persistent memory](#virtual-persistent-memory)
|
||||
- [Linux kernel bzImage](#linux-kernel-bzimage)
|
||||
- [Console over virtio](#console-over-virtio)
|
||||
- [Unit testing](#unit-testing)
|
||||
- [Integration tests parallelization](#integration-tests-parallelization)
|
||||
|
||||
# v0.2.0
|
||||
|
||||
This release has been tracked through the [0.2.0 project](https://github.com/intel/cloud-hypervisor/projects/2).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.2.0 include:
|
||||
|
||||
### Network device offloading
|
||||
|
||||
As part of our general effort to offload paravirtualized I/O to external
|
||||
processes, we added support for
|
||||
[vhost-user-net](https://access.redhat.com/solutions/3394851) backends. This
|
||||
enables `cloud-hypervisor` users to plug a `vhost-user` based networking device
|
||||
(e.g. [DPDK](https://dpdk.org)) into the VMM as their virtio network backend.
|
||||
|
||||
### Minimal hardware-reduced ACPI
|
||||
|
||||
In order to properly implement and guest reset and shutdown, we implemented
|
||||
a minimal version of the hardware-reduced ACPI specification. Together with
|
||||
a tiny I/O port based ACPI device, this allows `cloud-hypervisor` guests to
|
||||
cleanly reboot and shutdown.
|
||||
|
||||
The ACPI implementation is a `cloud-hypervisor` build time option that is
|
||||
enabled by default.
|
||||
|
||||
### Debug I/O port
|
||||
|
||||
Based on the Firecracker idea of using a dedicated I/O port to measure guest
|
||||
boot times, we added support for logging guest events through the
|
||||
[0x80](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
|
||||
PC debug port. This allows, among other things, for granular guest boot time
|
||||
measurements. See our [debug port documentation](https://github.com/intel/cloud-hypervisor/blob/master/docs/debug-port.md)
|
||||
for more details.
|
||||
|
||||
### Improved direct device assignment
|
||||
|
||||
We fixed a major performance issue with our initial VFIO implementation: When
|
||||
enabling VT-d through the KVM and VFIO APIs, our guest memory writes and reads
|
||||
were (in many cases) not cached. After correctly tagging the guest memory from
|
||||
`cloud-hypervisor` we're now able to reach the expected performance from
|
||||
directly assigned devices.
|
||||
|
||||
### Improved shared filesystem
|
||||
|
||||
We added shared memory region with [DAX](https://www.kernel.org/doc/Documentation/filesystems/dax.txt)
|
||||
support to our [virtio-fs](https://virtio-fs.gitlab.io/) shared file system.
|
||||
This provides better shared filesystem IO performance with a smaller guest
|
||||
memory footprint.
|
||||
|
||||
### Ubuntu bionic based CI
|
||||
|
||||
Thanks to our [simple KVM firmware](https://github.com/intel/rust-hypervisor-firmware)
|
||||
improvements, we are now able to boot Ubuntu bionic images. We added those to
|
||||
our CI pipeline.
|
||||
|
||||
# v0.1.0
|
||||
|
||||
This release has been tracked through the [0.1.0 project](https://github.com/intel/cloud-hypervisor/projects/1).
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
#
|
||||
# Automatically generated file; DO NOT EDIT.
|
||||
# Linux/x86 5.2.0-rc5 Kernel Configuration
|
||||
# Linux/x86 5.3.0-rc3 Kernel Configuration
|
||||
#
|
||||
|
||||
#
|
||||
# Compiler: gcc (Ubuntu 7.4.0-1ubuntu1~18.04) 7.4.0
|
||||
# Compiler: gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
|
||||
#
|
||||
CONFIG_CC_IS_GCC=y
|
||||
CONFIG_GCC_VERSION=70400
|
||||
CONFIG_CLANG_VERSION=0
|
||||
CONFIG_CC_CAN_LINK=y
|
||||
CONFIG_CC_HAS_ASM_GOTO=y
|
||||
CONFIG_CC_HAS_WARN_MAYBE_UNINITIALIZED=y
|
||||
CONFIG_IRQ_WORK=y
|
||||
@@ -20,6 +21,7 @@ CONFIG_THREAD_INFO_IN_TASK=y
|
||||
#
|
||||
CONFIG_INIT_ENV_ARG_LIMIT=32
|
||||
# CONFIG_COMPILE_TEST is not set
|
||||
# CONFIG_HEADER_TEST is not set
|
||||
CONFIG_LOCALVERSION=""
|
||||
# CONFIG_LOCALVERSION_AUTO is not set
|
||||
CONFIG_BUILD_SALT=""
|
||||
@@ -128,6 +130,12 @@ CONFIG_LOG_BUF_SHIFT=21
|
||||
CONFIG_LOG_CPU_MAX_BUF_SHIFT=12
|
||||
CONFIG_PRINTK_SAFE_LOG_BUF_SHIFT=13
|
||||
CONFIG_HAVE_UNSTABLE_SCHED_CLOCK=y
|
||||
|
||||
#
|
||||
# Scheduler features
|
||||
#
|
||||
# end of Scheduler features
|
||||
|
||||
CONFIG_ARCH_SUPPORTS_NUMA_BALANCING=y
|
||||
CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH=y
|
||||
CONFIG_ARCH_SUPPORTS_INT128=y
|
||||
@@ -140,7 +148,6 @@ CONFIG_MEMCG_SWAP=y
|
||||
CONFIG_MEMCG_SWAP_ENABLED=y
|
||||
CONFIG_MEMCG_KMEM=y
|
||||
CONFIG_BLK_CGROUP=y
|
||||
# CONFIG_DEBUG_BLK_CGROUP is not set
|
||||
CONFIG_CGROUP_WRITEBACK=y
|
||||
CONFIG_CGROUP_SCHED=y
|
||||
CONFIG_FAIR_GROUP_SCHED=y
|
||||
@@ -255,7 +262,6 @@ CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK=y
|
||||
CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK=y
|
||||
CONFIG_ARCH_HIBERNATION_POSSIBLE=y
|
||||
CONFIG_ARCH_SUSPEND_POSSIBLE=y
|
||||
CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y
|
||||
CONFIG_ARCH_WANT_GENERAL_HUGETLB=y
|
||||
CONFIG_ZONE_DMA32=y
|
||||
CONFIG_AUDIT_ARCH=y
|
||||
@@ -294,6 +300,7 @@ CONFIG_KVM_DEBUG_FS=y
|
||||
CONFIG_PARAVIRT_TIME_ACCOUNTING=y
|
||||
CONFIG_PARAVIRT_CLOCK=y
|
||||
# CONFIG_JAILHOUSE_GUEST is not set
|
||||
# CONFIG_ACRN_GUEST is not set
|
||||
# CONFIG_MK8 is not set
|
||||
# CONFIG_MPSC is not set
|
||||
# CONFIG_MCORE2 is not set
|
||||
@@ -310,6 +317,7 @@ CONFIG_CPU_SUP_INTEL=y
|
||||
CONFIG_CPU_SUP_AMD=y
|
||||
CONFIG_CPU_SUP_HYGON=y
|
||||
CONFIG_CPU_SUP_CENTAUR=y
|
||||
CONFIG_CPU_SUP_ZHAOXIN=y
|
||||
CONFIG_HPET_TIMER=y
|
||||
CONFIG_DMI=y
|
||||
# CONFIG_GART_IOMMU is not set
|
||||
@@ -399,6 +407,7 @@ CONFIG_HOTPLUG_CPU=y
|
||||
# CONFIG_BOOTPARAM_HOTPLUG_CPU0 is not set
|
||||
# CONFIG_DEBUG_HOTPLUG_CPU0 is not set
|
||||
CONFIG_LEGACY_VSYSCALL_EMULATE=y
|
||||
# CONFIG_LEGACY_VSYSCALL_XONLY is not set
|
||||
# CONFIG_LEGACY_VSYSCALL_NONE is not set
|
||||
# CONFIG_CMDLINE_BOOL is not set
|
||||
CONFIG_MODIFY_LDT_SYSCALL=y
|
||||
@@ -541,8 +550,6 @@ CONFIG_AMD_NB=y
|
||||
# CONFIG_X86_X32 is not set
|
||||
# end of Binary Emulations
|
||||
|
||||
CONFIG_HAVE_GENERIC_GUP=y
|
||||
|
||||
#
|
||||
# Firmware Drivers
|
||||
#
|
||||
@@ -551,7 +558,6 @@ CONFIG_FIRMWARE_MEMMAP=y
|
||||
CONFIG_DMIID=y
|
||||
CONFIG_DMI_SYSFS=y
|
||||
CONFIG_DMI_SCAN_MACHINE_NON_EFI_FALLBACK=y
|
||||
# CONFIG_ISCSI_IBFT_FIND is not set
|
||||
# CONFIG_FW_CFG_SYSFS is not set
|
||||
# CONFIG_GOOGLE_FIRMWARE is not set
|
||||
|
||||
@@ -640,6 +646,7 @@ CONFIG_HAVE_MOVE_PMD=y
|
||||
CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE=y
|
||||
CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD=y
|
||||
CONFIG_HAVE_ARCH_HUGE_VMAP=y
|
||||
CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y
|
||||
CONFIG_HAVE_ARCH_SOFT_DIRTY=y
|
||||
CONFIG_HAVE_MOD_ARCH_SPECIFIC=y
|
||||
CONFIG_MODULES_USE_ELF_RELA=y
|
||||
@@ -766,6 +773,7 @@ CONFIG_SPARSEMEM_EXTREME=y
|
||||
CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y
|
||||
CONFIG_SPARSEMEM_VMEMMAP=y
|
||||
CONFIG_HAVE_MEMBLOCK_NODE_MAP=y
|
||||
CONFIG_HAVE_FAST_GUP=y
|
||||
CONFIG_MEMORY_ISOLATION=y
|
||||
CONFIG_HAVE_BOOTMEM_INFO_NODE=y
|
||||
CONFIG_MEMORY_HOTPLUG=y
|
||||
@@ -802,10 +810,11 @@ CONFIG_ZSMALLOC_STAT=y
|
||||
CONFIG_GENERIC_EARLY_IOREMAP=y
|
||||
# CONFIG_DEFERRED_STRUCT_PAGE_INIT is not set
|
||||
# CONFIG_IDLE_PAGE_TRACKING is not set
|
||||
CONFIG_ARCH_HAS_ZONE_DEVICE=y
|
||||
# CONFIG_ZONE_DEVICE is not set
|
||||
CONFIG_ARCH_HAS_HMM_MIRROR=y
|
||||
CONFIG_ARCH_HAS_HMM_DEVICE=y
|
||||
CONFIG_ARCH_HAS_PTE_DEVMAP=y
|
||||
CONFIG_ZONE_DEVICE=y
|
||||
CONFIG_DEV_PAGEMAP_OPS=y
|
||||
# CONFIG_HMM_MIRROR is not set
|
||||
# CONFIG_DEVICE_PRIVATE is not set
|
||||
CONFIG_PERCPU_STATS=y
|
||||
# CONFIG_GUP_BENCHMARK is not set
|
||||
CONFIG_ARCH_HAS_PTE_SPECIAL=y
|
||||
@@ -965,6 +974,7 @@ CONFIG_PCI_LOCKLESS_CONFIG=y
|
||||
# CONFIG_PCI_IOV is not set
|
||||
# CONFIG_PCI_PRI is not set
|
||||
# CONFIG_PCI_PASID is not set
|
||||
# CONFIG_PCI_P2PDMA is not set
|
||||
CONFIG_PCI_LABEL=y
|
||||
# CONFIG_HOTPLUG_PCI is not set
|
||||
|
||||
@@ -1016,9 +1026,11 @@ CONFIG_PREVENT_FIRMWARE_BUILD=y
|
||||
# Firmware loader
|
||||
#
|
||||
CONFIG_FW_LOADER=y
|
||||
CONFIG_FW_LOADER_PAGED_BUF=y
|
||||
CONFIG_EXTRA_FIRMWARE=""
|
||||
CONFIG_FW_LOADER_USER_HELPER=y
|
||||
# CONFIG_FW_LOADER_USER_HELPER_FALLBACK is not set
|
||||
# CONFIG_FW_LOADER_COMPRESS is not set
|
||||
# end of Firmware loader
|
||||
|
||||
CONFIG_ALLOW_DEV_COREDUMP=y
|
||||
@@ -1093,6 +1105,7 @@ CONFIG_VIRTIO_BLK=y
|
||||
# CONFIG_HP_ILO is not set
|
||||
# CONFIG_SRAM is not set
|
||||
# CONFIG_PCI_ENDPOINT_TEST is not set
|
||||
# CONFIG_XILINX_SDFEC is not set
|
||||
# CONFIG_PVPANIC is not set
|
||||
# CONFIG_C2PORT is not set
|
||||
|
||||
@@ -1710,6 +1723,8 @@ CONFIG_DMA_ACPI=y
|
||||
CONFIG_DW_DMAC_CORE=y
|
||||
# CONFIG_DW_DMAC is not set
|
||||
# CONFIG_DW_DMAC_PCI is not set
|
||||
# CONFIG_DW_EDMA is not set
|
||||
# CONFIG_DW_EDMA_PCIE is not set
|
||||
CONFIG_HSU_DMA=y
|
||||
|
||||
#
|
||||
@@ -1867,12 +1882,10 @@ CONFIG_IOMMU_SUPPORT=y
|
||||
#
|
||||
# IRQ chip support
|
||||
#
|
||||
CONFIG_ARM_GIC_MAX_NR=1
|
||||
# end of IRQ chip support
|
||||
|
||||
# CONFIG_IPACK_BUS is not set
|
||||
# CONFIG_RESET_CONTROLLER is not set
|
||||
# CONFIG_FMC is not set
|
||||
|
||||
#
|
||||
# PHY Subsystem
|
||||
@@ -1906,6 +1919,9 @@ CONFIG_ND_BLK=y
|
||||
CONFIG_ND_CLAIM=y
|
||||
CONFIG_ND_BTT=y
|
||||
CONFIG_BTT=y
|
||||
CONFIG_ND_PFN=y
|
||||
CONFIG_NVDIMM_PFN=y
|
||||
CONFIG_NVDIMM_DAX=y
|
||||
CONFIG_DAX_DRIVER=y
|
||||
CONFIG_DAX=y
|
||||
# CONFIG_DEV_DAX is not set
|
||||
@@ -1950,7 +1966,8 @@ CONFIG_FS_MBCACHE=y
|
||||
# CONFIG_BTRFS_FS is not set
|
||||
# CONFIG_NILFS2_FS is not set
|
||||
# CONFIG_F2FS_FS is not set
|
||||
# CONFIG_FS_DAX is not set
|
||||
CONFIG_FS_DAX=y
|
||||
CONFIG_FS_DAX_PMD=y
|
||||
CONFIG_FS_POSIX_ACL=y
|
||||
CONFIG_EXPORTFS=y
|
||||
# CONFIG_EXPORTFS_BLOCK_OPS is not set
|
||||
@@ -2014,6 +2031,7 @@ CONFIG_PROC_KCORE=y
|
||||
CONFIG_PROC_SYSCTL=y
|
||||
CONFIG_PROC_PAGE_MONITOR=y
|
||||
CONFIG_PROC_CHILDREN=y
|
||||
CONFIG_PROC_PID_ARCH_STATUS=y
|
||||
CONFIG_KERNFS=y
|
||||
CONFIG_SYSFS=y
|
||||
CONFIG_TMPFS=y
|
||||
@@ -2088,6 +2106,7 @@ CONFIG_NLS_UTF8=y
|
||||
# Security options
|
||||
#
|
||||
CONFIG_KEYS=y
|
||||
# CONFIG_KEYS_REQUEST_CACHE is not set
|
||||
CONFIG_PERSISTENT_KEYRINGS=y
|
||||
# CONFIG_BIG_KEYS is not set
|
||||
# CONFIG_ENCRYPTED_KEYS is not set
|
||||
@@ -2111,6 +2130,8 @@ CONFIG_LSM="yama,loadpin,safesetid,integrity"
|
||||
# Memory initialization
|
||||
#
|
||||
CONFIG_INIT_STACK_NONE=y
|
||||
# CONFIG_INIT_ON_ALLOC_DEFAULT_ON is not set
|
||||
# CONFIG_INIT_ON_FREE_DEFAULT_ON is not set
|
||||
# end of Memory initialization
|
||||
# end of Kernel hardening options
|
||||
# end of Security options
|
||||
@@ -2144,7 +2165,6 @@ CONFIG_CRYPTO_GF128MUL=y
|
||||
CONFIG_CRYPTO_NULL=y
|
||||
CONFIG_CRYPTO_NULL2=y
|
||||
# CONFIG_CRYPTO_PCRYPT is not set
|
||||
CONFIG_CRYPTO_WORKQUEUE=y
|
||||
CONFIG_CRYPTO_CRYPTD=y
|
||||
# CONFIG_CRYPTO_AUTHENC is not set
|
||||
CONFIG_CRYPTO_SIMD=y
|
||||
@@ -2210,6 +2230,7 @@ CONFIG_CRYPTO_CRC32C=y
|
||||
CONFIG_CRYPTO_CRC32C_INTEL=y
|
||||
# CONFIG_CRYPTO_CRC32 is not set
|
||||
# CONFIG_CRYPTO_CRC32_PCLMUL is not set
|
||||
# CONFIG_CRYPTO_XXHASH is not set
|
||||
CONFIG_CRYPTO_CRCT10DIF=y
|
||||
# CONFIG_CRYPTO_CRCT10DIF_PCLMUL is not set
|
||||
CONFIG_CRYPTO_GHASH=y
|
||||
@@ -2243,6 +2264,7 @@ CONFIG_CRYPTO_AES=y
|
||||
CONFIG_CRYPTO_AES_X86_64=y
|
||||
CONFIG_CRYPTO_AES_NI_INTEL=y
|
||||
# CONFIG_CRYPTO_ANUBIS is not set
|
||||
CONFIG_CRYPTO_LIB_ARC4=y
|
||||
CONFIG_CRYPTO_ARC4=y
|
||||
# CONFIG_CRYPTO_BLOWFISH is not set
|
||||
# CONFIG_CRYPTO_BLOWFISH_X86_64 is not set
|
||||
@@ -2354,15 +2376,18 @@ CONFIG_HAS_DMA=y
|
||||
CONFIG_NEED_SG_DMA_LENGTH=y
|
||||
CONFIG_NEED_DMA_MAP_STATE=y
|
||||
CONFIG_ARCH_DMA_ADDR_T_64BIT=y
|
||||
CONFIG_ARCH_HAS_FORCE_DMA_UNENCRYPTED=y
|
||||
CONFIG_SWIOTLB=y
|
||||
# CONFIG_DMA_API_DEBUG is not set
|
||||
CONFIG_SGL_ALLOC=y
|
||||
CONFIG_CPU_RMAP=y
|
||||
CONFIG_DQL=y
|
||||
CONFIG_NLATTR=y
|
||||
# CONFIG_DDR is not set
|
||||
CONFIG_IRQ_POLL=y
|
||||
# CONFIG_DIMLIB is not set
|
||||
CONFIG_UCS2_STRING=y
|
||||
CONFIG_HAVE_GENERIC_VDSO=y
|
||||
CONFIG_GENERIC_GETTIMEOFDAY=y
|
||||
CONFIG_FONT_SUPPORT=y
|
||||
# CONFIG_FONTS is not set
|
||||
CONFIG_FONT_8x8=y
|
||||
@@ -2400,7 +2425,7 @@ CONFIG_STRIP_ASM_SYMS=y
|
||||
# CONFIG_READABLE_ASM is not set
|
||||
CONFIG_UNUSED_SYMBOLS=y
|
||||
CONFIG_DEBUG_FS=y
|
||||
# CONFIG_HEADERS_CHECK is not set
|
||||
# CONFIG_HEADERS_INSTALL is not set
|
||||
CONFIG_OPTIMIZE_INLINING=y
|
||||
CONFIG_DEBUG_SECTION_MISMATCH=y
|
||||
CONFIG_SECTION_MISMATCH_WARN_ONLY=y
|
||||
@@ -2526,6 +2551,7 @@ CONFIG_RUNTIME_TESTING_MENU=y
|
||||
# CONFIG_TEST_SORT is not set
|
||||
# CONFIG_BACKTRACE_SELF_TEST is not set
|
||||
# CONFIG_RBTREE_TEST is not set
|
||||
# CONFIG_REED_SOLOMON_TEST is not set
|
||||
# CONFIG_INTERVAL_TREE_TEST is not set
|
||||
# CONFIG_ATOMIC64_SELFTEST is not set
|
||||
# CONFIG_TEST_HEXDUMP is not set
|
||||
@@ -2547,6 +2573,7 @@ CONFIG_RUNTIME_TESTING_MENU=y
|
||||
# CONFIG_TEST_UDELAY is not set
|
||||
# CONFIG_TEST_MEMCAT_P is not set
|
||||
# CONFIG_TEST_STACKINIT is not set
|
||||
# CONFIG_TEST_MEMINIT is not set
|
||||
# CONFIG_MEMTEST is not set
|
||||
CONFIG_BUG_ON_DATA_CORRUPTION=y
|
||||
# CONFIG_SAMPLES is not set
|
||||
@@ -2569,15 +2596,10 @@ CONFIG_EARLY_PRINTK=y
|
||||
CONFIG_DOUBLEFAULT=y
|
||||
# CONFIG_DEBUG_TLBFLUSH is not set
|
||||
CONFIG_HAVE_MMIOTRACE_SUPPORT=y
|
||||
CONFIG_IO_DELAY_TYPE_0X80=0
|
||||
CONFIG_IO_DELAY_TYPE_0XED=1
|
||||
CONFIG_IO_DELAY_TYPE_UDELAY=2
|
||||
CONFIG_IO_DELAY_TYPE_NONE=3
|
||||
CONFIG_IO_DELAY_0X80=y
|
||||
# CONFIG_IO_DELAY_0XED is not set
|
||||
# CONFIG_IO_DELAY_UDELAY is not set
|
||||
# CONFIG_IO_DELAY_NONE is not set
|
||||
CONFIG_DEFAULT_IO_DELAY_TYPE=0
|
||||
# CONFIG_DEBUG_BOOT_PARAMS is not set
|
||||
# CONFIG_CPA_DEBUG is not set
|
||||
# CONFIG_DEBUG_ENTRY is not set
|
||||
13
scripts/create-cloud-init.sh
Executable file
13
scripts/create-cloud-init.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
set -x
|
||||
|
||||
rm /tmp/clear-cloudinit.img
|
||||
mkdosfs -n config-2 -C /tmp/clear-cloudinit.img 8192
|
||||
mcopy -oi /tmp/clear-cloudinit.img -s test_data/cloud-init/clear/openstack ::
|
||||
|
||||
rm /tmp/ubuntu-cloudinit.img
|
||||
mkdosfs -n cidata -C /tmp/ubuntu-cloudinit.img 8192
|
||||
mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/user-data ::
|
||||
mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/meta-data ::
|
||||
mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/network-config ::
|
||||
|
||||
@@ -22,21 +22,38 @@ if [ ! -f "$OVMF" ]; then
|
||||
popd
|
||||
fi
|
||||
|
||||
OS_IMAGE_NAME="clear-29810-cloud.img"
|
||||
OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$OS_IMAGE_NAME.xz"
|
||||
OS_IMAGE="$WORKLOADS_DIR/$OS_IMAGE_NAME"
|
||||
if [ ! -f "$OS_IMAGE" ]; then
|
||||
CLEAR_OS_IMAGE_NAME="clear-29810-cloud.img"
|
||||
CLEAR_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$CLEAR_OS_IMAGE_NAME.xz"
|
||||
CLEAR_OS_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_IMAGE_NAME"
|
||||
if [ ! -f "$CLEAR_OS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
wget --quiet $OS_IMAGE_URL
|
||||
unxz $OS_IMAGE_NAME.xz
|
||||
wget --quiet $CLEAR_OS_IMAGE_URL
|
||||
unxz $CLEAR_OS_IMAGE_NAME.xz
|
||||
popd
|
||||
fi
|
||||
|
||||
OS_RAW_IMAGE_NAME="clear-29810-cloud-raw.img"
|
||||
OS_RAW_IMAGE="$WORKLOADS_DIR/$OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$OS_RAW_IMAGE" ]; then
|
||||
CLEAR_OS_RAW_IMAGE_NAME="clear-29810-cloud-raw.img"
|
||||
CLEAR_OS_RAW_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$CLEAR_OS_RAW_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
qemu-img convert -p -f qcow2 -O raw $OS_IMAGE_NAME $OS_RAW_IMAGE_NAME
|
||||
qemu-img convert -p -f qcow2 -O raw $CLEAR_OS_IMAGE_NAME $CLEAR_OS_RAW_IMAGE_NAME
|
||||
popd
|
||||
fi
|
||||
|
||||
BIONIC_OS_IMAGE_NAME="bionic-server-cloudimg-amd64.img"
|
||||
BIONIC_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$BIONIC_OS_IMAGE_NAME"
|
||||
BIONIC_OS_IMAGE="$WORKLOADS_DIR/$BIONIC_OS_IMAGE_NAME"
|
||||
if [ ! -f "$BIONIC_OS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
wget --quiet $BIONIC_OS_IMAGE_URL
|
||||
popd
|
||||
fi
|
||||
|
||||
BIONIC_OS_RAW_IMAGE_NAME="bionic-server-cloudimg-amd64-raw.img"
|
||||
BIONIC_OS_RAW_IMAGE="$WORKLOADS_DIR/$BIONIC_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$BIONIC_OS_RAW_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
qemu-img convert -p -f qcow2 -O raw $BIONIC_OS_IMAGE_NAME $BIONIC_OS_RAW_IMAGE_NAME
|
||||
popd
|
||||
fi
|
||||
|
||||
@@ -50,24 +67,31 @@ LINUX_CUSTOM_DIR="linux-custom"
|
||||
if [ ! -f "$VMLINUX_IMAGE" ]; then
|
||||
SRCDIR=$PWD
|
||||
pushd $WORKLOADS_DIR
|
||||
git clone --depth 1 "https://github.com/sboeuf/linux.git" -b "virtio-pmem_and_virtio-fs" $LINUX_CUSTOM_DIR
|
||||
git clone --depth 1 "https://github.com/sboeuf/linux.git" -b "virtio-fs" $LINUX_CUSTOM_DIR
|
||||
pushd $LINUX_CUSTOM_DIR
|
||||
cp $SRCDIR/resources/linux-virtio-pmem-and-virtio-fs-config .config
|
||||
cp $SRCDIR/resources/linux-virtio-fs-config .config
|
||||
make bzImage -j `nproc`
|
||||
cp vmlinux $VMLINUX_IMAGE
|
||||
cp arch/x86/boot/bzImage $BZIMAGE_IMAGE
|
||||
popd
|
||||
rm -r $LINUX_CUSTOM_DIR
|
||||
rm -rf $LINUX_CUSTOM_DIR
|
||||
popd
|
||||
fi
|
||||
|
||||
VIRTIOFSD_URL="$(curl --silent https://api.github.com/repos/intel/nemu/releases/latest | grep "browser_download_url" | grep "virtiofsd-x86_64" | grep -o 'https://.*[^ "]')"
|
||||
VIRTIOFSD="$WORKLOADS_DIR/virtiofsd"
|
||||
if [ ! -f "$VIRTIOFSD" ]; then
|
||||
VUBRIDGE="$WORKLOADS_DIR/vubridge"
|
||||
QEMU_DIR="qemu_build"
|
||||
if [ ! -f "$VIRTIOFSD" ] || [ ! -f "$VUBRIDGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
wget --quiet $VIRTIOFSD_URL -O "virtiofsd"
|
||||
chmod +x "virtiofsd"
|
||||
sudo setcap cap_sys_admin+epi "virtiofsd"
|
||||
git clone --depth 1 "https://github.com/sboeuf/qemu.git" -b "virtio-fs" $QEMU_DIR
|
||||
pushd $QEMU_DIR
|
||||
./configure --prefix=$PWD --target-list=x86_64-softmmu
|
||||
make virtiofsd tests/vhost-user-bridge -j `nproc`
|
||||
cp virtiofsd $VIRTIOFSD
|
||||
cp tests/vhost-user-bridge $VUBRIDGE
|
||||
popd
|
||||
rm -rf $QEMU_DIR
|
||||
sudo setcap cap_dac_override,cap_sys_admin+epi "virtiofsd"
|
||||
popd
|
||||
fi
|
||||
|
||||
@@ -81,7 +105,7 @@ fi
|
||||
VFIO_DIR="$WORKLOADS_DIR/vfio"
|
||||
if [ ! -d "$VFIO_DIR" ]; then
|
||||
mkdir -p $VFIO_DIR
|
||||
cp $OS_IMAGE $VFIO_DIR
|
||||
cp $CLEAR_OS_IMAGE $VFIO_DIR
|
||||
cp $FW $VFIO_DIR
|
||||
cp $VMLINUX_IMAGE $VFIO_DIR
|
||||
fi
|
||||
@@ -110,10 +134,14 @@ sudo setcap cap_net_admin+ep /usr/bin/qemu-system-x86_64
|
||||
|
||||
sudo adduser $USER kvm
|
||||
newgrp kvm << EOF
|
||||
export RUST_BACKTRACE=1
|
||||
cargo test --features "integration_tests"
|
||||
EOF
|
||||
RES=$?
|
||||
|
||||
# Tear VFIO test network down
|
||||
sudo ip link del vfio-br0
|
||||
sudo ip link del vfio-tap0
|
||||
sudo ip link del vfio-tap1
|
||||
|
||||
exit $RES
|
||||
|
||||
@@ -13,8 +13,12 @@ pushd target/debug
|
||||
ls | grep net_util | grep -v "\.d" | xargs -n 1 sudo setcap cap_net_admin,cap_net_raw+ep
|
||||
popd
|
||||
|
||||
for f in $(find . -name Cargo.toml -printf '%h\n' | sort -u); do
|
||||
pushd $f > /dev/null;
|
||||
sudo adduser $USER kvm
|
||||
newgrp kvm << EOF || exit 1
|
||||
for f in \$(find . -name Cargo.toml -printf '%h\n' | sort -u); do
|
||||
pushd \$f > /dev/null;
|
||||
export RUST_BACKTRACE=1
|
||||
cargo test || exit 1;
|
||||
popd > /dev/null;
|
||||
done
|
||||
EOF
|
||||
|
||||
1537
src/main.rs
1537
src/main.rs
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
#cloud-config
|
||||
users:
|
||||
- name: admin
|
||||
- name: cloud
|
||||
passwd: $6$7125787751a8d18a$sHwGySomUA1PawiNFWVCKYQN.Ec.Wzz0JtPPL1MvzFrkwmop2dq7.4CYf03A5oemPQ4pOFCCrtCelvFBEle/K.
|
||||
sudo:
|
||||
- ALL=(ALL) NOPASSWD:ALL
|
||||
2
test_data/cloud-init/ubuntu/meta-data
Executable file
2
test_data/cloud-init/ubuntu/meta-data
Executable file
@@ -0,0 +1,2 @@
|
||||
instance-id: cloud
|
||||
local-hostname: cloud
|
||||
12
test_data/cloud-init/ubuntu/network-config
Normal file
12
test_data/cloud-init/ubuntu/network-config
Normal file
@@ -0,0 +1,12 @@
|
||||
network:
|
||||
version: 1
|
||||
config:
|
||||
- type: physical
|
||||
name: eth0
|
||||
mac_address: 12:34:56:78:90:ab
|
||||
subnets:
|
||||
- type: static
|
||||
address: 192.168.2.2/24
|
||||
gateway: 192.168.2.1
|
||||
dns_nameservers:
|
||||
- 192.168.2.1
|
||||
10
test_data/cloud-init/ubuntu/user-data
Executable file
10
test_data/cloud-init/ubuntu/user-data
Executable file
@@ -0,0 +1,10 @@
|
||||
#cloud-config
|
||||
users:
|
||||
- name: cloud
|
||||
passwd: $6$7125787751a8d18a$sHwGySomUA1PawiNFWVCKYQN.Ec.Wzz0JtPPL1MvzFrkwmop2dq7.4CYf03A5oemPQ4pOFCCrtCelvFBEle/K.
|
||||
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||
lock_passwd: False
|
||||
inactive: False
|
||||
shell: /bin/bash
|
||||
|
||||
ssh_pwauth: True
|
||||
@@ -4,12 +4,12 @@ version = "0.0.1"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = ">=1.2.1"
|
||||
byteorder = "1.3.2"
|
||||
devices = { path = "../devices" }
|
||||
kvm-bindings = "0.1"
|
||||
kvm-bindings = "0.1.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = ">=0.2.39"
|
||||
log = "*"
|
||||
libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
pci = { path = "../pci" }
|
||||
vfio-bindings = { path = "../vfio-bindings" }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
|
||||
@@ -9,6 +9,7 @@ extern crate byteorder;
|
||||
extern crate devices;
|
||||
extern crate kvm_bindings;
|
||||
extern crate kvm_ioctls;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate pci;
|
||||
extern crate vfio_bindings;
|
||||
|
||||
@@ -14,13 +14,13 @@ use std::mem;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::os::unix::prelude::FileExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::u32;
|
||||
use vfio_bindings::bindings::vfio::*;
|
||||
use vfio_ioctls::*;
|
||||
use vm_memory::{Address, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::ioctl::*;
|
||||
use vmm_sys_util::EventFd;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VfioError {
|
||||
@@ -513,14 +513,18 @@ pub struct VfioDevice {
|
||||
group: VfioGroup,
|
||||
regions: Vec<VfioRegion>,
|
||||
irqs: HashMap<u32, VfioIrq>,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
}
|
||||
|
||||
impl VfioDevice {
|
||||
/// Create a new vfio device, then guest read/write on this device could be
|
||||
/// transfered into kernel vfio.
|
||||
/// sysfspath specify the vfio device path in sys file system.
|
||||
pub fn new(sysfspath: &Path, device_fd: Arc<DeviceFd>, mem: GuestMemoryMmap) -> Result<Self> {
|
||||
pub fn new(
|
||||
sysfspath: &Path,
|
||||
device_fd: Arc<DeviceFd>,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
) -> Result<Self> {
|
||||
let uuid_path: PathBuf = [sysfspath, Path::new("iommu_group")].iter().collect();
|
||||
let group_path = uuid_path.read_link().map_err(|_| VfioError::InvalidPath)?;
|
||||
let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?;
|
||||
@@ -769,7 +773,7 @@ impl VfioDevice {
|
||||
/// Add all guest memory regions into vfio container's iommu table,
|
||||
/// then vfio kernel driver could access guest memory from gfn
|
||||
pub fn setup_dma_map(&self) -> Result<()> {
|
||||
self.mem.with_regions(|_index, region| {
|
||||
self.mem.read().unwrap().with_regions(|_index, region| {
|
||||
self.vfio_dma_map(
|
||||
region.start_addr().raw_value(),
|
||||
region.len() as u64,
|
||||
@@ -782,7 +786,7 @@ impl VfioDevice {
|
||||
/// remove all guest memory regions from vfio containers iommu table
|
||||
/// then vfio kernel driver couldn't access this guest memory
|
||||
pub fn unset_dma_map(&self) -> Result<()> {
|
||||
self.mem.with_regions(|_index, region| {
|
||||
self.mem.read().unwrap().with_regions(|_index, region| {
|
||||
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64)
|
||||
})?;
|
||||
Ok(())
|
||||
|
||||
@@ -27,7 +27,7 @@ use std::{fmt, io};
|
||||
use vfio_bindings::bindings::vfio::*;
|
||||
use vm_allocator::SystemAllocator;
|
||||
use vm_memory::{Address, GuestAddress, GuestUsize};
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VfioPciError {
|
||||
@@ -207,12 +207,14 @@ impl Interrupt {
|
||||
|
||||
fn msix_write_table(&mut self, offset: u64, data: &[u8]) {
|
||||
if let Some(ref mut msix) = &mut self.msix {
|
||||
let offset = offset - u64::from(msix.cap.table_offset());
|
||||
msix.bar.write_table(offset, data)
|
||||
}
|
||||
}
|
||||
|
||||
fn msix_read_table(&self, offset: u64, data: &mut [u8]) {
|
||||
if let Some(msix) = &self.msix {
|
||||
let offset = offset - u64::from(msix.cap.table_offset());
|
||||
msix.bar.read_table(offset, data)
|
||||
}
|
||||
}
|
||||
@@ -386,11 +388,9 @@ impl VfioPciDevice {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
entry.u.msi.address_lo = route.msi_vector.msg_addr_lo;
|
||||
entry.u.msi.address_hi = route.msi_vector.msg_addr_hi;
|
||||
entry.u.msi.data = route.msi_vector.msg_data;
|
||||
};
|
||||
entry.u.msi.address_lo = route.msi_vector.msg_addr_lo;
|
||||
entry.u.msi.address_hi = route.msi_vector.msg_addr_hi;
|
||||
entry.u.msi.data = route.msi_vector.msg_data;
|
||||
|
||||
entry_vec.push(entry);
|
||||
}
|
||||
@@ -694,8 +694,6 @@ impl BusDevice for VfioPciDevice {
|
||||
|
||||
// First BAR offset in the PCI config space.
|
||||
const PCI_CONFIG_BAR_OFFSET: u32 = 0x10;
|
||||
// First BAR register index
|
||||
const PCI_CONFIG_BAR0_INDEX: usize = 4;
|
||||
// Capability register offset in the PCI config space.
|
||||
const PCI_CONFIG_CAPABILITY_OFFSET: u32 = 0x34;
|
||||
// IO BAR when first BAR bit is 1.
|
||||
@@ -708,6 +706,10 @@ const PCI_CONFIG_MEMORY_BAR_64BIT: u32 = 0x4;
|
||||
const PCI_CONFIG_REGISTER_SIZE: usize = 4;
|
||||
// Number of BARs for a PCI device
|
||||
const BAR_NUMS: usize = 6;
|
||||
// PCI Header Type register index
|
||||
const PCI_HEADER_TYPE_REG_INDEX: usize = 3;
|
||||
// First BAR register index
|
||||
const PCI_CONFIG_BAR0_INDEX: usize = 4;
|
||||
// PCI ROM expansion BAR register index
|
||||
const PCI_ROM_EXP_BAR_INDEX: usize = 12;
|
||||
// PCI interrupt pin and line register index
|
||||
@@ -726,14 +728,18 @@ impl PciDevice for VfioPciDevice {
|
||||
// We're not saving the BAR address to restore it, because we
|
||||
// are going to allocate a guest address for each BAR and write
|
||||
// that new address back.
|
||||
while bar_id < VFIO_PCI_ROM_REGION_INDEX {
|
||||
while bar_id < VFIO_PCI_CONFIG_REGION_INDEX {
|
||||
let mut lsb_size: u32 = 0xffff_ffff;
|
||||
let mut msb_size = 0;
|
||||
let mut region_size: u64;
|
||||
let bar_addr: GuestAddress;
|
||||
|
||||
// Read the BAR size (Starts by all 1s to the BAR)
|
||||
let bar_offset = PCI_CONFIG_BAR_OFFSET + bar_id * 4;
|
||||
let bar_offset = if bar_id == VFIO_PCI_ROM_REGION_INDEX {
|
||||
(PCI_ROM_EXP_BAR_INDEX * 4) as u32
|
||||
} else {
|
||||
PCI_CONFIG_BAR_OFFSET + bar_id * 4
|
||||
};
|
||||
|
||||
self.vfio_pci_configuration
|
||||
.write_config_dword(lsb_size, bar_offset);
|
||||
@@ -748,15 +754,23 @@ impl PciDevice for VfioPciDevice {
|
||||
}
|
||||
|
||||
// Is this an IO BAR?
|
||||
let io_bar = match lsb_flag & PCI_CONFIG_IO_BAR {
|
||||
PCI_CONFIG_IO_BAR => true,
|
||||
_ => false,
|
||||
let io_bar = if bar_id != VFIO_PCI_ROM_REGION_INDEX {
|
||||
match lsb_flag & PCI_CONFIG_IO_BAR {
|
||||
PCI_CONFIG_IO_BAR => true,
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Is this a 64-bit BAR?
|
||||
let is_64bit_bar = match lsb_flag & PCI_CONFIG_MEMORY_BAR_64BIT {
|
||||
PCI_CONFIG_MEMORY_BAR_64BIT => true,
|
||||
_ => false,
|
||||
let is_64bit_bar = if bar_id != VFIO_PCI_ROM_REGION_INDEX {
|
||||
match lsb_flag & PCI_CONFIG_MEMORY_BAR_64BIT {
|
||||
PCI_CONFIG_MEMORY_BAR_64BIT => true,
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// By default, the region type is 32 bits memory BAR.
|
||||
@@ -808,14 +822,15 @@ impl PciDevice for VfioPciDevice {
|
||||
// In case the BAR is mappable directly, this means it might be
|
||||
// set as KVM user memory region, which expects to deal with 4K
|
||||
// pages. Therefore, the aligment has to be set accordingly.
|
||||
let bar_alignment =
|
||||
if self.device.get_region_flags(bar_id) & VFIO_REGION_INFO_FLAG_MMAP != 0 {
|
||||
// 4K alignment
|
||||
0x1000
|
||||
} else {
|
||||
// Default 16 bytes alignment
|
||||
0x10
|
||||
};
|
||||
let bar_alignment = if (bar_id == VFIO_PCI_ROM_REGION_INDEX)
|
||||
|| (self.device.get_region_flags(bar_id) & VFIO_REGION_INFO_FLAG_MMAP != 0)
|
||||
{
|
||||
// 4K alignment
|
||||
0x1000
|
||||
} else {
|
||||
// Default 16 bytes alignment
|
||||
0x10
|
||||
};
|
||||
if is_64bit_bar {
|
||||
bar_addr = allocator
|
||||
.allocate_mmio_addresses(None, region_size, Some(bar_alignment))
|
||||
@@ -827,16 +842,28 @@ impl PciDevice for VfioPciDevice {
|
||||
}
|
||||
}
|
||||
|
||||
let reg_idx = if bar_id == VFIO_PCI_ROM_REGION_INDEX {
|
||||
PCI_ROM_EXP_BAR_INDEX
|
||||
} else {
|
||||
bar_id as usize
|
||||
};
|
||||
|
||||
// We can now build our BAR configuration block.
|
||||
let config = PciBarConfiguration::default()
|
||||
.set_register_index(bar_id as usize)
|
||||
.set_register_index(reg_idx)
|
||||
.set_address(bar_addr.raw_value())
|
||||
.set_size(region_size)
|
||||
.set_region_type(region_type);
|
||||
|
||||
self.configuration
|
||||
.add_pci_bar(&config)
|
||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
|
||||
if bar_id == VFIO_PCI_ROM_REGION_INDEX {
|
||||
self.configuration
|
||||
.add_pci_rom_bar(&config, lsb_flag & 0x1)
|
||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
|
||||
} else {
|
||||
self.configuration
|
||||
.add_pci_bar(&config)
|
||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
|
||||
}
|
||||
|
||||
ranges.push((bar_addr, region_size, region_type));
|
||||
self.mmio_regions.push(MmioRegion {
|
||||
@@ -862,7 +889,9 @@ impl PciDevice for VfioPciDevice {
|
||||
// When the guest wants to write to a BAR, we trap it into
|
||||
// our local configuration space. We're not reprogramming
|
||||
// VFIO device.
|
||||
if reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS {
|
||||
if (reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS)
|
||||
|| reg_idx == PCI_ROM_EXP_BAR_INDEX
|
||||
{
|
||||
// We keep our local cache updated with the BARs.
|
||||
// We'll read it back from there when the guest is asking
|
||||
// for BARs (see read_config_register()).
|
||||
@@ -900,23 +929,25 @@ impl PciDevice for VfioPciDevice {
|
||||
// from our local configuration space. We want the guest to
|
||||
// use that and not the VFIO device BARs as it does not map
|
||||
// with the guest address space.
|
||||
if reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS {
|
||||
if (reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS)
|
||||
|| reg_idx == PCI_ROM_EXP_BAR_INDEX
|
||||
{
|
||||
return self.configuration.read_reg(reg_idx);
|
||||
}
|
||||
|
||||
// Since the ROM expansion BAR is not yet handled by the code, it is
|
||||
// more proper to expose it to the guest as being disabled.
|
||||
if reg_idx == PCI_ROM_EXP_BAR_INDEX {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Since we don't support INTx (only MSI and MSI-X), we should not
|
||||
// expose an invalid Interrupt Pin to the guest. By using a specific
|
||||
// mask in case the register being read correspond to the interrupt
|
||||
// register, this code makes sure to always expose an Interrupt Pin
|
||||
// value of 0, which stands for no interrupt pin support.
|
||||
//
|
||||
// Since we don't support passing multi-functions devices, we should
|
||||
// mask the multi-function bit, bit 7 of the Header Type byte on the
|
||||
// register 3.
|
||||
let mask = if reg_idx == PCI_INTX_REG_INDEX {
|
||||
0xffff_00ff
|
||||
} else if reg_idx == PCI_HEADER_TYPE_REG_INDEX {
|
||||
0xff7f_ffff
|
||||
} else {
|
||||
0xffff_ffff
|
||||
};
|
||||
|
||||
@@ -13,8 +13,8 @@ vhost-user-master = []
|
||||
vhost-user-slave = []
|
||||
|
||||
[dependencies]
|
||||
bitflags = ">=1.0.1"
|
||||
libc = ">=0.2.39"
|
||||
bitflags = "1.1.0"
|
||||
libc = "0.2.60"
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
||||
[dependencies.vm-memory]
|
||||
@@ -22,4 +22,4 @@ git = "https://github.com/rust-vmm/vm-memory"
|
||||
optional = true
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.0.5"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
use super::Result;
|
||||
use std::os::unix::io::RawFd;
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// Maximum number of memory regions supported.
|
||||
pub const VHOST_MAX_MEMORY_REGIONS: usize = 255;
|
||||
|
||||
@@ -15,8 +15,8 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::ptr::null;
|
||||
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestUsize};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::ioctl::{ioctl, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref};
|
||||
use vmm_sys_util::EventFd;
|
||||
|
||||
use super::{
|
||||
Error, Result, VhostBackend, VhostUserMemoryRegionInfo, VringConfigData,
|
||||
|
||||
@@ -197,6 +197,45 @@ impl<R: Req> Endpoint<R> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a message with header, body and config info. Optional file descriptors may be attached to
|
||||
/// the message.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - number of bytes sent on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
pub fn send_config_message(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<R>,
|
||||
user_config: &VhostUserConfig,
|
||||
buf: &mut [u8],
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> Result<()> {
|
||||
// Safe because there can't be other mutable referance to hdr and body.
|
||||
let iovs = unsafe {
|
||||
[
|
||||
slice::from_raw_parts(
|
||||
hdr as *const VhostUserMsgHeader<R> as *const u8,
|
||||
mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
),
|
||||
slice::from_raw_parts(
|
||||
user_config as *const VhostUserConfig as *const u8,
|
||||
mem::size_of::<VhostUserConfig>(),
|
||||
),
|
||||
slice::from_raw_parts(buf.as_ptr() as *const u8, buf.len()),
|
||||
]
|
||||
};
|
||||
let bytes = self.send_iovec(&iovs[..], fds)?;
|
||||
let total =
|
||||
mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<VhostUserConfig>() + buf.len();
|
||||
if bytes != total {
|
||||
return Err(Error::PartialMessage);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a message with header, body and payload. Optional file descriptors
|
||||
/// may also be attached to the message.
|
||||
///
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::connection::Endpoint;
|
||||
use super::message::*;
|
||||
@@ -352,7 +352,7 @@ impl VhostUserMaster for Master {
|
||||
|
||||
let mut node = self.node.lock().unwrap();
|
||||
// depends on VhostUserProtocolFeatures::CONFIG
|
||||
if node.acked_virtio_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
|
||||
@@ -360,7 +360,7 @@ impl VhostUserMaster for Master {
|
||||
// "Master payload: virtio device config space"
|
||||
// But what content should the payload contains for a get_config() request?
|
||||
// So current implementation doesn't conform to the spec.
|
||||
let hdr = node.send_request_with_body(MasterReq::GET_CONFIG, &body, None)?;
|
||||
let hdr = node.send_request_with_config_body(MasterReq::GET_CONFIG, &body, None)?;
|
||||
let (reply, buf, rfds) = node.recv_reply_with_payload::<VhostUserConfig>(&hdr)?;
|
||||
if rfds.is_some() {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
@@ -384,11 +384,11 @@ impl VhostUserMaster for Master {
|
||||
|
||||
let mut node = self.node.lock().unwrap();
|
||||
// depends on VhostUserProtocolFeatures::CONFIG
|
||||
if node.acked_virtio_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
|
||||
let hdr = node.send_request_with_payload(MasterReq::GET_CONFIG, &body, buf, None)?;
|
||||
let hdr = node.send_request_with_payload(MasterReq::SET_CONFIG, &body, buf, None)?;
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
@@ -480,6 +480,27 @@ impl MasterInternal {
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
fn send_request_with_config_body(
|
||||
&mut self,
|
||||
code: MasterReq,
|
||||
user_config: &VhostUserConfig,
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> VhostUserResult<VhostUserMsgHeader<MasterReq>> {
|
||||
if mem::size_of::<VhostUserConfig>() + user_config.size as usize > MAX_MSG_SIZE {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
let hdr = Self::new_request_header(
|
||||
code,
|
||||
mem::size_of::<VhostUserConfig>() as u32 + user_config.size,
|
||||
);
|
||||
let mut buf = vec![0; user_config.size as usize];
|
||||
self.main_sock
|
||||
.send_config_message(&hdr, user_config, &mut buf, fds)?;
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
fn send_request_with_payload<T: Sized, P: Sized>(
|
||||
&mut self,
|
||||
code: MasterReq,
|
||||
@@ -555,7 +576,7 @@ impl MasterInternal {
|
||||
if !reply.is_reply_for(hdr)
|
||||
|| reply.get_size() as usize != mem::size_of::<T>() + bytes
|
||||
|| rfds.is_some()
|
||||
|| body.is_valid()
|
||||
|| !body.is_valid()
|
||||
{
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return Err(VhostUserError::InvalidMessage);
|
||||
@@ -603,7 +624,7 @@ impl MasterInternal {
|
||||
#[inline]
|
||||
fn new_request_header(request: MasterReq, size: u32) -> VhostUserMsgHeader<MasterReq> {
|
||||
// TODO: handle NEED_REPLY flag
|
||||
VhostUserMsgHeader::new(request, 0, size)
|
||||
VhostUserMsgHeader::new(request, 0x1, size)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,14 +109,14 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
||||
}
|
||||
};
|
||||
|
||||
match hdr.get_code() {
|
||||
let res = match hdr.get_code() {
|
||||
SlaveReq::CONFIG_CHANGE_MSG => {
|
||||
self.check_msg_size(&hdr, size, 0)?;
|
||||
self.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.handle_config_change()
|
||||
.map_err(Error::ReqHandlerError)?;
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
SlaveReq::FS_MAP => {
|
||||
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
||||
@@ -124,7 +124,7 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.fs_slave_map(msg, rfds.unwrap()[0])
|
||||
.map_err(Error::ReqHandlerError)?;
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
SlaveReq::FS_UNMAP => {
|
||||
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
||||
@@ -132,7 +132,7 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.fs_slave_unmap(msg)
|
||||
.map_err(Error::ReqHandlerError)?;
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
SlaveReq::FS_SYNC => {
|
||||
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
||||
@@ -140,14 +140,14 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.fs_slave_sync(msg)
|
||||
.map_err(Error::ReqHandlerError)?;
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
}
|
||||
_ => Err(Error::InvalidMessage),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
self.send_ack_message(&hdr, &res)?;
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn check_state(&self) -> Result<()> {
|
||||
@@ -217,6 +217,38 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
fn new_reply_header<T: Sized>(
|
||||
&self,
|
||||
req: &VhostUserMsgHeader<SlaveReq>,
|
||||
) -> Result<VhostUserMsgHeader<SlaveReq>> {
|
||||
if mem::size_of::<T>() > MAX_MSG_SIZE {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
Ok(VhostUserMsgHeader::new(
|
||||
req.get_code(),
|
||||
VhostUserHeaderFlag::REPLY.bits(),
|
||||
mem::size_of::<T>() as u32,
|
||||
))
|
||||
}
|
||||
|
||||
fn send_ack_message(
|
||||
&mut self,
|
||||
req: &VhostUserMsgHeader<SlaveReq>,
|
||||
res: &Result<()>,
|
||||
) -> Result<()> {
|
||||
if req.is_need_reply() {
|
||||
let hdr = self.new_reply_header::<VhostUserU64>(req)?;
|
||||
let val = match res {
|
||||
Ok(_) => 0,
|
||||
Err(_) => 1,
|
||||
};
|
||||
let msg = VhostUserU64::new(val);
|
||||
self.sub_sock.send_message(&hdr, &msg, None)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: VhostUserMasterReqHandler> AsRawFd for MasterReqHandler<S> {
|
||||
|
||||
@@ -540,12 +540,10 @@ impl VhostUserMsgValidator for VhostUserVringAddr {
|
||||
bitflags! {
|
||||
/// Flags for the device configuration message.
|
||||
pub struct VhostUserConfigFlags: u32 {
|
||||
/// TODO: seems the vhost-user spec has refined the definition, EMPTY is removed.
|
||||
const EMPTY = 0x0;
|
||||
/// Vhost master messages used for writable fields
|
||||
const WRITABLE = 0x1;
|
||||
/// Mark that message is part of an ongoing live-migration operation.
|
||||
const LIVE_MIGRATION = 0x2;
|
||||
/// Vhost master messages used for writeable fields.
|
||||
const WRITABLE = 0x0;
|
||||
/// Vhost master messages used for live migration.
|
||||
const LIVE_MIGRATION = 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,10 +575,9 @@ impl VhostUserMsgValidator for VhostUserConfig {
|
||||
fn is_valid(&self) -> bool {
|
||||
if (self.flags & !VhostUserConfigFlags::all().bits()) != 0 {
|
||||
return false;
|
||||
} else if self.offset < VHOST_USER_CONFIG_OFFSET
|
||||
|| self.offset >= VHOST_USER_CONFIG_SIZE
|
||||
} else if self.offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| self.size == 0
|
||||
|| self.size > (VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET)
|
||||
|| self.size > VHOST_USER_CONFIG_SIZE
|
||||
|| self.size + self.offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return false;
|
||||
@@ -787,7 +784,7 @@ mod tests {
|
||||
let mut msg = VhostUserConfig::new(
|
||||
VHOST_USER_CONFIG_OFFSET,
|
||||
VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET,
|
||||
VhostUserConfigFlags::EMPTY,
|
||||
VhostUserConfigFlags::WRITABLE,
|
||||
);
|
||||
|
||||
assert!(msg.is_valid());
|
||||
@@ -804,7 +801,7 @@ mod tests {
|
||||
msg.size = 2;
|
||||
assert!(!msg.is_valid());
|
||||
msg.size = 1;
|
||||
msg.flags |= VhostUserConfigFlags::WRITABLE.bits();
|
||||
msg.flags |= VhostUserConfigFlags::LIVE_MIGRATION.bits();
|
||||
assert!(msg.is_valid());
|
||||
msg.flags |= 0x4;
|
||||
assert!(!msg.is_valid());
|
||||
|
||||
@@ -116,10 +116,10 @@ impl Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<vmm_sys_util::Error> for Error {
|
||||
impl std::convert::From<vmm_sys_util::errno::Error> for Error {
|
||||
/// Convert raw socket errors into meaningful vhost-user errors.
|
||||
///
|
||||
/// The vmm_sys_util::Error is a simple wrapper over the raw errno, which doesn't means much
|
||||
/// The vmm_sys_util::errno::Error is a simple wrapper over the raw errno, which doesn't means much
|
||||
/// to the vhost-user connection manager. So convert it into meaningful errors to simplify
|
||||
/// the connection manager logic.
|
||||
///
|
||||
@@ -128,7 +128,7 @@ impl std::convert::From<vmm_sys_util::Error> for Error {
|
||||
/// * - Error::SocketBroken: the underline socket is broken.
|
||||
/// * - Error::SocketError: other socket related errors.
|
||||
#[allow(unreachable_patterns)] // EWOULDBLOCK equals to EGAIN on linux
|
||||
fn from(err: vmm_sys_util::Error) -> Self {
|
||||
fn from(err: vmm_sys_util::errno::Error) -> Self {
|
||||
match err.errno() {
|
||||
// The socket is marked nonblocking and the requested operation would block.
|
||||
libc::EAGAIN => Error::SocketRetry(IOError::from_raw_os_error(libc::EAGAIN)),
|
||||
|
||||
@@ -16,7 +16,7 @@ use std::ptr::{copy_nonoverlapping, null_mut, write_unaligned};
|
||||
use libc::{
|
||||
c_long, c_void, cmsghdr, iovec, msghdr, recvmsg, sendmsg, MSG_NOSIGNAL, SCM_RIGHTS, SOL_SOCKET,
|
||||
};
|
||||
use vmm_sys_util::{Error, Result};
|
||||
use vmm_sys_util::errno::{Error, Result};
|
||||
|
||||
// Each of the following macros performs the same function as their C counterparts. They are each
|
||||
// macros because they are used to size statically allocated arrays.
|
||||
@@ -176,7 +176,7 @@ fn raw_recvmsg(fd: RawFd, iovecs: &mut [iovec], in_fds: &mut [RawFd]) -> Result<
|
||||
|
||||
// Safe because the msghdr was properly constructed from valid (or null) pointers of the
|
||||
// indicated length and we check the return value.
|
||||
let total_read = unsafe { recvmsg(fd, &mut msg, 0) };
|
||||
let total_read = unsafe { recvmsg(fd, &mut msg, libc::MSG_WAITALL) };
|
||||
|
||||
if total_read == -1 {
|
||||
return Err(Error::last());
|
||||
@@ -335,7 +335,7 @@ mod tests {
|
||||
|
||||
use libc::cmsghdr;
|
||||
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[test]
|
||||
fn buffer_len() {
|
||||
|
||||
@@ -5,5 +5,5 @@ authors = ["The Chromium OS Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
libc = "*"
|
||||
libc = "0.2.60"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
|
||||
@@ -5,15 +5,15 @@ authors = ["Samuel Ortiz <sameo@linux.intel.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "=1.2.1"
|
||||
byteorder = "1.3.2"
|
||||
devices = { path = "../devices" }
|
||||
epoll = "=4.0.1"
|
||||
libc = ">=0.2.39"
|
||||
log = "*"
|
||||
epoll = "4.1.0"
|
||||
libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
net_gen = { path = "../net_gen" }
|
||||
net_util = { path = "../net_util" }
|
||||
pci = { path = "../pci" }
|
||||
tempfile = ">=3.0.2"
|
||||
tempfile = "3.1.0"
|
||||
virtio-bindings = { path = "../virtio-bindings" }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
||||
@@ -17,19 +17,18 @@ use std::os::linux::fs::MetadataExt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::PathBuf;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
|
||||
VirtioDeviceType, INTERRUPT_STATUS_USED_RING,
|
||||
VirtioDeviceType, VirtioInterruptType,
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use virtio_bindings::virtio_blk::*;
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const CONFIG_SPACE_SIZE: usize = 8;
|
||||
const SECTOR_SHIFT: u8 = 9;
|
||||
@@ -322,10 +321,9 @@ impl Request {
|
||||
|
||||
struct BlockEpollHandler<T: DiskFile> {
|
||||
queues: Vec<Queue>,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
disk_image: T,
|
||||
disk_nsectors: u64,
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
disk_image_id: Vec<u8>,
|
||||
}
|
||||
@@ -336,14 +334,15 @@ impl<T: DiskFile> BlockEpollHandler<T> {
|
||||
|
||||
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 mem = self.mem.read().unwrap();
|
||||
for avail_desc in queue.iter(&mem) {
|
||||
let len;
|
||||
match Request::parse(&avail_desc, &self.mem) {
|
||||
match Request::parse(&avail_desc, &mem) {
|
||||
Ok(request) => {
|
||||
let status = match request.execute(
|
||||
&mut self.disk_image,
|
||||
self.disk_nsectors,
|
||||
&self.mem,
|
||||
&mem,
|
||||
&self.disk_image_id,
|
||||
) {
|
||||
Ok(l) => {
|
||||
@@ -358,7 +357,7 @@ impl<T: DiskFile> BlockEpollHandler<T> {
|
||||
};
|
||||
// We use unwrap because the request parsing process already checked that the
|
||||
// status_addr was valid.
|
||||
self.mem.write_obj(status, request.status_addr).unwrap();
|
||||
mem.write_obj(status, request.status_addr).unwrap();
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to parse available descriptor chain: {:?}", e);
|
||||
@@ -370,18 +369,18 @@ impl<T: DiskFile> BlockEpollHandler<T> {
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
queue.add_used(&self.mem, desc_index, len);
|
||||
queue.add_used(&mem, desc_index, len);
|
||||
}
|
||||
used_count > 0
|
||||
}
|
||||
|
||||
fn signal_used_queue(&self, queue_index: usize) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_status
|
||||
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
|
||||
(self.interrupt_cb)(&self.queues[queue_index]).map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queues[queue_index])).map_err(
|
||||
|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -424,8 +423,22 @@ impl<T: DiskFile> BlockEpollHandler<T> {
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events =
|
||||
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
|
||||
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
// It's well defined from the epoll_wait() syscall
|
||||
// documentation that the epoll loop can be interrupted
|
||||
// before any of the requested events occurred or the
|
||||
// timeout expired. In both those cases, epoll_wait()
|
||||
// returns an error of type EINTR, but this should not
|
||||
// be considered as a regular error. Instead it is more
|
||||
// appropriate to retry, by calling into epoll_wait().
|
||||
continue;
|
||||
}
|
||||
return Err(DeviceError::EpollWait(e));
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let ev_type = event.data as u16;
|
||||
@@ -598,9 +611,8 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
status: Arc<AtomicUsize>,
|
||||
queues: Vec<Queue>,
|
||||
mut queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
@@ -644,7 +656,6 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
|
||||
mem,
|
||||
disk_image,
|
||||
disk_nsectors: self.disk_nsectors,
|
||||
interrupt_status: status,
|
||||
interrupt_cb,
|
||||
disk_image_id,
|
||||
};
|
||||
|
||||
@@ -10,18 +10,18 @@ use std::io;
|
||||
use std::io::Write;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread;
|
||||
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
|
||||
INTERRUPT_STATUS_USED_RING, VIRTIO_F_VERSION_1,
|
||||
VirtioInterruptType, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use vm_memory::{Bytes, GuestMemoryMmap};
|
||||
use vmm_sys_util::EventFd;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use vm_memory::{ByteValued, Bytes, GuestMemoryMmap};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
const NUM_QUEUES: usize = 2;
|
||||
@@ -34,17 +34,34 @@ const OUTPUT_QUEUE_EVENT: DeviceEventT = 1;
|
||||
const INPUT_EVENT: DeviceEventT = 2;
|
||||
// The device has been dropped.
|
||||
const KILL_EVENT: DeviceEventT = 3;
|
||||
// Console configuration change event is triggered.
|
||||
const CONFIG_EVENT: DeviceEventT = 4;
|
||||
|
||||
//Console size feature bit
|
||||
const VIRTIO_CONSOLE_F_SIZE: u64 = 0;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioConsoleConfig {
|
||||
cols: u16,
|
||||
rows: u16,
|
||||
max_nr_ports: u32,
|
||||
emerg_wr: u32,
|
||||
}
|
||||
|
||||
// Safe because it only has data and has no implicit padding.
|
||||
unsafe impl ByteValued for VirtioConsoleConfig {}
|
||||
|
||||
struct ConsoleEpollHandler {
|
||||
queues: Vec<Queue>,
|
||||
mem: GuestMemoryMmap,
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
in_buffer: Arc<Mutex<VecDeque<u8>>>,
|
||||
out: Box<io::Write + Send>,
|
||||
out: Box<dyn io::Write + Send>,
|
||||
input_queue_evt: EventFd,
|
||||
output_queue_evt: EventFd,
|
||||
input_evt: EventFd,
|
||||
config_evt: EventFd,
|
||||
kill_evt: EventFd,
|
||||
}
|
||||
|
||||
@@ -63,14 +80,15 @@ impl ConsoleEpollHandler {
|
||||
let mut used_count = 0;
|
||||
let mut write_count = 0;
|
||||
|
||||
for avail_desc in recv_queue.iter(&self.mem) {
|
||||
let mem = self.mem.read().unwrap();
|
||||
for avail_desc in recv_queue.iter(&mem) {
|
||||
let len;
|
||||
|
||||
let limit = cmp::min(write_count + avail_desc.len as u32, count as u32);
|
||||
let source_slice = in_buffer
|
||||
.drain(write_count as usize..limit as usize)
|
||||
.collect::<Vec<u8>>();
|
||||
let write_result = self.mem.write_slice(&source_slice[..], avail_desc.addr);
|
||||
let write_result = mem.write_slice(&source_slice[..], avail_desc.addr);
|
||||
|
||||
match write_result {
|
||||
Ok(_) => {
|
||||
@@ -92,7 +110,7 @@ impl ConsoleEpollHandler {
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
recv_queue.add_used(&self.mem, desc_index, len);
|
||||
recv_queue.add_used(&mem, desc_index, len);
|
||||
}
|
||||
used_count > 0
|
||||
}
|
||||
@@ -109,11 +127,10 @@ impl ConsoleEpollHandler {
|
||||
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
|
||||
let mut used_count = 0;
|
||||
|
||||
for avail_desc in trans_queue.iter(&self.mem) {
|
||||
let mem = self.mem.read().unwrap();
|
||||
for avail_desc in trans_queue.iter(&mem) {
|
||||
let len;
|
||||
let _ = self
|
||||
.mem
|
||||
.write_to(avail_desc.addr, &mut self.out, avail_desc.len as usize);
|
||||
let _ = mem.write_to(avail_desc.addr, &mut self.out, avail_desc.len as usize);
|
||||
let _ = self.out.flush();
|
||||
|
||||
len = avail_desc.len;
|
||||
@@ -122,15 +139,13 @@ impl ConsoleEpollHandler {
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
trans_queue.add_used(&self.mem, desc_index, len);
|
||||
trans_queue.add_used(&mem, desc_index, len);
|
||||
}
|
||||
used_count > 0
|
||||
}
|
||||
|
||||
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_status
|
||||
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
|
||||
(self.interrupt_cb)(&self.queues[0]).map_err(|e| {
|
||||
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queues[0])).map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
@@ -162,6 +177,14 @@ impl ConsoleEpollHandler {
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(INPUT_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.config_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(CONFIG_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
@@ -174,8 +197,22 @@ impl ConsoleEpollHandler {
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events =
|
||||
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
|
||||
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
// It's well defined from the epoll_wait() syscall
|
||||
// documentation that the epoll loop can be interrupted
|
||||
// before any of the requested events occurred or the
|
||||
// timeout expired. In both those cases, epoll_wait()
|
||||
// returns an error of type EINTR, but this should not
|
||||
// be considered as a regular error. Instead it is more
|
||||
// appropriate to retry, by calling into epoll_wait().
|
||||
continue;
|
||||
}
|
||||
return Err(DeviceError::EpollWait(e));
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let ev_type = event.data as u16;
|
||||
@@ -206,6 +243,17 @@ impl ConsoleEpollHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
CONFIG_EVENT => {
|
||||
if let Err(e) = self.config_evt.read() {
|
||||
error!("Failed to get config event: {:?}", e);
|
||||
break 'epoll;
|
||||
} else if let Err(e) =
|
||||
(self.interrupt_cb)(&VirtioInterruptType::Config, None)
|
||||
{
|
||||
error!("Failed to signal console driver: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
KILL_EVENT => {
|
||||
debug!("KILL_EVENT received, stopping epoll loop");
|
||||
break 'epoll;
|
||||
@@ -221,19 +269,13 @@ impl ConsoleEpollHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Virtio device for exposing console to the guest OS through virtio.
|
||||
pub struct Console {
|
||||
kill_evt: Option<EventFd>,
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
input: Arc<ConsoleInput>,
|
||||
out: Option<Box<io::Write + Send>>,
|
||||
}
|
||||
|
||||
/// Input device.
|
||||
pub struct ConsoleInput {
|
||||
input_evt: EventFd,
|
||||
config_evt: EventFd,
|
||||
in_buffer: Arc<Mutex<VecDeque<u8>>>,
|
||||
config: Arc<Mutex<VirtioConsoleConfig>>,
|
||||
acked_features: AtomicU64,
|
||||
}
|
||||
|
||||
impl ConsoleInput {
|
||||
@@ -242,18 +284,64 @@ impl ConsoleInput {
|
||||
in_buffer.extend(input);
|
||||
let _ = self.input_evt.write(1);
|
||||
}
|
||||
|
||||
pub fn update_console_size(&self, cols: u16, rows: u16) {
|
||||
if self
|
||||
.acked_features
|
||||
.fetch_and(1u64 << VIRTIO_CONSOLE_F_SIZE, Ordering::SeqCst)
|
||||
!= 0
|
||||
{
|
||||
self.config.lock().unwrap().update_console_size(cols, rows);
|
||||
//Send the interrupt to the driver
|
||||
let _ = self.config_evt.write(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioConsoleConfig {
|
||||
pub fn new(cols: u16, rows: u16) -> Self {
|
||||
VirtioConsoleConfig {
|
||||
cols,
|
||||
rows,
|
||||
max_nr_ports: 1u32,
|
||||
emerg_wr: 0u32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_console_size(&mut self, cols: u16, rows: u16) {
|
||||
self.cols = cols;
|
||||
self.rows = rows;
|
||||
}
|
||||
}
|
||||
|
||||
/// Virtio device for exposing console to the guest OS through virtio.
|
||||
pub struct Console {
|
||||
kill_evt: Option<EventFd>,
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
config: Arc<Mutex<VirtioConsoleConfig>>,
|
||||
input: Arc<ConsoleInput>,
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
}
|
||||
|
||||
impl Console {
|
||||
/// Create a new virtio console device that gets random data from /dev/urandom.
|
||||
pub fn new(out: Option<Box<io::Write + Send>>) -> io::Result<(Console, Arc<ConsoleInput>)> {
|
||||
let avail_features = 1u64 << VIRTIO_F_VERSION_1;
|
||||
pub fn new(
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
cols: u16,
|
||||
rows: u16,
|
||||
) -> io::Result<(Console, Arc<ConsoleInput>)> {
|
||||
let avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_CONSOLE_F_SIZE;
|
||||
|
||||
let input_evt = EventFd::new(EFD_NONBLOCK).unwrap();
|
||||
|
||||
let config_evt = EventFd::new(EFD_NONBLOCK).unwrap();
|
||||
let console_config = Arc::new(Mutex::new(VirtioConsoleConfig::new(cols, rows)));
|
||||
let console_input = Arc::new(ConsoleInput {
|
||||
input_evt,
|
||||
config_evt,
|
||||
in_buffer: Arc::new(Mutex::new(VecDeque::new())),
|
||||
config: console_config.clone(),
|
||||
acked_features: AtomicU64::new(0),
|
||||
});
|
||||
|
||||
Ok((
|
||||
@@ -261,6 +349,7 @@ impl Console {
|
||||
kill_evt: None,
|
||||
avail_features,
|
||||
acked_features: 0u64,
|
||||
config: console_config,
|
||||
input: console_input.clone(),
|
||||
out,
|
||||
},
|
||||
@@ -321,19 +410,30 @@ impl VirtioDevice for Console {
|
||||
self.acked_features |= v;
|
||||
}
|
||||
|
||||
fn read_config(&self, _offset: u64, _data: &mut [u8]) {
|
||||
warn!("Device specific configuration is not defined yet");
|
||||
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
|
||||
let config = self.config.lock().unwrap();
|
||||
let config_slice = config.as_slice();
|
||||
let config_len = config_slice.len() as u64;
|
||||
if offset >= config_len {
|
||||
error!("Failed to read config space");
|
||||
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_all(&config_slice[offset as usize..cmp::min(end, config_len) as usize])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn write_config(&mut self, _offset: u64, _data: &[u8]) {
|
||||
warn!("Device specific configuration is not defined yet");
|
||||
warn!("No device specific configration requires write");
|
||||
}
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
status: Arc<AtomicUsize>,
|
||||
queues: Vec<Queue>,
|
||||
mut queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
@@ -356,17 +456,27 @@ impl VirtioDevice for Console {
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
self.input
|
||||
.acked_features
|
||||
.store(self.acked_features, Ordering::Relaxed);
|
||||
|
||||
if (self.acked_features & (1u64 << VIRTIO_CONSOLE_F_SIZE)) != 0 {
|
||||
if let Err(e) = (interrupt_cb)(&VirtioInterruptType::Config, None) {
|
||||
error!("Failed to signal console driver: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(out) = self.out.take() {
|
||||
let mut handler = ConsoleEpollHandler {
|
||||
queues,
|
||||
mem,
|
||||
interrupt_status: status,
|
||||
interrupt_cb,
|
||||
in_buffer: self.input.in_buffer.clone(),
|
||||
out,
|
||||
input_queue_evt: queue_evts.remove(0),
|
||||
output_queue_evt: queue_evts.remove(0),
|
||||
input_evt: self.input.input_evt.try_clone().unwrap(),
|
||||
config_evt: self.input.config_evt.try_clone().unwrap(),
|
||||
kill_evt,
|
||||
};
|
||||
|
||||
|
||||
@@ -7,13 +7,33 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use super::*;
|
||||
use pci::{PciBarConfiguration, PciCapability};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::Arc;
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
use vmm_sys_util::EventFd;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap, GuestUsize};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub type VirtioInterrupt = Box<Fn(&Queue) -> std::result::Result<(), std::io::Error> + Send + Sync>;
|
||||
pub enum VirtioInterruptType {
|
||||
Config,
|
||||
Queue,
|
||||
}
|
||||
|
||||
pub type VirtioInterrupt = Box<
|
||||
dyn Fn(&VirtioInterruptType, Option<&Queue>) -> std::result::Result<(), std::io::Error>
|
||||
+ Send
|
||||
+ Sync,
|
||||
>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VirtioSharedMemory {
|
||||
pub offset: u64,
|
||||
pub len: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VirtioSharedMemoryList {
|
||||
pub addr: GuestAddress,
|
||||
pub len: GuestUsize,
|
||||
pub region_list: Vec<VirtioSharedMemory>,
|
||||
}
|
||||
|
||||
/// Trait for virtio devices to be driven by a virtio transport.
|
||||
///
|
||||
@@ -47,9 +67,8 @@ pub trait VirtioDevice: Send {
|
||||
/// Activates this device for real usage.
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_evt: Arc<VirtioInterrupt>,
|
||||
status: Arc<AtomicUsize>,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult;
|
||||
@@ -60,13 +79,8 @@ pub trait VirtioDevice: Send {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns any additional BAR configuration required by the device.
|
||||
fn get_device_bars(&self) -> Vec<PciBarConfiguration> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Returns any additional capabilities required by the device.
|
||||
fn get_device_caps(&self) -> Vec<Box<dyn PciCapability>> {
|
||||
Vec::new()
|
||||
/// Returns the list of shared memory regions required by the device.
|
||||
fn get_shm_regions(&self) -> Option<VirtioSharedMemoryList> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,438 +0,0 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType,
|
||||
INTERRUPT_STATUS_USED_RING,
|
||||
};
|
||||
use crate::{VirtioInterrupt, VIRTIO_F_VERSION_1_BITMASK};
|
||||
use epoll;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std::cmp;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use vhost_rs::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
|
||||
use vhost_rs::vhost_user::{Master, VhostUserMaster};
|
||||
use vhost_rs::{VhostBackend, VhostUserMemoryRegionInfo, VringConfigData};
|
||||
use vm_memory::{Address, Error as MmapError, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
|
||||
use vmm_sys_util::EventFd;
|
||||
|
||||
const CONFIG_SPACE_TAG_SIZE: usize = 36;
|
||||
const CONFIG_SPACE_NUM_QUEUES_SIZE: usize = 4;
|
||||
const CONFIG_SPACE_SIZE: usize = CONFIG_SPACE_TAG_SIZE + CONFIG_SPACE_NUM_QUEUES_SIZE;
|
||||
const NUM_QUEUE_OFFSET: usize = 1;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// common
|
||||
|
||||
/// Invalid descriptor table address.
|
||||
DescriptorTableAddress,
|
||||
/// Invalid used address.
|
||||
UsedAddress,
|
||||
/// Invalid available address.
|
||||
AvailAddress,
|
||||
|
||||
/// vhost
|
||||
|
||||
/// Creating kill eventfd failed.
|
||||
CreateKillEventFd(io::Error),
|
||||
/// Cloning kill eventfd failed.
|
||||
CloneKillEventFd(io::Error),
|
||||
/// Error while polling for events.
|
||||
PollError(io::Error),
|
||||
/// Failed to create irq eventfd.
|
||||
IrqEventCreate(io::Error),
|
||||
/// Failed to read vhost eventfd.
|
||||
VhostIrqRead(io::Error),
|
||||
|
||||
/// vhost-user
|
||||
|
||||
/// Connection to socket failed.
|
||||
VhostUserConnect(vhost_rs::Error),
|
||||
/// Get features failed.
|
||||
VhostUserGetFeatures(vhost_rs::Error),
|
||||
/// Get protocol features failed.
|
||||
VhostUserGetProtocolFeatures(vhost_rs::Error),
|
||||
/// Set owner failed.
|
||||
VhostUserSetOwner(vhost_rs::Error),
|
||||
/// Set features failed.
|
||||
VhostUserSetFeatures(vhost_rs::Error),
|
||||
/// Set protocol features failed.
|
||||
VhostUserSetProtocolFeatures(vhost_rs::Error),
|
||||
/// Set mem table failed.
|
||||
VhostUserSetMemTable(vhost_rs::Error),
|
||||
/// Set vring num failed.
|
||||
VhostUserSetVringNum(vhost_rs::Error),
|
||||
/// Set vring addr failed.
|
||||
VhostUserSetVringAddr(vhost_rs::Error),
|
||||
/// Set vring base failed.
|
||||
VhostUserSetVringBase(vhost_rs::Error),
|
||||
/// Set vring call failed.
|
||||
VhostUserSetVringCall(vhost_rs::Error),
|
||||
/// Set vring kick failed.
|
||||
VhostUserSetVringKick(vhost_rs::Error),
|
||||
|
||||
/// Invalid features provided from vhost-user backend.
|
||||
InvalidFeatures,
|
||||
|
||||
/// Missing file descriptor.
|
||||
FdMissing,
|
||||
|
||||
/// Failure going through memory regions.
|
||||
MemoryRegions(MmapError),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
struct FsEpollHandler {
|
||||
vu_call_evt_queue_list: Vec<(EventFd, Queue)>,
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
kill_evt: EventFd,
|
||||
}
|
||||
|
||||
impl FsEpollHandler {
|
||||
fn run(&mut self) -> result::Result<(), DeviceError> {
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
||||
|
||||
for (evt_index, vu_call_evt_queue) in self.vu_call_evt_queue_list.iter().enumerate() {
|
||||
// Add events
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
vu_call_evt_queue.0.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, evt_index as u64),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
}
|
||||
|
||||
let kill_evt_index = self.vu_call_evt_queue_list.len();
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.kill_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, kill_evt_index as u64),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
|
||||
const EPOLL_EVENTS_LEN: usize = 100;
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events =
|
||||
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let ev_type = event.data as usize;
|
||||
|
||||
match ev_type {
|
||||
x if (x < kill_evt_index) => {
|
||||
if let Err(e) = self.vu_call_evt_queue_list[x].0.read() {
|
||||
error!("Failed to get queue event: {:?}", e);
|
||||
break 'epoll;
|
||||
} else {
|
||||
self.interrupt_status
|
||||
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
|
||||
if let Err(e) = (self.interrupt_cb)(&self.vu_call_evt_queue_list[x].1) {
|
||||
error!(
|
||||
"Failed to signal used queue: {:?}",
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
);
|
||||
break 'epoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
x if (x == kill_evt_index) => {
|
||||
debug!("KILL_EVENT received, stopping epoll loop");
|
||||
break 'epoll;
|
||||
}
|
||||
_ => {
|
||||
error!("Unknown event for virtio-fs");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Fs {
|
||||
vu: Master,
|
||||
queue_sizes: Vec<u16>,
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
config_space: Vec<u8>,
|
||||
kill_evt: Option<EventFd>,
|
||||
}
|
||||
|
||||
impl Fs {
|
||||
/// Create a new virtio-fs device.
|
||||
pub fn new(path: &str, tag: &str, req_num_queues: usize, queue_size: u16) -> Result<Fs> {
|
||||
// Calculate the actual number of queues needed.
|
||||
let num_queues = NUM_QUEUE_OFFSET + req_num_queues;
|
||||
// Connect to the vhost-user socket.
|
||||
let mut master =
|
||||
Master::connect(path, num_queues as u64).map_err(Error::VhostUserConnect)?;
|
||||
// Retrieve available features only when connecting the first time.
|
||||
let mut avail_features = master.get_features().map_err(Error::VhostUserGetFeatures)?;
|
||||
// Let only ack features we expect, that is VIRTIO_F_VERSION_1.
|
||||
if (avail_features & VIRTIO_F_VERSION_1_BITMASK) != VIRTIO_F_VERSION_1_BITMASK {
|
||||
return Err(Error::InvalidFeatures);
|
||||
}
|
||||
avail_features =
|
||||
VIRTIO_F_VERSION_1_BITMASK | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
master
|
||||
.set_features(avail_features)
|
||||
.map_err(Error::VhostUserSetFeatures)?;
|
||||
// Identify if protocol features are supported by the slave.
|
||||
if (avail_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits())
|
||||
== VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
|
||||
{
|
||||
let mut protocol_features = master
|
||||
.get_protocol_features()
|
||||
.map_err(Error::VhostUserGetProtocolFeatures)?;
|
||||
protocol_features &= VhostUserProtocolFeatures::MQ;
|
||||
master
|
||||
.set_protocol_features(protocol_features)
|
||||
.map_err(Error::VhostUserSetProtocolFeatures)?;
|
||||
}
|
||||
// Create virtio device config space.
|
||||
// First by adding the tag.
|
||||
let mut config_space = tag.to_string().into_bytes();
|
||||
config_space.resize(CONFIG_SPACE_SIZE, 0);
|
||||
// And then by copying the number of queues.
|
||||
let num_queues_slice = (req_num_queues as u32).to_le_bytes();
|
||||
config_space[CONFIG_SPACE_TAG_SIZE..CONFIG_SPACE_SIZE].copy_from_slice(&num_queues_slice);
|
||||
|
||||
Ok(Fs {
|
||||
vu: master,
|
||||
queue_sizes: vec![queue_size; num_queues],
|
||||
avail_features,
|
||||
acked_features: 0u64,
|
||||
config_space,
|
||||
kill_evt: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn setup_vu(
|
||||
&mut self,
|
||||
mem: &GuestMemoryMmap,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
) -> Result<Vec<(EventFd, Queue)>> {
|
||||
// Set vhost-user owner.
|
||||
self.vu.set_owner().map_err(Error::VhostUserSetOwner)?;
|
||||
|
||||
// Set backend features.
|
||||
self.vu
|
||||
.set_features(self.acked_features)
|
||||
.map_err(Error::VhostUserSetFeatures)?;
|
||||
|
||||
let mut regions: Vec<VhostUserMemoryRegionInfo> = Vec::new();
|
||||
|
||||
mem.with_regions_mut(|_, region| {
|
||||
let (mmap_handle, mmap_offset) = match region.file_offset() {
|
||||
Some(fo) => (fo.file().as_raw_fd(), fo.start()),
|
||||
None => return Err(MmapError::NoMemoryRegion),
|
||||
};
|
||||
|
||||
let vu_mem_reg = VhostUserMemoryRegionInfo {
|
||||
guest_phys_addr: region.start_addr().raw_value(),
|
||||
memory_size: region.len() as u64,
|
||||
userspace_addr: region.as_ptr() as u64,
|
||||
mmap_offset,
|
||||
mmap_handle,
|
||||
};
|
||||
|
||||
regions.push(vu_mem_reg);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(Error::MemoryRegions)?;
|
||||
|
||||
self.vu
|
||||
.set_mem_table(regions.as_slice())
|
||||
.map_err(Error::VhostUserSetMemTable)?;
|
||||
|
||||
let mut result = Vec::new();
|
||||
for (queue_index, queue) in queues.into_iter().enumerate() {
|
||||
self.vu
|
||||
.set_vring_num(queue_index, queue.get_max_size())
|
||||
.map_err(Error::VhostUserSetVringNum)?;
|
||||
|
||||
let vring_config = VringConfigData {
|
||||
queue_max_size: queue.get_max_size(),
|
||||
queue_size: queue.size,
|
||||
flags: 0u32,
|
||||
desc_table_addr: mem
|
||||
.get_host_address(queue.desc_table)
|
||||
.ok_or_else(|| Error::DescriptorTableAddress)?
|
||||
as u64,
|
||||
used_ring_addr: mem
|
||||
.get_host_address(queue.used_ring)
|
||||
.ok_or_else(|| Error::UsedAddress)? as u64,
|
||||
avail_ring_addr: mem
|
||||
.get_host_address(queue.avail_ring)
|
||||
.ok_or_else(|| Error::AvailAddress)? as u64,
|
||||
log_addr: None,
|
||||
};
|
||||
|
||||
self.vu
|
||||
.set_vring_addr(queue_index, &vring_config)
|
||||
.map_err(Error::VhostUserSetVringAddr)?;
|
||||
|
||||
self.vu
|
||||
.set_vring_base(queue_index, 0u16)
|
||||
.map_err(Error::VhostUserSetVringBase)?;
|
||||
|
||||
let vu_call_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::IrqEventCreate)?;
|
||||
|
||||
self.vu
|
||||
.set_vring_call(queue_index, &vu_call_evt)
|
||||
.map_err(Error::VhostUserSetVringCall)?;
|
||||
|
||||
result.push((vu_call_evt, queue));
|
||||
|
||||
self.vu
|
||||
.set_vring_kick(queue_index, &queue_evts[queue_index])
|
||||
.map_err(Error::VhostUserSetVringKick)?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Fs {
|
||||
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 Fs {
|
||||
fn device_type(&self) -> u32 {
|
||||
VirtioDeviceType::TYPE_FS as u32
|
||||
}
|
||||
|
||||
fn queue_max_sizes(&self) -> &[u16] {
|
||||
&self.queue_sizes.as_slice()
|
||||
}
|
||||
|
||||
fn features(&self, page: u32) -> u32 {
|
||||
match page {
|
||||
// Get the lower 32-bits of the features bitfield.
|
||||
0 => self.avail_features as u32,
|
||||
// Get the upper 32-bits of the features bitfield.
|
||||
1 => (self.avail_features >> 32) as u32,
|
||||
_ => {
|
||||
warn!("fs: Received request for unknown features page: {}", page);
|
||||
0u32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ack_features(&mut self, page: u32, value: u32) {
|
||||
let mut v = match page {
|
||||
0 => u64::from(value),
|
||||
1 => u64::from(value) << 32,
|
||||
_ => {
|
||||
warn!("fs: Cannot acknowledge unknown features page: {}", page);
|
||||
0u64
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the guest is ACK'ing a feature that we didn't claim to have.
|
||||
let unrequested_features = v & !self.avail_features;
|
||||
if unrequested_features != 0 {
|
||||
warn!("fs: virtio-fs got unknown feature ack: {:x}", v);
|
||||
|
||||
// Don't count these features as acked.
|
||||
v &= !unrequested_features;
|
||||
}
|
||||
self.acked_features |= v;
|
||||
}
|
||||
|
||||
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
|
||||
let config_len = self.config_space.len() as u64;
|
||||
if offset >= config_len {
|
||||
error!("Failed to read config space");
|
||||
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_all(&self.config_space[offset as usize..cmp::min(end, config_len) as usize])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn write_config(&mut self, offset: u64, data: &[u8]) {
|
||||
let data_len = data.len() as u64;
|
||||
let config_len = self.config_space.len() as u64;
|
||||
if offset + data_len > config_len {
|
||||
error!("Failed to write config space");
|
||||
return;
|
||||
}
|
||||
let (_, right) = self.config_space.split_at_mut(offset as usize);
|
||||
right.copy_from_slice(&data[..]);
|
||||
}
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryMmap,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
status: Arc<AtomicUsize>,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
if queues.len() != self.queue_sizes.len() || queue_evts.len() != self.queue_sizes.len() {
|
||||
error!(
|
||||
"Cannot perform activate. Expected {} queue(s), got {}",
|
||||
self.queue_sizes.len(),
|
||||
queues.len()
|
||||
);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
let (self_kill_evt, kill_evt) =
|
||||
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed creating kill EventFd pair: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
let vu_call_evt_queue_list = self
|
||||
.setup_vu(&mem, queues, queue_evts)
|
||||
.map_err(ActivateError::VhostUserSetup)?;
|
||||
|
||||
let mut handler = FsEpollHandler {
|
||||
vu_call_evt_queue_list,
|
||||
interrupt_status: status,
|
||||
interrupt_cb,
|
||||
kill_evt,
|
||||
};
|
||||
|
||||
let worker_result = thread::Builder::new()
|
||||
.name("virtio_fs".to_string())
|
||||
.spawn(move || handler.run());
|
||||
|
||||
if let Err(e) = worker_result {
|
||||
error!("failed to spawn virtio_blk worker: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ extern crate epoll;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate pci;
|
||||
extern crate vhost_rs;
|
||||
extern crate virtio_bindings;
|
||||
extern crate vm_memory;
|
||||
|
||||
@@ -22,18 +23,17 @@ use std::io;
|
||||
mod block;
|
||||
mod console;
|
||||
mod device;
|
||||
pub mod fs;
|
||||
pub mod net;
|
||||
mod pmem;
|
||||
mod queue;
|
||||
mod rng;
|
||||
|
||||
pub mod transport;
|
||||
pub mod vhost_user;
|
||||
|
||||
pub use self::block::*;
|
||||
pub use self::console::*;
|
||||
pub use self::device::*;
|
||||
pub use self::fs::*;
|
||||
pub use self::net::*;
|
||||
pub use self::pmem::*;
|
||||
pub use self::queue::*;
|
||||
@@ -47,7 +47,6 @@ const DEVICE_FEATURES_OK: u32 = 0x08;
|
||||
const DEVICE_FAILED: u32 = 0x80;
|
||||
|
||||
const VIRTIO_F_VERSION_1: u32 = 32;
|
||||
const VIRTIO_F_VERSION_1_BITMASK: u64 = 1 << VIRTIO_F_VERSION_1;
|
||||
|
||||
// Types taken from linux/virtio_ids.h
|
||||
#[derive(Copy, Clone)]
|
||||
@@ -117,9 +116,16 @@ const INTERRUPT_STATUS_CONFIG_CHANGED: u32 = 0x2;
|
||||
pub enum ActivateError {
|
||||
EpollCtl(std::io::Error),
|
||||
BadActivate,
|
||||
|
||||
/// Queue number is not correct
|
||||
BadQueueNum,
|
||||
/// Failed to clone Kill event
|
||||
CloneKillEventFd,
|
||||
/// Failed to create Vhost-user interrupt eventfd
|
||||
VhostIrqCreate,
|
||||
/// Failed to setup vhost-user daemon.
|
||||
VhostUserSetup(fs::Error),
|
||||
VhostUserSetup(vhost_user::Error),
|
||||
/// Failed to setup vhost-user daemon.
|
||||
VhostUserNetSetup(vhost_user::Error),
|
||||
}
|
||||
|
||||
pub type ActivateResult = std::result::Result<(), ActivateError>;
|
||||
@@ -143,4 +149,5 @@ pub enum Error {
|
||||
EpollCreateFd(io::Error),
|
||||
EpollCtl(io::Error),
|
||||
EpollWait(io::Error),
|
||||
FailedSignalingDriver(io::Error),
|
||||
}
|
||||
|
||||
@@ -13,10 +13,9 @@ use std::io::Read;
|
||||
use std::io::{self, Write};
|
||||
use std::mem;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
use std::vec::Vec;
|
||||
|
||||
@@ -25,13 +24,13 @@ use net_gen;
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
|
||||
INTERRUPT_STATUS_USED_RING,
|
||||
VirtioInterruptType,
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use net_util::{MacAddr, Tap, TapError, MAC_ADDR_LEN};
|
||||
use virtio_bindings::virtio_net::*;
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// The maximum buffer size when segmentation offload is enabled. This
|
||||
/// includes the 12-byte virtio net header.
|
||||
@@ -116,20 +115,19 @@ fn vnet_hdr_len() -> usize {
|
||||
}
|
||||
|
||||
struct NetEpollHandler {
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
tap: Tap,
|
||||
rx: RxVirtio,
|
||||
tx: TxVirtio,
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
kill_evt: EventFd,
|
||||
epoll_fd: RawFd,
|
||||
rx_tap_listening: bool,
|
||||
}
|
||||
|
||||
impl NetEpollHandler {
|
||||
fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_status
|
||||
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
|
||||
(self.interrupt_cb)(queue).map_err(|e| {
|
||||
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(queue)).map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
@@ -139,9 +137,15 @@ impl NetEpollHandler {
|
||||
// if a buffer was used, and false if the frame must be deferred until a buffer
|
||||
// is made available by the driver.
|
||||
fn rx_single_frame(&mut self) -> bool {
|
||||
let mut next_desc = self.rx.queue.iter(&self.mem).next();
|
||||
let mem = self.mem.read().unwrap();
|
||||
let mut next_desc = self.rx.queue.iter(&mem).next();
|
||||
|
||||
if next_desc.is_none() {
|
||||
// Queue has no available descriptors
|
||||
if self.rx_tap_listening {
|
||||
self.unregister_tap_rx_listener().unwrap();
|
||||
self.rx_tap_listening = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -158,7 +162,7 @@ impl NetEpollHandler {
|
||||
}
|
||||
let limit = cmp::min(write_count + desc.len as usize, self.rx.bytes_read);
|
||||
let source_slice = &self.rx.frame_buf[write_count..limit];
|
||||
let write_result = self.mem.write_slice(source_slice, desc.addr);
|
||||
let write_result = mem.write_slice(source_slice, desc.addr);
|
||||
|
||||
match write_result {
|
||||
Ok(_) => {
|
||||
@@ -182,9 +186,7 @@ impl NetEpollHandler {
|
||||
}
|
||||
}
|
||||
|
||||
self.rx
|
||||
.queue
|
||||
.add_used(&self.mem, head_index, write_count as u32);
|
||||
self.rx.queue.add_used(&mem, head_index, write_count as u32);
|
||||
|
||||
// Mark that we have at least one pending packet and we need to interrupt the guest.
|
||||
self.rx.deferred_irqs = true;
|
||||
@@ -244,7 +246,8 @@ impl NetEpollHandler {
|
||||
}
|
||||
|
||||
fn process_tx(&mut self) -> result::Result<(), DeviceError> {
|
||||
while let Some(avail_desc) = self.tx.queue.iter(&self.mem).next() {
|
||||
let mem = self.mem.read().unwrap();
|
||||
while let Some(avail_desc) = self.tx.queue.iter(&mem).next() {
|
||||
let head_index = avail_desc.index;
|
||||
let mut read_count = 0;
|
||||
let mut next_desc = Some(avail_desc);
|
||||
@@ -266,13 +269,14 @@ impl NetEpollHandler {
|
||||
for (desc_addr, desc_len) in self.tx.iovec.drain(..) {
|
||||
let limit = cmp::min((read_count + desc_len) as usize, self.tx.frame_buf.len());
|
||||
|
||||
let read_result = self.mem.read_slice(
|
||||
let read_result = mem.read_slice(
|
||||
&mut self.tx.frame_buf[read_count..limit as usize],
|
||||
desc_addr,
|
||||
);
|
||||
match read_result {
|
||||
Ok(_) => {
|
||||
read_count += limit;
|
||||
// Increment by number of bytes actually read
|
||||
read_count += limit - read_count;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to read slice: {:?}", e);
|
||||
@@ -289,7 +293,7 @@ impl NetEpollHandler {
|
||||
}
|
||||
};
|
||||
|
||||
self.tx.queue.add_used(&self.mem, head_index, 0);
|
||||
self.tx.queue.add_used(&mem, head_index, 0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -299,34 +303,49 @@ impl NetEpollHandler {
|
||||
self.tap.read(&mut self.rx.frame_buf)
|
||||
}
|
||||
|
||||
fn register_tap_rx_listener(&self) -> std::result::Result<(), std::io::Error> {
|
||||
epoll::ctl(
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.tap.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_TAP_EVENT)),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unregister_tap_rx_listener(&self) -> std::result::Result<(), std::io::Error> {
|
||||
epoll::ctl(
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_DEL,
|
||||
self.tap.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_TAP_EVENT)),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&mut self) -> result::Result<(), DeviceError> {
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
||||
|
||||
self.epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
||||
// Add events
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.rx.queue_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_QUEUE_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.tx.queue_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(TX_QUEUE_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
self.register_tap_rx_listener()
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
self.rx_tap_listening = true;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.tap.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_TAP_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.kill_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
|
||||
@@ -337,8 +356,22 @@ impl NetEpollHandler {
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events =
|
||||
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
|
||||
let num_events = match epoll::wait(self.epoll_fd, -1, &mut events[..]) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
// It's well defined from the epoll_wait() syscall
|
||||
// documentation that the epoll loop can be interrupted
|
||||
// before any of the requested events occurred or the
|
||||
// timeout expired. In both those cases, epoll_wait()
|
||||
// returns an error of type EINTR, but this should not
|
||||
// be considered as a regular error. Instead it is more
|
||||
// appropriate to retry, by calling into epoll_wait().
|
||||
continue;
|
||||
}
|
||||
return Err(DeviceError::EpollWait(e));
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let ev_type = event.data as u16;
|
||||
@@ -352,6 +385,10 @@ impl NetEpollHandler {
|
||||
}
|
||||
|
||||
self.resume_rx().unwrap();
|
||||
if !self.rx_tap_listening {
|
||||
self.register_tap_rx_listener().unwrap();
|
||||
self.rx_tap_listening = true;
|
||||
}
|
||||
}
|
||||
TX_QUEUE_EVENT => {
|
||||
debug!("TX_QUEUE_EVENT received");
|
||||
@@ -536,9 +573,8 @@ impl VirtioDevice for Net {
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
status: Arc<AtomicUsize>,
|
||||
mut queues: Vec<Queue>,
|
||||
mut queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
@@ -571,9 +607,10 @@ impl VirtioDevice for Net {
|
||||
tap,
|
||||
rx: RxVirtio::new(rx_queue, rx_queue_evt),
|
||||
tx: TxVirtio::new(tx_queue, tx_queue_evt),
|
||||
interrupt_status: status,
|
||||
interrupt_cb,
|
||||
kill_evt,
|
||||
epoll_fd: 0,
|
||||
rx_tap_listening: false,
|
||||
};
|
||||
|
||||
let worker_result = thread::Builder::new()
|
||||
|
||||
@@ -15,20 +15,19 @@ use std::io::{self, Write};
|
||||
use std::mem::size_of;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
|
||||
VirtioDeviceType, INTERRUPT_STATUS_USED_RING, VIRTIO_F_VERSION_1,
|
||||
VirtioDeviceType, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use crate::{VirtioInterrupt, VirtioInterruptType};
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap, GuestUsize,
|
||||
};
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
const NUM_QUEUES: usize = 1;
|
||||
@@ -155,9 +154,8 @@ impl Request {
|
||||
|
||||
struct PmemEpollHandler {
|
||||
queue: Queue,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
disk: File,
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
queue_evt: EventFd,
|
||||
kill_evt: EventFd,
|
||||
@@ -167,8 +165,9 @@ impl PmemEpollHandler {
|
||||
fn process_queue(&mut self) -> bool {
|
||||
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
|
||||
let mut used_count = 0;
|
||||
for avail_desc in self.queue.iter(&self.mem) {
|
||||
let len = match Request::parse(&avail_desc, &self.mem) {
|
||||
let mem = self.mem.read().unwrap();
|
||||
for avail_desc in self.queue.iter(&mem) {
|
||||
let len = match Request::parse(&avail_desc, &mem) {
|
||||
Ok(ref req) if (req.type_ == RequestType::Flush) => {
|
||||
let status_code = match self.disk.sync_all() {
|
||||
Ok(()) => VIRTIO_PMEM_RESP_TYPE_OK,
|
||||
@@ -179,7 +178,7 @@ impl PmemEpollHandler {
|
||||
};
|
||||
|
||||
let resp = VirtioPmemResp { ret: status_code };
|
||||
match self.mem.write_obj(resp, req.status_addr) {
|
||||
match mem.write_obj(resp, req.status_addr) {
|
||||
Ok(_) => size_of::<VirtioPmemResp>() as u32,
|
||||
Err(e) => {
|
||||
error!("bad guest memory address: {}", e);
|
||||
@@ -203,15 +202,13 @@ impl PmemEpollHandler {
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
self.queue.add_used(&self.mem, desc_index, len);
|
||||
self.queue.add_used(&mem, desc_index, len);
|
||||
}
|
||||
used_count > 0
|
||||
}
|
||||
|
||||
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_status
|
||||
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
|
||||
(self.interrupt_cb)(&self.queue).map_err(|e| {
|
||||
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queue)).map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
@@ -241,8 +238,22 @@ impl PmemEpollHandler {
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events =
|
||||
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
|
||||
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
// It's well defined from the epoll_wait() syscall
|
||||
// documentation that the epoll loop can be interrupted
|
||||
// before any of the requested events occurred or the
|
||||
// timeout expired. In both those cases, epoll_wait()
|
||||
// returns an error of type EINTR, but this should not
|
||||
// be considered as a regular error. Instead it is more
|
||||
// appropriate to retry, by calling into epoll_wait().
|
||||
continue;
|
||||
}
|
||||
return Err(DeviceError::EpollWait(e));
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let ev_type = event.data as u16;
|
||||
@@ -372,9 +383,8 @@ impl VirtioDevice for Pmem {
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
status: Arc<AtomicUsize>,
|
||||
mut queues: Vec<Queue>,
|
||||
mut queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
@@ -402,7 +412,6 @@ impl VirtioDevice for Pmem {
|
||||
queue: queues.remove(0),
|
||||
mem,
|
||||
disk,
|
||||
interrupt_status: status,
|
||||
interrupt_cb,
|
||||
queue_evt: queue_evts.remove(0),
|
||||
kill_evt,
|
||||
|
||||
@@ -9,18 +9,17 @@ use std::fs::File;
|
||||
use std::io;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
|
||||
INTERRUPT_STATUS_USED_RING, VIRTIO_F_VERSION_1,
|
||||
VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use crate::{VirtioInterrupt, VirtioInterruptType};
|
||||
use vm_memory::{Bytes, GuestMemoryMmap};
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
const NUM_QUEUES: usize = 1;
|
||||
@@ -33,9 +32,8 @@ const KILL_EVENT: DeviceEventT = 1;
|
||||
|
||||
struct RngEpollHandler {
|
||||
queues: Vec<Queue>,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
random_file: File,
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
queue_evt: EventFd,
|
||||
kill_evt: EventFd,
|
||||
@@ -47,14 +45,14 @@ impl RngEpollHandler {
|
||||
|
||||
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 mem = self.mem.read().unwrap();
|
||||
for avail_desc in queue.iter(&mem) {
|
||||
let mut len = 0;
|
||||
|
||||
// Drivers can only read from the random device.
|
||||
if avail_desc.is_write_only() {
|
||||
// Fill the read with data from the random device on the host.
|
||||
if self
|
||||
.mem
|
||||
if mem
|
||||
.read_from(
|
||||
avail_desc.addr,
|
||||
&mut self.random_file,
|
||||
@@ -71,15 +69,13 @@ impl RngEpollHandler {
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
queue.add_used(&self.mem, desc_index, len);
|
||||
queue.add_used(&mem, desc_index, len);
|
||||
}
|
||||
used_count > 0
|
||||
}
|
||||
|
||||
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_status
|
||||
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
|
||||
(self.interrupt_cb)(&self.queues[0]).map_err(|e| {
|
||||
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queues[0])).map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
@@ -109,8 +105,22 @@ impl RngEpollHandler {
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events =
|
||||
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
|
||||
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
// It's well defined from the epoll_wait() syscall
|
||||
// documentation that the epoll loop can be interrupted
|
||||
// before any of the requested events occurred or the
|
||||
// timeout expired. In both those cases, epoll_wait()
|
||||
// returns an error of type EINTR, but this should not
|
||||
// be considered as a regular error. Instead it is more
|
||||
// appropriate to retry, by calling into epoll_wait().
|
||||
continue;
|
||||
}
|
||||
return Err(DeviceError::EpollWait(e));
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let ev_type = event.data as u16;
|
||||
@@ -227,9 +237,8 @@ impl VirtioDevice for Rng {
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
status: Arc<AtomicUsize>,
|
||||
queues: Vec<Queue>,
|
||||
mut queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
@@ -257,7 +266,6 @@ impl VirtioDevice for Rng {
|
||||
queues,
|
||||
mem,
|
||||
random_file,
|
||||
interrupt_status: status,
|
||||
interrupt_cb,
|
||||
queue_evt: queue_evts.remove(0),
|
||||
kill_evt,
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
extern crate byteorder;
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
use std::sync::Arc;
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
use crate::{Queue, VirtioDevice};
|
||||
@@ -40,7 +42,7 @@ pub struct VirtioPciCommonConfig {
|
||||
pub device_feature_select: u32,
|
||||
pub driver_feature_select: u32,
|
||||
pub queue_select: u16,
|
||||
pub msix_config: u16,
|
||||
pub msix_config: Arc<AtomicU16>,
|
||||
}
|
||||
|
||||
impl VirtioPciCommonConfig {
|
||||
@@ -120,7 +122,7 @@ impl VirtioPciCommonConfig {
|
||||
fn read_common_config_word(&self, offset: u64, queues: &[Queue]) -> u16 {
|
||||
debug!("read_common_config_word: offset 0x{:x}", offset);
|
||||
match offset {
|
||||
0x10 => self.msix_config,
|
||||
0x10 => self.msix_config.load(Ordering::SeqCst),
|
||||
0x12 => queues.len() as u16, // num_queues
|
||||
0x16 => self.queue_select,
|
||||
0x18 => self.with_queue(queues, |q| q.size).unwrap_or(0),
|
||||
@@ -143,7 +145,7 @@ impl VirtioPciCommonConfig {
|
||||
fn write_common_config_word(&mut self, offset: u64, value: u16, queues: &mut Vec<Queue>) {
|
||||
debug!("write_common_config_word: offset 0x{:x}", offset);
|
||||
match offset {
|
||||
0x10 => self.msix_config = value,
|
||||
0x10 => self.msix_config.store(value, Ordering::SeqCst),
|
||||
0x16 => self.queue_select = value,
|
||||
0x18 => self.with_queue_mut(queues, |q| q.size = value),
|
||||
0x1a => self.with_queue_mut(queues, |q| q.vector = value),
|
||||
@@ -252,10 +254,9 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{ActivateResult, VirtioInterrupt};
|
||||
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
struct DummyDevice(u32);
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
@@ -270,9 +271,8 @@ mod tests {
|
||||
}
|
||||
fn activate(
|
||||
&mut self,
|
||||
_mem: GuestMemoryMmap,
|
||||
_mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
_interrupt_evt: Arc<VirtioInterrupt>,
|
||||
_status: Arc<AtomicUsize>,
|
||||
_queues: Vec<Queue>,
|
||||
_queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
@@ -298,7 +298,7 @@ mod tests {
|
||||
device_feature_select: 0x0,
|
||||
driver_feature_select: 0x0,
|
||||
queue_select: 0xff,
|
||||
msix_config: 0,
|
||||
msix_config: Arc::new(AtomicU16::new(0)),
|
||||
};
|
||||
|
||||
let dev = &mut DummyDevice(0) as &mut dyn VirtioDevice;
|
||||
|
||||
@@ -13,9 +13,8 @@ extern crate vm_memory;
|
||||
extern crate vmm_sys_util;
|
||||
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::{AtomicU16, AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
use devices::BusDevice;
|
||||
use pci::{
|
||||
@@ -26,12 +25,13 @@ use pci::{
|
||||
};
|
||||
use vm_allocator::SystemAllocator;
|
||||
use vm_memory::{Address, ByteValued, GuestAddress, GuestMemoryMmap, GuestUsize, Le32};
|
||||
use vmm_sys_util::{EventFd, Result};
|
||||
use vmm_sys_util::{errno::Result, eventfd::EventFd};
|
||||
|
||||
use super::VirtioPciCommonConfig;
|
||||
use crate::{
|
||||
Queue, VirtioDevice, VirtioDeviceType, VirtioInterrupt, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER,
|
||||
DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK, DEVICE_INIT,
|
||||
Queue, VirtioDevice, VirtioDeviceType, VirtioInterrupt, VirtioInterruptType,
|
||||
DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK,
|
||||
DEVICE_INIT, INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING,
|
||||
};
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
@@ -41,6 +41,7 @@ enum PciCapabilityType {
|
||||
IsrConfig = 3,
|
||||
DeviceConfig = 4,
|
||||
PciConfig = 5,
|
||||
SharedMemoryConfig = 8,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -50,7 +51,8 @@ struct VirtioPciCap {
|
||||
cap_len: u8, // Generic PCI field: capability length
|
||||
cfg_type: u8, // Identifies the structure.
|
||||
pci_bar: u8, // Where to find it.
|
||||
padding: [u8; 3], // Pad to full dword.
|
||||
id: u8, // Multiple capabilities of the same type
|
||||
padding: [u8; 2], // Pad to full dword.
|
||||
offset: Le32, // Offset within bar.
|
||||
length: Le32, // Length of the structure, in bytes.
|
||||
}
|
||||
@@ -67,15 +69,16 @@ impl PciCapability for VirtioPciCap {
|
||||
}
|
||||
}
|
||||
|
||||
const VIRTIO_PCI_CAPABILITY_BYTES: u8 = 16;
|
||||
const VIRTIO_PCI_CAP_LEN_OFFSET: u8 = 2;
|
||||
|
||||
impl VirtioPciCap {
|
||||
pub fn new(cfg_type: PciCapabilityType, pci_bar: u8, offset: u32, length: u32) -> Self {
|
||||
VirtioPciCap {
|
||||
cap_len: VIRTIO_PCI_CAPABILITY_BYTES,
|
||||
cap_len: (std::mem::size_of::<VirtioPciCap>() as u8) + VIRTIO_PCI_CAP_LEN_OFFSET,
|
||||
cfg_type: cfg_type as u8,
|
||||
pci_bar,
|
||||
padding: [0; 3],
|
||||
id: 0,
|
||||
padding: [0; 2],
|
||||
offset: Le32::from(offset),
|
||||
length: Le32::from(length),
|
||||
}
|
||||
@@ -112,10 +115,12 @@ impl VirtioPciNotifyCap {
|
||||
) -> Self {
|
||||
VirtioPciNotifyCap {
|
||||
cap: VirtioPciCap {
|
||||
cap_len: std::mem::size_of::<VirtioPciNotifyCap>() as u8,
|
||||
cap_len: (std::mem::size_of::<VirtioPciNotifyCap>() as u8)
|
||||
+ VIRTIO_PCI_CAP_LEN_OFFSET,
|
||||
cfg_type: cfg_type as u8,
|
||||
pci_bar,
|
||||
padding: [0; 3],
|
||||
id: 0,
|
||||
padding: [0; 2],
|
||||
offset: Le32::from(offset),
|
||||
length: Le32::from(length),
|
||||
},
|
||||
@@ -124,6 +129,45 @@ impl VirtioPciNotifyCap {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
struct VirtioPciCap64 {
|
||||
cap: VirtioPciCap,
|
||||
offset_hi: Le32,
|
||||
length_hi: Le32,
|
||||
}
|
||||
// It is safe to implement ByteValued. All members are simple numbers and any value is valid.
|
||||
unsafe impl ByteValued for VirtioPciCap64 {}
|
||||
|
||||
impl PciCapability for VirtioPciCap64 {
|
||||
fn bytes(&self) -> &[u8] {
|
||||
self.as_slice()
|
||||
}
|
||||
|
||||
fn id(&self) -> PciCapabilityID {
|
||||
PciCapabilityID::VendorSpecific
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioPciCap64 {
|
||||
pub fn new(cfg_type: PciCapabilityType, pci_bar: u8, id: u8, offset: u64, length: u64) -> Self {
|
||||
VirtioPciCap64 {
|
||||
cap: VirtioPciCap {
|
||||
cap_len: (std::mem::size_of::<VirtioPciCap64>() as u8) + VIRTIO_PCI_CAP_LEN_OFFSET,
|
||||
cfg_type: cfg_type as u8,
|
||||
pci_bar,
|
||||
id,
|
||||
padding: [0; 2],
|
||||
offset: Le32::from(offset as u32),
|
||||
length: Le32::from(length as u32),
|
||||
},
|
||||
offset_hi: Le32::from((offset >> 32) as u32),
|
||||
length_hi: Le32::from((length >> 32) as u32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum PciVirtioSubclass {
|
||||
@@ -178,7 +222,7 @@ pub struct VirtioPciDevice {
|
||||
msix_num: u16,
|
||||
|
||||
// Virtio device reference and status
|
||||
device: Box<VirtioDevice>,
|
||||
device: Box<dyn VirtioDevice>,
|
||||
device_activated: bool,
|
||||
|
||||
// PCI interrupts.
|
||||
@@ -190,7 +234,7 @@ pub struct VirtioPciDevice {
|
||||
queue_evts: Vec<EventFd>,
|
||||
|
||||
// Guest memory
|
||||
memory: Option<GuestMemoryMmap>,
|
||||
memory: Option<Arc<RwLock<GuestMemoryMmap>>>,
|
||||
|
||||
// Setting PCI BAR
|
||||
settings_bar: u8,
|
||||
@@ -198,7 +242,11 @@ pub struct VirtioPciDevice {
|
||||
|
||||
impl VirtioPciDevice {
|
||||
/// Constructs a new PCI transport for the given virtio device.
|
||||
pub fn new(memory: GuestMemoryMmap, device: Box<VirtioDevice>, msix_num: u16) -> Result<Self> {
|
||||
pub fn new(
|
||||
memory: Arc<RwLock<GuestMemoryMmap>>,
|
||||
device: Box<dyn VirtioDevice>,
|
||||
msix_num: u16,
|
||||
) -> Result<Self> {
|
||||
let mut queue_evts = Vec::new();
|
||||
for _ in device.queue_max_sizes().iter() {
|
||||
queue_evts.push(EventFd::new(EFD_NONBLOCK)?)
|
||||
@@ -222,15 +270,15 @@ impl VirtioPciDevice {
|
||||
let (class, subclass) = match VirtioDeviceType::from(device.device_type()) {
|
||||
VirtioDeviceType::TYPE_NET => (
|
||||
PciClassCode::NetworkController,
|
||||
&PciNetworkControllerSubclass::EthernetController as &PciSubclass,
|
||||
&PciNetworkControllerSubclass::EthernetController as &dyn PciSubclass,
|
||||
),
|
||||
VirtioDeviceType::TYPE_BLOCK => (
|
||||
PciClassCode::MassStorage,
|
||||
&PciMassStorageSubclass::MassStorage as &PciSubclass,
|
||||
&PciMassStorageSubclass::MassStorage as &dyn PciSubclass,
|
||||
),
|
||||
_ => (
|
||||
PciClassCode::Other,
|
||||
&PciVirtioSubclass::NonTransitionalBase as &PciSubclass,
|
||||
&PciVirtioSubclass::NonTransitionalBase as &dyn PciSubclass,
|
||||
),
|
||||
};
|
||||
|
||||
@@ -254,7 +302,7 @@ impl VirtioPciDevice {
|
||||
device_feature_select: 0,
|
||||
driver_feature_select: 0,
|
||||
queue_select: 0,
|
||||
msix_config: 0,
|
||||
msix_config: Arc::new(AtomicU16::new(0)),
|
||||
},
|
||||
msix_config,
|
||||
msix_num,
|
||||
@@ -290,7 +338,7 @@ impl VirtioPciDevice {
|
||||
|
||||
fn are_queues_valid(&self) -> bool {
|
||||
if let Some(mem) = self.memory.as_ref() {
|
||||
self.queues.iter().all(|q| q.is_valid(mem))
|
||||
self.queues.iter().all(|q| q.is_valid(&mem.read().unwrap()))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -376,10 +424,20 @@ impl PciDevice for VirtioPciDevice {
|
||||
) {
|
||||
self.configuration.set_irq(irq_num as u8, irq_pin);
|
||||
|
||||
let cb = Arc::new(Box::new(move |_queue: &Queue| {
|
||||
let param = InterruptParameters { msix: None };
|
||||
(irq_cb)(param)
|
||||
}) as VirtioInterrupt);
|
||||
let interrupt_status = self.interrupt_status.clone();
|
||||
let cb = Arc::new(Box::new(
|
||||
move |int_type: &VirtioInterruptType, _queue: Option<&Queue>| {
|
||||
let param = InterruptParameters { msix: None };
|
||||
|
||||
let status = match int_type {
|
||||
VirtioInterruptType::Config => INTERRUPT_STATUS_CONFIG_CHANGED,
|
||||
VirtioInterruptType::Queue => INTERRUPT_STATUS_USED_RING,
|
||||
};
|
||||
interrupt_status.fetch_or(status as usize, Ordering::SeqCst);
|
||||
|
||||
(irq_cb)(param)
|
||||
},
|
||||
) as VirtioInterrupt);
|
||||
|
||||
self.interrupt_cb = Some(cb);
|
||||
}
|
||||
@@ -393,22 +451,44 @@ impl PciDevice for VirtioPciDevice {
|
||||
|
||||
let msix_config_clone = msix_config.clone();
|
||||
|
||||
let cb = Arc::new(Box::new(move |queue: &Queue| {
|
||||
let config = &mut msix_config_clone.lock().unwrap();
|
||||
let entry = &config.table_entries[queue.vector as usize];
|
||||
let common_config_msi_vector = self.common_config.msix_config.clone();
|
||||
let cb = Arc::new(Box::new(
|
||||
move |int_type: &VirtioInterruptType, queue: Option<&Queue>| {
|
||||
let vector = match int_type {
|
||||
VirtioInterruptType::Config => {
|
||||
common_config_msi_vector.load(Ordering::SeqCst)
|
||||
}
|
||||
VirtioInterruptType::Queue => {
|
||||
if let Some(q) = queue {
|
||||
q.vector
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// In case the vector control register associated with the entry
|
||||
// has its first bit set, this means the vector is masked and the
|
||||
// device should not inject the interrupt.
|
||||
// Instead, the Pending Bit Array table is updated to reflect there
|
||||
// is a pending interrupt for this specific vector.
|
||||
if config.masked() || entry.masked() {
|
||||
config.set_pba_bit(queue.vector, false);
|
||||
return Ok(());
|
||||
}
|
||||
let config = &mut msix_config_clone.lock().unwrap();
|
||||
let entry = &config.table_entries[vector as usize];
|
||||
|
||||
(msi_cb)(InterruptParameters { msix: Some(entry) })
|
||||
}) as VirtioInterrupt);
|
||||
// If MSI-X interrupts are not enabled for this device, then simply
|
||||
// ignore the interrupt.
|
||||
if !config.enabled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// In case the vector control register associated with the entry
|
||||
// has its first bit set, this means the vector is masked and the
|
||||
// device should not inject the interrupt.
|
||||
// Instead, the Pending Bit Array table is updated to reflect there
|
||||
// is a pending interrupt for this specific vector.
|
||||
if config.masked() || entry.masked() {
|
||||
config.set_pba_bit(vector, false);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
(msi_cb)(InterruptParameters { msix: Some(entry) })
|
||||
},
|
||||
) as VirtioInterrupt);
|
||||
|
||||
self.interrupt_cb = Some(cb);
|
||||
}
|
||||
@@ -471,20 +551,29 @@ impl PciDevice for VirtioPciDevice {
|
||||
// Once the BARs are allocated, the capabilities can be added to the PCI configuration.
|
||||
self.add_pci_capabilities(virtio_pci_bar)?;
|
||||
|
||||
// Allocate the device specific BARs.
|
||||
for config in self.device.get_device_bars() {
|
||||
let device_bar_addr = allocator
|
||||
.allocate_mmio_addresses(None, config.get_size(), None)
|
||||
.ok_or_else(|| PciDeviceError::IoAllocationFailed(config.get_size()))?;
|
||||
config.set_address(device_bar_addr.raw_value());
|
||||
let _device_bar = self.configuration.add_pci_bar(&config).map_err(|e| {
|
||||
PciDeviceError::IoRegistrationFailed(device_bar_addr.raw_value(), e)
|
||||
})?;
|
||||
ranges.push((
|
||||
device_bar_addr,
|
||||
config.get_size(),
|
||||
PciBarRegionType::Memory64BitRegion,
|
||||
));
|
||||
// Allocate a dedicated BAR if there are some shared memory regions.
|
||||
if let Some(shm_list) = self.device.get_shm_regions() {
|
||||
let config = PciBarConfiguration::default()
|
||||
.set_register_index(2)
|
||||
.set_address(shm_list.addr.raw_value())
|
||||
.set_size(shm_list.len);
|
||||
let virtio_pci_shm_bar =
|
||||
self.configuration.add_pci_bar(&config).map_err(|e| {
|
||||
PciDeviceError::IoRegistrationFailed(shm_list.addr.raw_value(), e)
|
||||
})? as u8;
|
||||
|
||||
for (idx, shm) in shm_list.region_list.iter().enumerate() {
|
||||
let shm_cap = VirtioPciCap64::new(
|
||||
PciCapabilityType::SharedMemoryConfig,
|
||||
virtio_pci_shm_bar,
|
||||
idx as u8,
|
||||
shm.offset,
|
||||
shm.len,
|
||||
);
|
||||
self.configuration
|
||||
.add_capability(&shm_cap)
|
||||
.map_err(PciDeviceError::CapabilitiesSetup)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ranges)
|
||||
@@ -585,7 +674,6 @@ impl PciDevice for VirtioPciDevice {
|
||||
.activate(
|
||||
mem,
|
||||
interrupt_cb,
|
||||
self.interrupt_status.clone(),
|
||||
self.queues.clone(),
|
||||
self.queue_evts.split_off(0),
|
||||
)
|
||||
|
||||
371
vm-virtio/src/vhost_user/fs.rs
Normal file
371
vm-virtio/src/vhost_user/fs.rs
Normal file
@@ -0,0 +1,371 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::vu_common_ctrl::setup_vhost_user;
|
||||
use super::{Error, Result};
|
||||
use crate::vhost_user::handler::{VhostUserEpollConfig, VhostUserEpollHandler};
|
||||
use crate::{
|
||||
ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType, VirtioInterrupt,
|
||||
VirtioSharedMemoryList, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use libc::{self, EFD_NONBLOCK};
|
||||
use std::cmp;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread;
|
||||
use vhost_rs::vhost_user::message::{
|
||||
VhostUserFSSlaveMsg, VhostUserProtocolFeatures, VhostUserVirtioFeatures,
|
||||
};
|
||||
use vhost_rs::vhost_user::{
|
||||
HandlerResult, Master, MasterReqHandler, VhostUserMaster, VhostUserMasterReqHandler,
|
||||
};
|
||||
use vhost_rs::VhostBackend;
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const CONFIG_SPACE_TAG_SIZE: usize = 36;
|
||||
const CONFIG_SPACE_NUM_QUEUES_SIZE: usize = 4;
|
||||
const CONFIG_SPACE_SIZE: usize = CONFIG_SPACE_TAG_SIZE + CONFIG_SPACE_NUM_QUEUES_SIZE;
|
||||
const NUM_QUEUE_OFFSET: usize = 1;
|
||||
|
||||
struct SlaveReqHandler {
|
||||
cache_size: u64,
|
||||
mmap_cache_addr: u64,
|
||||
}
|
||||
|
||||
impl VhostUserMasterReqHandler for SlaveReqHandler {
|
||||
fn handle_config_change(&mut self) -> HandlerResult<()> {
|
||||
debug!("handle_config_change");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fs_slave_map(&mut self, fs: &VhostUserFSSlaveMsg, fd: RawFd) -> HandlerResult<()> {
|
||||
debug!("fs_slave_map");
|
||||
|
||||
let addr = self.mmap_cache_addr + fs.cache_offset[0];
|
||||
let ret = unsafe {
|
||||
libc::mmap(
|
||||
addr as *mut libc::c_void,
|
||||
fs.len[0] as usize,
|
||||
fs.flags[0].bits() as i32,
|
||||
libc::MAP_SHARED | libc::MAP_FIXED,
|
||||
fd,
|
||||
fs.fd_offset[0] as libc::off_t,
|
||||
)
|
||||
};
|
||||
if ret == libc::MAP_FAILED {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let ret = unsafe { libc::close(fd) };
|
||||
if ret == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fs_slave_unmap(&mut self, fs: &VhostUserFSSlaveMsg) -> HandlerResult<()> {
|
||||
debug!("fs_slave_unmap");
|
||||
|
||||
let mut len = fs.len[0];
|
||||
// Need to handle a special case where the slave ask for the unmapping
|
||||
// of the entire mapping.
|
||||
if len == 0xffff_ffff_ffff_ffff {
|
||||
len = self.cache_size;
|
||||
}
|
||||
|
||||
let addr = self.mmap_cache_addr + fs.cache_offset[0];
|
||||
let ret = unsafe {
|
||||
libc::mmap(
|
||||
addr as *mut libc::c_void,
|
||||
len as usize,
|
||||
libc::PROT_NONE,
|
||||
libc::MAP_ANONYMOUS | libc::MAP_PRIVATE | libc::MAP_FIXED,
|
||||
-1,
|
||||
0 as libc::off_t,
|
||||
)
|
||||
};
|
||||
if ret == libc::MAP_FAILED {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fs_slave_sync(&mut self, fs: &VhostUserFSSlaveMsg) -> HandlerResult<()> {
|
||||
debug!("fs_slave_sync");
|
||||
|
||||
let addr = self.mmap_cache_addr + fs.cache_offset[0];
|
||||
let ret =
|
||||
unsafe { libc::msync(addr as *mut libc::c_void, fs.len[0] as usize, libc::MS_SYNC) };
|
||||
if ret == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Fs {
|
||||
vu: Master,
|
||||
queue_sizes: Vec<u16>,
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
config_space: Vec<u8>,
|
||||
kill_evt: Option<EventFd>,
|
||||
cache: Option<(VirtioSharedMemoryList, u64)>,
|
||||
slave_req_support: bool,
|
||||
}
|
||||
|
||||
impl Fs {
|
||||
/// Create a new virtio-fs device.
|
||||
pub fn new(
|
||||
path: &str,
|
||||
tag: &str,
|
||||
req_num_queues: usize,
|
||||
queue_size: u16,
|
||||
cache: Option<(VirtioSharedMemoryList, u64)>,
|
||||
) -> Result<Fs> {
|
||||
let mut slave_req_support = false;
|
||||
|
||||
// Calculate the actual number of queues needed.
|
||||
let num_queues = NUM_QUEUE_OFFSET + req_num_queues;
|
||||
|
||||
// Connect to the vhost-user socket.
|
||||
let mut master =
|
||||
Master::connect(path, num_queues as u64).map_err(Error::VhostUserCreateMaster)?;
|
||||
|
||||
// Filling device and vring features VMM supports.
|
||||
let mut avail_features =
|
||||
1 << VIRTIO_F_VERSION_1 | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
|
||||
// Set vhost-user owner.
|
||||
master.set_owner().map_err(Error::VhostUserSetOwner)?;
|
||||
|
||||
// Get features from backend, do negotiation to get a feature collection which
|
||||
// both VMM and backend support.
|
||||
let backend_features = master.get_features().map_err(Error::VhostUserGetFeatures)?;
|
||||
avail_features &= backend_features;
|
||||
// Set features back is required by the vhost crate mechanism, since the
|
||||
// later vhost call will check if features is filled in master before execution.
|
||||
master
|
||||
.set_features(avail_features)
|
||||
.map_err(Error::VhostUserSetFeatures)?;
|
||||
|
||||
// Identify if protocol features are supported by the slave.
|
||||
let mut acked_features = 0;
|
||||
if avail_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() != 0 {
|
||||
acked_features |= VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
|
||||
let mut protocol_features = master
|
||||
.get_protocol_features()
|
||||
.map_err(Error::VhostUserGetProtocolFeatures)?;
|
||||
|
||||
if cache.is_some() {
|
||||
protocol_features &= VhostUserProtocolFeatures::MQ
|
||||
| VhostUserProtocolFeatures::REPLY_ACK
|
||||
| VhostUserProtocolFeatures::SLAVE_REQ
|
||||
| VhostUserProtocolFeatures::SLAVE_SEND_FD;
|
||||
} else {
|
||||
protocol_features &=
|
||||
VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::REPLY_ACK;
|
||||
}
|
||||
|
||||
master
|
||||
.set_protocol_features(protocol_features)
|
||||
.map_err(Error::VhostUserSetProtocolFeatures)?;
|
||||
|
||||
slave_req_support = true;
|
||||
}
|
||||
|
||||
// Create virtio device config space.
|
||||
// First by adding the tag.
|
||||
let mut config_space = tag.to_string().into_bytes();
|
||||
config_space.resize(CONFIG_SPACE_SIZE, 0);
|
||||
|
||||
// And then by copying the number of queues.
|
||||
let num_queues_slice = (req_num_queues as u32).to_le_bytes();
|
||||
config_space[CONFIG_SPACE_TAG_SIZE..CONFIG_SPACE_SIZE].copy_from_slice(&num_queues_slice);
|
||||
|
||||
Ok(Fs {
|
||||
vu: master,
|
||||
queue_sizes: vec![queue_size; num_queues],
|
||||
avail_features,
|
||||
acked_features,
|
||||
config_space,
|
||||
kill_evt: None,
|
||||
cache,
|
||||
slave_req_support,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Fs {
|
||||
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 Fs {
|
||||
fn device_type(&self) -> u32 {
|
||||
VirtioDeviceType::TYPE_FS as u32
|
||||
}
|
||||
|
||||
fn queue_max_sizes(&self) -> &[u16] {
|
||||
&self.queue_sizes.as_slice()
|
||||
}
|
||||
|
||||
fn features(&self, page: u32) -> u32 {
|
||||
match page {
|
||||
// Get the lower 32-bits of the features bitfield.
|
||||
0 => self.avail_features as u32,
|
||||
// Get the upper 32-bits of the features bitfield.
|
||||
1 => (self.avail_features >> 32) as u32,
|
||||
_ => {
|
||||
warn!("fs: Received request for unknown features page: {}", page);
|
||||
0u32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ack_features(&mut self, page: u32, value: u32) {
|
||||
let mut v = match page {
|
||||
0 => u64::from(value),
|
||||
1 => u64::from(value) << 32,
|
||||
_ => {
|
||||
warn!("fs: Cannot acknowledge unknown features page: {}", page);
|
||||
0u64
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the guest is ACK'ing a feature that we didn't claim to have.
|
||||
let unrequested_features = v & !self.avail_features;
|
||||
if unrequested_features != 0 {
|
||||
warn!("fs: virtio-fs got unknown feature ack: {:x}", v);
|
||||
|
||||
// Don't count these features as acked.
|
||||
v &= !unrequested_features;
|
||||
}
|
||||
self.acked_features |= v;
|
||||
}
|
||||
|
||||
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
|
||||
let config_len = self.config_space.len() as u64;
|
||||
if offset >= config_len {
|
||||
error!("Failed to read config space");
|
||||
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_all(&self.config_space[offset as usize..cmp::min(end, config_len) as usize])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn write_config(&mut self, offset: u64, data: &[u8]) {
|
||||
let data_len = data.len() as u64;
|
||||
let config_len = self.config_space.len() as u64;
|
||||
if offset + data_len > config_len {
|
||||
error!("Failed to write config space");
|
||||
return;
|
||||
}
|
||||
let (_, right) = self.config_space.split_at_mut(offset as usize);
|
||||
right.copy_from_slice(&data[..]);
|
||||
}
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
if queues.len() != self.queue_sizes.len() || queue_evts.len() != self.queue_sizes.len() {
|
||||
error!(
|
||||
"Cannot perform activate. Expected {} queue(s), got {}",
|
||||
self.queue_sizes.len(),
|
||||
queues.len()
|
||||
);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
let (self_kill_evt, kill_evt) =
|
||||
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed creating kill EventFd pair: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
let vu_call_evt_queue_list = setup_vhost_user(
|
||||
&mut self.vu,
|
||||
&mem.read().unwrap(),
|
||||
queues,
|
||||
queue_evts,
|
||||
self.acked_features,
|
||||
)
|
||||
.map_err(ActivateError::VhostUserSetup)?;
|
||||
|
||||
// Initialize slave communication.
|
||||
let slave_req_handler = if self.slave_req_support {
|
||||
if let Some(cache) = self.cache.clone() {
|
||||
let vu_master_req_handler = Arc::new(Mutex::new(SlaveReqHandler {
|
||||
cache_size: cache.0.len,
|
||||
mmap_cache_addr: cache.1,
|
||||
}));
|
||||
|
||||
let req_handler = MasterReqHandler::new(vu_master_req_handler).map_err(|e| {
|
||||
ActivateError::VhostUserSetup(Error::MasterReqHandlerCreation(e))
|
||||
})?;
|
||||
self.vu
|
||||
.set_slave_request_fd(req_handler.get_tx_raw_fd())
|
||||
.map_err(|e| {
|
||||
ActivateError::VhostUserSetup(Error::VhostUserSetSlaveRequestFd(e))
|
||||
})?;
|
||||
Some(req_handler)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut handler = VhostUserEpollHandler::new(VhostUserEpollConfig {
|
||||
vu_interrupt_list: vu_call_evt_queue_list,
|
||||
interrupt_cb,
|
||||
kill_evt,
|
||||
slave_req_handler,
|
||||
});
|
||||
|
||||
let worker_result = thread::Builder::new()
|
||||
.name("virtio_fs".to_string())
|
||||
.spawn(move || {
|
||||
if let Err(e) = handler.run() {
|
||||
error!("net worker thread exited with error {:?}!", e);
|
||||
}
|
||||
});
|
||||
|
||||
if let Err(e) = worker_result {
|
||||
error!("failed to spawn virtio-fs worker: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_shm_regions(&self) -> Option<VirtioSharedMemoryList> {
|
||||
if let Some(cache) = self.cache.clone() {
|
||||
Some(cache.0)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
156
vm-virtio/src/vhost_user/handler.rs
Normal file
156
vm-virtio/src/vhost_user/handler.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) 2019 Intel Corporation. All rights reserved.
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// 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-BSD-3-Clause file.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use super::super::{Queue, VirtioInterruptType};
|
||||
use super::{Error, Result};
|
||||
use epoll;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::VirtioInterrupt;
|
||||
use std::io;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::sync::Arc;
|
||||
use vhost_rs::vhost_user::{MasterReqHandler, VhostUserMasterReqHandler};
|
||||
|
||||
/// Collection of common parameters required by vhost-user devices while
|
||||
/// call Epoll handler.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `interrupt_cb` interrupt for virtqueue change.
|
||||
/// * `kill_evt` - EventFd used to kill the vhost-user device.
|
||||
/// * `vu_interrupt_list` - virtqueue and EventFd to signal when buffer used.
|
||||
pub struct VhostUserEpollConfig<S: VhostUserMasterReqHandler> {
|
||||
pub interrupt_cb: Arc<VirtioInterrupt>,
|
||||
pub kill_evt: EventFd,
|
||||
pub vu_interrupt_list: Vec<(EventFd, Queue)>,
|
||||
pub slave_req_handler: Option<MasterReqHandler<S>>,
|
||||
}
|
||||
|
||||
pub struct VhostUserEpollHandler<S: VhostUserMasterReqHandler> {
|
||||
vu_epoll_cfg: VhostUserEpollConfig<S>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserMasterReqHandler> VhostUserEpollHandler<S> {
|
||||
/// Construct a new event handler for vhost-user based devices.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `vu_epoll_cfg` - collection of common parameters for vhost-user devices
|
||||
///
|
||||
/// # Return
|
||||
/// * `VhostUserEpollHandler` - epoll handler for vhost-user based devices
|
||||
pub fn new(vu_epoll_cfg: VhostUserEpollConfig<S>) -> VhostUserEpollHandler<S> {
|
||||
VhostUserEpollHandler { vu_epoll_cfg }
|
||||
}
|
||||
|
||||
fn signal_used_queue(&self, queue: &Queue) -> Result<()> {
|
||||
(self.vu_epoll_cfg.interrupt_cb)(&VirtioInterruptType::Queue, Some(queue))
|
||||
.map_err(Error::FailedSignalingUsedQueue)
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<()> {
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).map_err(Error::EpollCreateFd)?;
|
||||
|
||||
for (index, vhost_user_interrupt) in self.vu_epoll_cfg.vu_interrupt_list.iter().enumerate()
|
||||
{
|
||||
// Add events
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
vhost_user_interrupt.0.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, index as u64),
|
||||
)
|
||||
.map_err(Error::EpollCtl)?;
|
||||
}
|
||||
|
||||
let kill_evt_index = self.vu_epoll_cfg.vu_interrupt_list.len();
|
||||
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.vu_epoll_cfg.kill_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, kill_evt_index as u64),
|
||||
)
|
||||
.map_err(Error::EpollCtl)?;
|
||||
|
||||
let mut index = kill_evt_index;
|
||||
|
||||
let slave_evt_index = if let Some(self_req_handler) = &self.vu_epoll_cfg.slave_req_handler {
|
||||
index = kill_evt_index + 1;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self_req_handler.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, index as u64),
|
||||
)
|
||||
.map_err(Error::EpollCtl)?;
|
||||
|
||||
Some(index)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); index + 1];
|
||||
|
||||
'poll: loop {
|
||||
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
// It's well defined from the epoll_wait() syscall
|
||||
// documentation that the epoll loop can be interrupted
|
||||
// before any of the requested events occurred or the
|
||||
// timeout expired. In both those cases, epoll_wait()
|
||||
// returns an error of type EINTR, but this should not
|
||||
// be considered as a regular error. Instead it is more
|
||||
// appropriate to retry, by calling into epoll_wait().
|
||||
continue;
|
||||
}
|
||||
return Err(Error::EpollWait(e));
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let ev_type = event.data as usize;
|
||||
|
||||
match ev_type {
|
||||
x if x < kill_evt_index => {
|
||||
self.vu_epoll_cfg.vu_interrupt_list[x]
|
||||
.0
|
||||
.read()
|
||||
.map_err(Error::FailedReadingQueue)?;
|
||||
if let Err(e) =
|
||||
self.signal_used_queue(&self.vu_epoll_cfg.vu_interrupt_list[x].1)
|
||||
{
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
break 'poll;
|
||||
}
|
||||
}
|
||||
x if kill_evt_index == x => {
|
||||
debug!("KILL_EVENT received, stopping epoll loop");
|
||||
break 'poll;
|
||||
}
|
||||
x if (slave_evt_index.is_some() && slave_evt_index.unwrap() == x) => {
|
||||
if let Some(slave_req_handler) =
|
||||
self.vu_epoll_cfg.slave_req_handler.as_mut()
|
||||
{
|
||||
slave_req_handler
|
||||
.handle_request()
|
||||
.map_err(Error::VhostUserSlaveRequest)?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("Unknown event for vhost-user");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
95
vm-virtio/src/vhost_user/mod.rs
Normal file
95
vm-virtio/src/vhost_user/mod.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
extern crate epoll;
|
||||
extern crate net_util;
|
||||
extern crate vhost_rs;
|
||||
extern crate virtio_bindings;
|
||||
extern crate vm_memory;
|
||||
|
||||
use std;
|
||||
use std::io;
|
||||
use vhost_rs::Error as VhostError;
|
||||
use vm_memory::Error as MmapError;
|
||||
|
||||
pub mod fs;
|
||||
mod handler;
|
||||
pub mod net;
|
||||
pub mod vu_common_ctrl;
|
||||
|
||||
pub use self::fs::*;
|
||||
pub use self::net::Net;
|
||||
pub use self::vu_common_ctrl::VhostUserConfig;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Invalid available address.
|
||||
AvailAddress,
|
||||
/// Queue number is not correct
|
||||
BadQueueNum,
|
||||
/// Creating kill eventfd failed.
|
||||
CreateKillEventFd(io::Error),
|
||||
/// Cloning kill eventfd failed.
|
||||
CloneKillEventFd(io::Error),
|
||||
/// Invalid descriptor table address.
|
||||
DescriptorTableAddress,
|
||||
/// Create Epoll eventfd failed
|
||||
EpollCreateFd(io::Error),
|
||||
/// Epoll ctl error
|
||||
EpollCtl(io::Error),
|
||||
/// Epoll wait error
|
||||
EpollWait(io::Error),
|
||||
/// Read queue failed.
|
||||
FailedReadingQueue(io::Error),
|
||||
/// Signal used queue failed.
|
||||
FailedSignalingUsedQueue(io::Error),
|
||||
/// Failed to read vhost eventfd.
|
||||
MemoryRegions(MmapError),
|
||||
/// Failed to create master.
|
||||
VhostUserCreateMaster(VhostError),
|
||||
/// Failed to open vhost device.
|
||||
VhostUserOpen(VhostError),
|
||||
/// Connection to socket failed.
|
||||
VhostUserConnect(vhost_rs::Error),
|
||||
/// Get features failed.
|
||||
VhostUserGetFeatures(VhostError),
|
||||
/// Get protocol features failed.
|
||||
VhostUserGetProtocolFeatures(VhostError),
|
||||
/// Vhost-user Backend not support vhost-user protocol.
|
||||
VhostUserProtocolNotSupport,
|
||||
/// Set owner failed.
|
||||
VhostUserSetOwner(VhostError),
|
||||
/// Set features failed.
|
||||
VhostUserSetFeatures(VhostError),
|
||||
/// Set protocol features failed.
|
||||
VhostUserSetProtocolFeatures(VhostError),
|
||||
/// Set mem table failed.
|
||||
VhostUserSetMemTable(VhostError),
|
||||
/// Set vring num failed.
|
||||
VhostUserSetVringNum(VhostError),
|
||||
/// Set vring addr failed.
|
||||
VhostUserSetVringAddr(VhostError),
|
||||
/// Set vring base failed.
|
||||
VhostUserSetVringBase(VhostError),
|
||||
/// Set vring call failed.
|
||||
VhostUserSetVringCall(VhostError),
|
||||
/// Set vring kick failed.
|
||||
VhostUserSetVringKick(VhostError),
|
||||
/// Set vring enable failed.
|
||||
VhostUserSetVringEnable(VhostError),
|
||||
/// Failed to create vhost eventfd.
|
||||
VhostIrqCreate(io::Error),
|
||||
/// Failed to read vhost eventfd.
|
||||
VhostIrqRead(io::Error),
|
||||
/// Failed to read vhost eventfd.
|
||||
VhostUserMemoryRegion(MmapError),
|
||||
/// Failed to handle vhost-user slave request.
|
||||
VhostUserSlaveRequest(vhost_rs::vhost_user::Error),
|
||||
/// Failed to create the master request handler from slave.
|
||||
MasterReqHandlerCreation(vhost_rs::vhost_user::Error),
|
||||
/// Set slave request fd failed.
|
||||
VhostUserSetSlaveRequestFd(vhost_rs::Error),
|
||||
/// Invalid used address.
|
||||
UsedAddress,
|
||||
}
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
229
vm-virtio/src/vhost_user/net.rs
Normal file
229
vm-virtio/src/vhost_user/net.rs
Normal file
@@ -0,0 +1,229 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use libc;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std::cmp;
|
||||
use std::io::Write;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
use std::vec::Vec;
|
||||
|
||||
use crate::VirtioInterrupt;
|
||||
use net_util::{MacAddr, MAC_ADDR_LEN};
|
||||
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::super::{ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType};
|
||||
use super::handler::*;
|
||||
use super::vu_common_ctrl::*;
|
||||
use super::{Error, Result};
|
||||
use vhost_rs::vhost_user::message::VhostUserVirtioFeatures;
|
||||
use vhost_rs::vhost_user::{Master, VhostUserMaster, VhostUserMasterReqHandler};
|
||||
use vhost_rs::VhostBackend;
|
||||
use virtio_bindings::virtio_net;
|
||||
use virtio_bindings::virtio_ring;
|
||||
|
||||
struct SlaveReqHandler {}
|
||||
impl VhostUserMasterReqHandler for SlaveReqHandler {}
|
||||
|
||||
pub struct Net {
|
||||
vhost_user_net: Master,
|
||||
kill_evt: EventFd,
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
backend_features: u64,
|
||||
config_space: Vec<u8>,
|
||||
queue_sizes: Vec<u16>,
|
||||
}
|
||||
|
||||
impl<'a> Net {
|
||||
/// Create a new vhost-user-net device
|
||||
pub fn new(mac_addr: MacAddr, vu_cfg: VhostUserConfig<'a>) -> Result<Net> {
|
||||
let mut vhost_user_net = Master::connect(vu_cfg.sock, vu_cfg.num_queues as u64)
|
||||
.map_err(Error::VhostUserCreateMaster)?;
|
||||
|
||||
let kill_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?;
|
||||
|
||||
// Filling device and vring features VMM supports.
|
||||
let mut avail_features = 1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM
|
||||
| 1 << virtio_net::VIRTIO_NET_F_CSUM
|
||||
| 1 << virtio_net::VIRTIO_NET_F_GUEST_TSO4
|
||||
| 1 << virtio_net::VIRTIO_NET_F_GUEST_TSO6
|
||||
| 1 << virtio_net::VIRTIO_NET_F_GUEST_ECN
|
||||
| 1 << virtio_net::VIRTIO_NET_F_GUEST_UFO
|
||||
| 1 << virtio_net::VIRTIO_NET_F_HOST_TSO4
|
||||
| 1 << virtio_net::VIRTIO_NET_F_HOST_TSO6
|
||||
| 1 << virtio_net::VIRTIO_NET_F_HOST_ECN
|
||||
| 1 << virtio_net::VIRTIO_NET_F_HOST_UFO
|
||||
| 1 << virtio_net::VIRTIO_NET_F_MRG_RXBUF
|
||||
| 1 << virtio_net::VIRTIO_F_NOTIFY_ON_EMPTY
|
||||
| 1 << virtio_net::VIRTIO_F_VERSION_1
|
||||
| 1 << virtio_ring::VIRTIO_RING_F_EVENT_IDX
|
||||
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
|
||||
vhost_user_net
|
||||
.set_owner()
|
||||
.map_err(Error::VhostUserSetOwner)?;
|
||||
|
||||
// Get features from backend, do negotiation to get a feature collection which
|
||||
// both VMM and backend support.
|
||||
let backend_features = vhost_user_net
|
||||
.get_features()
|
||||
.map_err(Error::VhostUserGetFeatures)?;
|
||||
avail_features &= backend_features;
|
||||
// Set features back is required by the vhost crate mechanism, since the
|
||||
// later vhost call will check if features is filled in master before execution.
|
||||
vhost_user_net
|
||||
.set_features(avail_features)
|
||||
.map_err(Error::VhostUserSetFeatures)?;
|
||||
|
||||
let mut acked_features = 0;
|
||||
if avail_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() != 0 {
|
||||
acked_features |= VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
vhost_user_net
|
||||
.get_protocol_features()
|
||||
.map_err(Error::VhostUserGetProtocolFeatures)?;
|
||||
} else {
|
||||
return Err(Error::VhostUserProtocolNotSupport);
|
||||
}
|
||||
|
||||
let mut config_space = Vec::with_capacity(MAC_ADDR_LEN);
|
||||
unsafe { config_space.set_len(MAC_ADDR_LEN) }
|
||||
config_space[..].copy_from_slice(mac_addr.get_bytes());
|
||||
avail_features |= 1 << virtio_net::VIRTIO_NET_F_MAC;
|
||||
|
||||
// Send set_vring_base here, since it could tell backends, like OVS + DPDK,
|
||||
// how many virt queues to be handled, which backend required to know at early stage.
|
||||
for i in 0..vu_cfg.num_queues {
|
||||
vhost_user_net
|
||||
.set_vring_base(i, 0)
|
||||
.map_err(Error::VhostUserSetVringBase)?;
|
||||
}
|
||||
|
||||
Ok(Net {
|
||||
vhost_user_net,
|
||||
kill_evt,
|
||||
avail_features,
|
||||
acked_features,
|
||||
backend_features,
|
||||
config_space,
|
||||
queue_sizes: vec![vu_cfg.queue_size; vu_cfg.num_queues],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Net {
|
||||
fn drop(&mut self) {
|
||||
if let Err(_e) = self.kill_evt.write(1) {
|
||||
error!("failed to kill vhost-user-net with error {}", _e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioDevice for Net {
|
||||
fn device_type(&self) -> u32 {
|
||||
VirtioDeviceType::TYPE_NET as u32
|
||||
}
|
||||
|
||||
fn queue_max_sizes(&self) -> &[u16] {
|
||||
&self.queue_sizes
|
||||
}
|
||||
|
||||
fn features(&self, page: u32) -> u32 {
|
||||
match page {
|
||||
0 => self.avail_features as u32,
|
||||
1 => (self.avail_features >> 32) as u32,
|
||||
_ => {
|
||||
warn!("Received request for unknown features page: {}", page);
|
||||
0u32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ack_features(&mut self, page: u32, value: u32) {
|
||||
let mut v = match page {
|
||||
0 => u64::from(value),
|
||||
1 => u64::from(value) << 32,
|
||||
_ => {
|
||||
warn!("Cannot acknowledge unknown features page: {}", page);
|
||||
0u64
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the guest is ACK'ing a feature that we didn't claim to have.
|
||||
let unrequested_features = v & !self.avail_features;
|
||||
if unrequested_features != 0 {
|
||||
warn!("Received acknowledge request for unknown feature: {:x}", v);
|
||||
// Don't count these features as acked.
|
||||
v &= !unrequested_features;
|
||||
}
|
||||
self.acked_features |= v;
|
||||
}
|
||||
|
||||
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
|
||||
let config_len = self.config_space.len() as u64;
|
||||
if offset >= config_len {
|
||||
error!("Failed to read config space");
|
||||
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_all(&self.config_space[offset as usize..cmp::min(end, config_len) as usize])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn write_config(&mut self, offset: u64, data: &[u8]) {
|
||||
let data_len = data.len() as u64;
|
||||
let config_len = self.config_space.len() as u64;
|
||||
if offset + data_len > config_len {
|
||||
error!("Failed to write config space");
|
||||
return;
|
||||
}
|
||||
let (_, right) = self.config_space.split_at_mut(offset as usize);
|
||||
right.copy_from_slice(&data[..]);
|
||||
}
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
let handler_kill_evt = self
|
||||
.kill_evt
|
||||
.try_clone()
|
||||
.map_err(|_| ActivateError::CloneKillEventFd)?;
|
||||
|
||||
let vu_interrupt_list = setup_vhost_user(
|
||||
&mut self.vhost_user_net,
|
||||
&mem.read().unwrap(),
|
||||
queues,
|
||||
queue_evts,
|
||||
self.acked_features & self.backend_features,
|
||||
)
|
||||
.map_err(ActivateError::VhostUserNetSetup)?;
|
||||
|
||||
let mut handler = VhostUserEpollHandler::<SlaveReqHandler>::new(VhostUserEpollConfig {
|
||||
interrupt_cb,
|
||||
kill_evt: handler_kill_evt,
|
||||
vu_interrupt_list,
|
||||
slave_req_handler: None,
|
||||
});
|
||||
|
||||
let handler_result = thread::Builder::new()
|
||||
.name("vhost_user_net".to_string())
|
||||
.spawn(move || {
|
||||
if let Err(e) = handler.run() {
|
||||
error!("net worker thread exited with error {:?}!", e);
|
||||
}
|
||||
});
|
||||
if let Err(e) = handler_result {
|
||||
error!("vhost-user net thread create failed with error {:?}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
108
vm-virtio/src/vhost_user/vu_common_ctrl.rs
Normal file
108
vm-virtio/src/vhost_user/vu_common_ctrl.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use libc;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::vec::Vec;
|
||||
|
||||
use vm_memory::{Address, Error as MmapError, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::super::Queue;
|
||||
use super::{Error, Result};
|
||||
use vhost_rs::vhost_user::{Master, VhostUserMaster};
|
||||
use vhost_rs::{VhostBackend, VhostUserMemoryRegionInfo, VringConfigData};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct VhostUserConfig<'a> {
|
||||
pub sock: &'a str,
|
||||
pub num_queues: usize,
|
||||
pub queue_size: u16,
|
||||
}
|
||||
|
||||
pub fn setup_vhost_user_vring(
|
||||
vu: &mut Master,
|
||||
mem: &GuestMemoryMmap,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
) -> Result<Vec<(EventFd, Queue)>> {
|
||||
let mut regions: Vec<VhostUserMemoryRegionInfo> = Vec::new();
|
||||
mem.with_regions_mut(|_, region| {
|
||||
let (mmap_handle, mmap_offset) = match region.file_offset() {
|
||||
Some(_file_offset) => (_file_offset.file().as_raw_fd(), _file_offset.start()),
|
||||
None => return Err(MmapError::NoMemoryRegion),
|
||||
};
|
||||
|
||||
let vhost_user_net_reg = VhostUserMemoryRegionInfo {
|
||||
guest_phys_addr: region.start_addr().raw_value(),
|
||||
memory_size: region.len() as u64,
|
||||
userspace_addr: region.as_ptr() as u64,
|
||||
mmap_offset,
|
||||
mmap_handle,
|
||||
};
|
||||
|
||||
regions.push(vhost_user_net_reg);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(Error::VhostUserMemoryRegion)?;
|
||||
|
||||
vu.set_mem_table(regions.as_slice())
|
||||
.map_err(Error::VhostUserSetMemTable)?;
|
||||
|
||||
let mut vu_interrupt_list = Vec::new();
|
||||
|
||||
for (queue_index, queue) in queues.into_iter().enumerate() {
|
||||
vu.set_vring_num(queue_index, queue.get_max_size())
|
||||
.map_err(Error::VhostUserSetVringNum)?;
|
||||
|
||||
let config_data = VringConfigData {
|
||||
queue_max_size: queue.get_max_size(),
|
||||
queue_size: queue.actual_size(),
|
||||
flags: 0u32,
|
||||
desc_table_addr: mem
|
||||
.get_host_address(queue.desc_table)
|
||||
.ok_or_else(|| Error::DescriptorTableAddress)? as u64,
|
||||
used_ring_addr: mem
|
||||
.get_host_address(queue.used_ring)
|
||||
.ok_or_else(|| Error::UsedAddress)? as u64,
|
||||
avail_ring_addr: mem
|
||||
.get_host_address(queue.avail_ring)
|
||||
.ok_or_else(|| Error::AvailAddress)? as u64,
|
||||
log_addr: None,
|
||||
};
|
||||
|
||||
vu.set_vring_addr(queue_index, &config_data)
|
||||
.map_err(Error::VhostUserSetVringAddr)?;
|
||||
vu.set_vring_base(queue_index, 0u16)
|
||||
.map_err(Error::VhostUserSetVringBase)?;
|
||||
|
||||
let vhost_user_interrupt = EventFd::new(EFD_NONBLOCK).map_err(Error::VhostIrqCreate)?;
|
||||
vu.set_vring_call(queue_index, &vhost_user_interrupt)
|
||||
.map_err(Error::VhostUserSetVringCall)?;
|
||||
vu_interrupt_list.push((vhost_user_interrupt, queue));
|
||||
|
||||
vu.set_vring_kick(queue_index, &queue_evts[queue_index])
|
||||
.map_err(Error::VhostUserSetVringKick)?;
|
||||
|
||||
vu.set_vring_enable(queue_index, true)
|
||||
.map_err(Error::VhostUserSetVringEnable)?;
|
||||
}
|
||||
|
||||
Ok(vu_interrupt_list)
|
||||
}
|
||||
|
||||
pub fn setup_vhost_user(
|
||||
vu: &mut Master,
|
||||
mem: &GuestMemoryMmap,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
acked_features: u64,
|
||||
) -> Result<Vec<(EventFd, Queue)>> {
|
||||
// Set features based on the acked features from the guest driver.
|
||||
vu.set_features(acked_features)
|
||||
.map_err(Error::VhostUserSetFeatures)?;
|
||||
|
||||
setup_vhost_user_vring(vu, mem, queues, queue_evts)
|
||||
}
|
||||
14
vmm/Cargo.toml
Executable file → Normal file
14
vmm/Cargo.toml
Executable file → Normal file
@@ -4,14 +4,19 @@ version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = ["acpi"]
|
||||
acpi = ["acpi_tables"]
|
||||
|
||||
[dependencies]
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
arch = { path = "../arch" }
|
||||
devices = { path = "../devices" }
|
||||
epoll = "=4.0.1"
|
||||
kvm-bindings = "0.1"
|
||||
epoll = "4.1.0"
|
||||
kvm-bindings = "0.1.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = ">=0.2.39"
|
||||
log = "*"
|
||||
libc = "0.2.62"
|
||||
log = "0.4.8"
|
||||
net_util = { path = "../net_util" }
|
||||
pci = {path = "../pci"}
|
||||
qcow = { path = "../qcow" }
|
||||
@@ -19,6 +24,7 @@ vfio = { path = "../vfio" }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
signal-hook = "0.1.10"
|
||||
|
||||
[dependencies.linux-loader]
|
||||
git = "https://github.com/rust-vmm/linux-loader"
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
extern crate vm_virtio;
|
||||
|
||||
use linux_loader::cmdline::Cmdline;
|
||||
use net_util::MacAddr;
|
||||
use std::convert::From;
|
||||
@@ -11,6 +13,7 @@ use std::net::Ipv4Addr;
|
||||
use std::path::Path;
|
||||
use std::result;
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_virtio::vhost_user::VhostUserConfig;
|
||||
|
||||
pub const DEFAULT_VCPUS: &str = "1";
|
||||
pub const DEFAULT_MEMORY: &str = "size=512M";
|
||||
@@ -46,6 +49,10 @@ pub enum Error<'a> {
|
||||
ParseFsNumQueuesParam(std::num::ParseIntError),
|
||||
/// Failed parsing fs queue size parameter.
|
||||
ParseFsQueueSizeParam(std::num::ParseIntError),
|
||||
/// Failed parsing fs dax parameter.
|
||||
ParseFsDax,
|
||||
/// Cannot have dax=off along with cache_size parameter.
|
||||
InvalidCacheSizeWithDaxOff,
|
||||
/// Failed parsing persitent memory file parameter.
|
||||
ParsePmemFileParam,
|
||||
/// Failed parsing size parameter.
|
||||
@@ -54,6 +61,16 @@ pub enum Error<'a> {
|
||||
ParseConsoleParam,
|
||||
/// Both console and serial are tty.
|
||||
ParseTTYParam,
|
||||
/// Failed parsing vhost-user-net mac parameter.
|
||||
ParseVuNetMacParam(&'a str),
|
||||
/// Failed parsing vhost-user-net sock parameter.
|
||||
ParseVuNetSockParam,
|
||||
/// Failed parsing vhost-user-net queue number parameter.
|
||||
ParseVuNetNumQueuesParam(std::num::ParseIntError),
|
||||
/// Failed parsing vhost-user-net queue size parameter.
|
||||
ParseVuNetQueueSizeParam(std::num::ParseIntError),
|
||||
/// Failed parsing vhost-user-net server parameter.
|
||||
ParseVuNetServerParam(std::num::ParseIntError),
|
||||
}
|
||||
pub type Result<'a, T> = result::Result<T, Error<'a>>;
|
||||
|
||||
@@ -70,6 +87,7 @@ pub struct VmParams<'a> {
|
||||
pub serial: &'a str,
|
||||
pub console: &'a str,
|
||||
pub devices: Option<Vec<&'a str>>,
|
||||
pub vhost_user_net: Option<Vec<&'a str>>,
|
||||
}
|
||||
|
||||
fn parse_size(size: &str) -> Result<u64> {
|
||||
@@ -260,6 +278,7 @@ pub struct FsConfig<'a> {
|
||||
pub sock: &'a Path,
|
||||
pub num_queues: usize,
|
||||
pub queue_size: u16,
|
||||
pub cache_size: Option<u64>,
|
||||
}
|
||||
|
||||
impl<'a> FsConfig<'a> {
|
||||
@@ -271,6 +290,8 @@ impl<'a> FsConfig<'a> {
|
||||
let mut sock: &str = "";
|
||||
let mut num_queues_str: &str = "";
|
||||
let mut queue_size_str: &str = "";
|
||||
let mut dax_str: &str = "";
|
||||
let mut cache_size_str: &str = "";
|
||||
|
||||
for param in params_list.iter() {
|
||||
if param.starts_with("tag=") {
|
||||
@@ -281,11 +302,18 @@ impl<'a> FsConfig<'a> {
|
||||
num_queues_str = ¶m[11..];
|
||||
} else if param.starts_with("queue_size=") {
|
||||
queue_size_str = ¶m[11..];
|
||||
} else if param.starts_with("dax=") {
|
||||
dax_str = ¶m[4..];
|
||||
} else if param.starts_with("cache_size=") {
|
||||
cache_size_str = ¶m[11..];
|
||||
}
|
||||
}
|
||||
|
||||
let mut num_queues: usize = 1;
|
||||
let mut queue_size: u16 = 1024;
|
||||
let mut dax: bool = true;
|
||||
// Default cache size set to 8Gib.
|
||||
let mut cache_size: Option<u64> = Some(0x0002_0000_0000);
|
||||
|
||||
if tag.is_empty() {
|
||||
return Err(Error::ParseFsTagParam);
|
||||
@@ -303,12 +331,31 @@ impl<'a> FsConfig<'a> {
|
||||
.parse()
|
||||
.map_err(Error::ParseFsQueueSizeParam)?;
|
||||
}
|
||||
if !dax_str.is_empty() {
|
||||
match dax_str {
|
||||
"on" => dax = true,
|
||||
"off" => dax = false,
|
||||
_ => return Err(Error::ParseFsDax),
|
||||
}
|
||||
}
|
||||
|
||||
// Take appropriate decision about cache_size based on DAX being
|
||||
// enabled or disabled.
|
||||
if !dax {
|
||||
if !cache_size_str.is_empty() {
|
||||
return Err(Error::InvalidCacheSizeWithDaxOff);
|
||||
}
|
||||
cache_size = None;
|
||||
} else if !cache_size_str.is_empty() {
|
||||
cache_size = Some(parse_size(cache_size_str)?);
|
||||
}
|
||||
|
||||
Ok(FsConfig {
|
||||
tag,
|
||||
sock: Path::new(sock),
|
||||
num_queues,
|
||||
queue_size,
|
||||
cache_size,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -350,6 +397,16 @@ pub enum ConsoleOutputMode {
|
||||
Off,
|
||||
Tty,
|
||||
File,
|
||||
Null,
|
||||
}
|
||||
|
||||
impl ConsoleOutputMode {
|
||||
pub fn input_enabled(&self) -> bool {
|
||||
match self {
|
||||
ConsoleOutputMode::Tty => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConsoleConfig<'a> {
|
||||
@@ -374,6 +431,11 @@ impl<'a> ConsoleConfig<'a> {
|
||||
mode: ConsoleOutputMode::File,
|
||||
file: Some(Path::new(¶m[5..])),
|
||||
})
|
||||
} else if param.starts_with("null") {
|
||||
Ok(Self {
|
||||
mode: ConsoleOutputMode::Null,
|
||||
file: None,
|
||||
})
|
||||
} else {
|
||||
Err(Error::ParseConsoleParam)
|
||||
}
|
||||
@@ -393,6 +455,64 @@ impl<'a> DeviceConfig<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VhostUserNetConfig<'a> {
|
||||
pub mac: MacAddr,
|
||||
pub vu_cfg: VhostUserConfig<'a>,
|
||||
}
|
||||
|
||||
impl<'a> VhostUserNetConfig<'a> {
|
||||
pub fn parse(vhost_user_net: &'a str) -> Result<Self> {
|
||||
// Split the parameters based on the comma delimiter
|
||||
let params_list: Vec<&str> = vhost_user_net.split(',').collect();
|
||||
|
||||
let mut mac_str: &str = "";
|
||||
let mut sock: &str = "";
|
||||
let mut num_queues_str: &str = "";
|
||||
let mut queue_size_str: &str = "";
|
||||
|
||||
for param in params_list.iter() {
|
||||
if param.starts_with("mac=") {
|
||||
mac_str = ¶m[4..];
|
||||
} else if param.starts_with("sock=") {
|
||||
sock = ¶m[5..];
|
||||
} else if param.starts_with("num_queues=") {
|
||||
num_queues_str = ¶m[11..];
|
||||
} else if param.starts_with("queue_size=") {
|
||||
queue_size_str = ¶m[11..];
|
||||
}
|
||||
}
|
||||
|
||||
let mut mac: MacAddr = MacAddr::local_random();
|
||||
let mut num_queues: usize = 2;
|
||||
let mut queue_size: u16 = 256;
|
||||
|
||||
if !mac_str.is_empty() {
|
||||
mac = MacAddr::parse_str(mac_str).map_err(Error::ParseVuNetMacParam)?;
|
||||
}
|
||||
if sock.is_empty() {
|
||||
return Err(Error::ParseVuNetSockParam);
|
||||
}
|
||||
if !num_queues_str.is_empty() {
|
||||
num_queues = num_queues_str
|
||||
.parse()
|
||||
.map_err(Error::ParseVuNetNumQueuesParam)?;
|
||||
}
|
||||
if !queue_size_str.is_empty() {
|
||||
queue_size = queue_size_str
|
||||
.parse()
|
||||
.map_err(Error::ParseVuNetQueueSizeParam)?;
|
||||
}
|
||||
|
||||
let vu_cfg = VhostUserConfig {
|
||||
sock,
|
||||
num_queues,
|
||||
queue_size,
|
||||
};
|
||||
|
||||
Ok(VhostUserNetConfig { mac, vu_cfg })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VmConfig<'a> {
|
||||
pub cpus: CpusConfig,
|
||||
pub memory: MemoryConfig<'a>,
|
||||
@@ -406,6 +526,7 @@ pub struct VmConfig<'a> {
|
||||
pub serial: ConsoleConfig<'a>,
|
||||
pub console: ConsoleConfig<'a>,
|
||||
pub devices: Option<Vec<DeviceConfig<'a>>>,
|
||||
pub vhost_user_net: Option<Vec<VhostUserNetConfig<'a>>>,
|
||||
}
|
||||
|
||||
impl<'a> VmConfig<'a> {
|
||||
@@ -461,6 +582,15 @@ impl<'a> VmConfig<'a> {
|
||||
devices = Some(device_config_list);
|
||||
}
|
||||
|
||||
let mut vhost_user_net: Option<Vec<VhostUserNetConfig>> = None;
|
||||
if let Some(vhost_user_net_list) = &vm_params.vhost_user_net {
|
||||
let mut vhost_user_net_config_list = Vec::new();
|
||||
for item in vhost_user_net_list.iter() {
|
||||
vhost_user_net_config_list.push(VhostUserNetConfig::parse(item)?);
|
||||
}
|
||||
vhost_user_net = Some(vhost_user_net_config_list);
|
||||
}
|
||||
|
||||
Ok(VmConfig {
|
||||
cpus: CpusConfig::parse(vm_params.cpus)?,
|
||||
memory: MemoryConfig::parse(vm_params.memory)?,
|
||||
@@ -474,6 +604,7 @@ impl<'a> VmConfig<'a> {
|
||||
serial,
|
||||
console,
|
||||
devices,
|
||||
vhost_user_net,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ pub mod config;
|
||||
pub mod vm;
|
||||
|
||||
use self::config::VmConfig;
|
||||
use self::vm::Vm;
|
||||
use self::vm::{ExitBehaviour, Vm};
|
||||
|
||||
/// Errors associated with VM management
|
||||
#[derive(Debug)]
|
||||
@@ -55,11 +55,15 @@ impl Vmm {
|
||||
}
|
||||
|
||||
pub fn boot_kernel(config: VmConfig) -> Result<()> {
|
||||
let vmm = Vmm::new()?;
|
||||
let mut vm = Vm::new(&vmm.kvm, config).map_err(Error::VmNew)?;
|
||||
loop {
|
||||
let vmm = Vmm::new()?;
|
||||
let mut vm = Vm::new(&vmm.kvm, &config).map_err(Error::VmNew)?;
|
||||
|
||||
let entry = vm.load_kernel().map_err(Error::LoadKernel)?;
|
||||
vm.start(entry).map_err(Error::VmStart)?;
|
||||
let entry = vm.load_kernel().map_err(Error::LoadKernel)?;
|
||||
if vm.start(entry).map_err(Error::VmStart)? == ExitBehaviour::Shutdown {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
723
vmm/src/vm.rs
723
vmm/src/vm.rs
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user