mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa208c147e | ||
|
|
3453eb6e86 | ||
|
|
e2cd6ff8c9 | ||
|
|
3ba8e92c6a | ||
|
|
7796c49afd | ||
|
|
68feea7cbe | ||
|
|
3bf94535d5 | ||
|
|
2e7ff20a11 | ||
|
|
7696bcc71f | ||
|
|
e97524ecf6 | ||
|
|
38dbcc2f44 | ||
|
|
fdd682c0b0 | ||
|
|
9503e1ade9 | ||
|
|
52b2ebb2b8 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -427,7 +427,7 @@ checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cloud-hypervisor"
|
name = "cloud-hypervisor"
|
||||||
version = "51.0.0"
|
version = "51.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"api_client",
|
"api_client",
|
||||||
|
|||||||
@@ -641,17 +641,16 @@ pub fn generate_common_cpuid(
|
|||||||
|
|
||||||
// Update some existing CPUID
|
// Update some existing CPUID
|
||||||
for entry in cpuid.as_mut_slice().iter_mut() {
|
for entry in cpuid.as_mut_slice().iter_mut() {
|
||||||
|
#[allow(unused_unsafe)]
|
||||||
match entry.function {
|
match entry.function {
|
||||||
// Clear AMX related bits if the AMX feature is not enabled
|
// Clear AMX related bits if the AMX feature is not enabled
|
||||||
0x7 => {
|
0x7 if !config.amx => {
|
||||||
if !config.amx {
|
if entry.index == 0 {
|
||||||
if entry.index == 0 {
|
entry.edx &= !((1 << AMX_BF16) | (1 << AMX_TILE) | (1 << AMX_INT8));
|
||||||
entry.edx &= !((1 << AMX_BF16) | (1 << AMX_TILE) | (1 << AMX_INT8));
|
}
|
||||||
}
|
if entry.index == 1 {
|
||||||
if entry.index == 1 {
|
entry.eax &= !(1 << AMX_FP16);
|
||||||
entry.eax &= !(1 << AMX_FP16);
|
entry.edx &= !(1 << AMX_COMPLEX);
|
||||||
entry.edx &= !(1 << AMX_COMPLEX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0xd =>
|
0xd =>
|
||||||
@@ -673,55 +672,46 @@ pub fn generate_common_cpuid(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0x1d => {
|
// Tile Information (purely AMX related).
|
||||||
// Tile Information (purely AMX related).
|
0x1d if !config.amx => {
|
||||||
if !config.amx {
|
entry.eax = 0;
|
||||||
entry.eax = 0;
|
entry.ebx = 0;
|
||||||
entry.ebx = 0;
|
entry.ecx = 0;
|
||||||
entry.ecx = 0;
|
entry.edx = 0;
|
||||||
entry.edx = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
0x1e => {
|
// TMUL information (purely AMX related)
|
||||||
// TMUL information (purely AMX related)
|
0x1e if !config.amx => {
|
||||||
if !config.amx {
|
entry.eax = 0;
|
||||||
entry.eax = 0;
|
entry.ebx = 0;
|
||||||
entry.ebx = 0;
|
entry.ecx = 0;
|
||||||
entry.ecx = 0;
|
entry.edx = 0;
|
||||||
entry.edx = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy host L1 cache details if not populated by KVM
|
// Copy host L1 cache details if not populated by KVM
|
||||||
0x8000_0005 => {
|
0x8000_0005
|
||||||
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0
|
||||||
#[allow(unused_unsafe)]
|
|
||||||
// SAFETY: cpuid called with valid leaves
|
// SAFETY: cpuid called with valid leaves
|
||||||
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0005 {
|
&& unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0005 =>
|
||||||
// SAFETY: cpuid called with valid leaves
|
{
|
||||||
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0005) };
|
// SAFETY: cpuid called with valid leaves
|
||||||
entry.eax = leaf.eax;
|
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0005) };
|
||||||
entry.ebx = leaf.ebx;
|
entry.eax = leaf.eax;
|
||||||
entry.ecx = leaf.ecx;
|
entry.ebx = leaf.ebx;
|
||||||
entry.edx = leaf.edx;
|
entry.ecx = leaf.ecx;
|
||||||
}
|
entry.edx = leaf.edx;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Copy host L2 cache details if not populated by KVM
|
// Copy host L2 cache details if not populated by KVM
|
||||||
0x8000_0006 => {
|
0x8000_0006
|
||||||
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0
|
||||||
#[allow(unused_unsafe)]
|
|
||||||
// SAFETY: cpuid called with valid leaves
|
// SAFETY: cpuid called with valid leaves
|
||||||
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0006 {
|
&& unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0006 =>
|
||||||
#[allow(unused_unsafe)]
|
{
|
||||||
// SAFETY: cpuid called with valid leaves
|
// SAFETY: cpuid called with valid leaves
|
||||||
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0006) };
|
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0006) };
|
||||||
entry.eax = leaf.eax;
|
entry.eax = leaf.eax;
|
||||||
entry.ebx = leaf.ebx;
|
entry.ebx = leaf.ebx;
|
||||||
entry.ecx = leaf.ecx;
|
entry.ecx = leaf.ecx;
|
||||||
entry.edx = leaf.edx;
|
entry.edx = leaf.edx;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Set CPU physical bits
|
// Set CPU physical bits
|
||||||
0x8000_0008 => {
|
0x8000_0008 => {
|
||||||
|
|||||||
90
block/src/aligned_operation.rs
Normal file
90
block/src/aligned_operation.rs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
|
use std::alloc::{Layout, alloc_zeroed, dealloc};
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use vm_memory::GuestAddress;
|
||||||
|
|
||||||
|
/// Owns an aligned bounce buffer used when a guest descriptor's host VA
|
||||||
|
/// does not meet the disk backend's alignment requirement.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AlignedOperation {
|
||||||
|
data_addr: GuestAddress,
|
||||||
|
aligned_ptr: *mut u8,
|
||||||
|
size: usize,
|
||||||
|
layout: Layout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AlignedOperation {
|
||||||
|
/// Allocate a zero-initialized buffer of `size` bytes aligned to
|
||||||
|
/// `alignment`. Returns `InvalidInput` if `size` is zero;
|
||||||
|
/// `alignment` must be a power of two and not exceed `isize::MAX`
|
||||||
|
/// after rounding up.
|
||||||
|
pub fn new(data_addr: GuestAddress, size: usize, alignment: usize) -> io::Result<Self> {
|
||||||
|
if size == 0 {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"AlignedOperation requires a non-zero size",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let layout = Layout::from_size_align(size, alignment)
|
||||||
|
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
|
||||||
|
// SAFETY: size is non-zero (checked above) and Layout::from_size_align
|
||||||
|
// rejects alignments that are not a power of two or that overflow.
|
||||||
|
let aligned_ptr = unsafe { alloc_zeroed(layout) };
|
||||||
|
if aligned_ptr.is_null() {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
data_addr,
|
||||||
|
aligned_ptr,
|
||||||
|
size,
|
||||||
|
layout,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the raw pointer to the aligned buffer.
|
||||||
|
pub fn as_mut_ptr(&mut self) -> *mut u8 {
|
||||||
|
self.aligned_ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the aligned buffer as a slice.
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
// SAFETY: `new` allocates `size` bytes via alloc_zeroed (so they
|
||||||
|
// are initialized) and AlignedOperation owns the buffer
|
||||||
|
// exclusively.
|
||||||
|
unsafe { std::slice::from_raw_parts(self.aligned_ptr, self.size) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the aligned buffer as a mutable slice.
|
||||||
|
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
|
||||||
|
// SAFETY: same invariant as as_bytes; &mut self rules out other
|
||||||
|
// simultaneous borrows.
|
||||||
|
unsafe { std::slice::from_raw_parts_mut(self.aligned_ptr, self.size) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the guest address for this op.
|
||||||
|
pub fn data_addr(&self) -> GuestAddress {
|
||||||
|
self.data_addr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for AlignedOperation {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// SAFETY: `new` is the only constructor, and it stores a pointer
|
||||||
|
// returned by `alloc_zeroed` paired with the exact `layout` used
|
||||||
|
// for that allocation. Ownership has not escaped (the type is
|
||||||
|
// neither `Clone` nor `Copy`).
|
||||||
|
unsafe {
|
||||||
|
dealloc(self.aligned_ptr, self.layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: AlignedOperation owns its heap allocation exclusively (no Clone/
|
||||||
|
// Copy, no shared aliases) and the allocation's lifetime is tied to the
|
||||||
|
// value's. Moving an AlignedOperation between threads transfers that
|
||||||
|
// ownership; the same rationale Box<T> uses for its Send impl.
|
||||||
|
unsafe impl Send for AlignedOperation {}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
|
mod aligned_operation;
|
||||||
pub mod async_io;
|
pub mod async_io;
|
||||||
pub mod fcntl;
|
pub mod fcntl;
|
||||||
pub mod fixed_vhd;
|
pub mod fixed_vhd;
|
||||||
@@ -28,7 +29,7 @@ pub mod vhd;
|
|||||||
pub mod vhdx;
|
pub mod vhdx;
|
||||||
pub mod vhdx_sync;
|
pub mod vhdx_sync;
|
||||||
|
|
||||||
use std::alloc::{Layout, alloc_zeroed, dealloc};
|
use std::alloc::{Layout, alloc_zeroed};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fmt::{self, Debug};
|
use std::fmt::{self, Debug};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@@ -40,6 +41,7 @@ use std::str::FromStr;
|
|||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{cmp, result};
|
use std::{cmp, result};
|
||||||
|
|
||||||
|
pub use aligned_operation::AlignedOperation;
|
||||||
#[cfg(feature = "io_uring")]
|
#[cfg(feature = "io_uring")]
|
||||||
use io_uring::{IoUring, Probe, opcode};
|
use io_uring::{IoUring, Probe, opcode};
|
||||||
use libc::{S_IFBLK, S_IFMT, ioctl};
|
use libc::{S_IFBLK, S_IFMT, ioctl};
|
||||||
@@ -232,14 +234,6 @@ fn sector<B: Bitmap + 'static>(
|
|||||||
|
|
||||||
const DEFAULT_DESCRIPTOR_VEC_SIZE: usize = 32;
|
const DEFAULT_DESCRIPTOR_VEC_SIZE: usize = 32;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct AlignedOperation {
|
|
||||||
origin_ptr: u64,
|
|
||||||
aligned_ptr: u64,
|
|
||||||
size: usize,
|
|
||||||
layout: Layout,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct BatchRequest {
|
pub struct BatchRequest {
|
||||||
pub offset: libc::off_t,
|
pub offset: libc::off_t,
|
||||||
pub iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]>,
|
pub iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]>,
|
||||||
@@ -473,31 +467,19 @@ impl Request {
|
|||||||
let iov_base = if (origin_ptr.as_ptr() as u64).is_multiple_of(SECTOR_SIZE) {
|
let iov_base = if (origin_ptr.as_ptr() as u64).is_multiple_of(SECTOR_SIZE) {
|
||||||
origin_ptr.as_ptr() as *mut libc::c_void
|
origin_ptr.as_ptr() as *mut libc::c_void
|
||||||
} else {
|
} else {
|
||||||
let layout = Layout::from_size_align(data_len, SECTOR_SIZE as usize).unwrap();
|
let mut aligned_op =
|
||||||
// SAFETY: layout has non-zero size
|
AlignedOperation::new(data_addr, data_len, SECTOR_SIZE as usize)
|
||||||
let aligned_ptr = unsafe { alloc_zeroed(layout) };
|
.map_err(ExecuteError::TemporaryBufferAllocation)?;
|
||||||
if aligned_ptr.is_null() {
|
|
||||||
return Err(ExecuteError::TemporaryBufferAllocation(
|
|
||||||
io::Error::last_os_error(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to perform the copy beforehand in case we're writing
|
// We need to perform the copy beforehand in case we're writing
|
||||||
// data out.
|
// data out.
|
||||||
if request_type == RequestType::Out {
|
if request_type == RequestType::Out {
|
||||||
// SAFETY: destination buffer has been allocated with
|
mem.read_slice(aligned_op.as_bytes_mut(), data_addr)
|
||||||
// the proper size.
|
.map_err(ExecuteError::Read)?;
|
||||||
unsafe { std::ptr::copy(origin_ptr.as_ptr(), aligned_ptr, data_len) };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store both origin and aligned pointers for complete_async()
|
let aligned_ptr = aligned_op.as_mut_ptr();
|
||||||
// to process them.
|
self.aligned_operations.push(aligned_op);
|
||||||
self.aligned_operations.push(AlignedOperation {
|
|
||||||
origin_ptr: origin_ptr.as_ptr() as u64,
|
|
||||||
aligned_ptr: aligned_ptr as u64,
|
|
||||||
size: data_len,
|
|
||||||
layout,
|
|
||||||
});
|
|
||||||
|
|
||||||
aligned_ptr as *mut libc::c_void
|
aligned_ptr as *mut libc::c_void
|
||||||
};
|
};
|
||||||
@@ -639,31 +621,17 @@ impl Request {
|
|||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn complete_async(&mut self) -> result::Result<(), Error> {
|
pub fn complete_async<B: Bitmap + 'static>(
|
||||||
for aligned_operation in self.aligned_operations.drain(..) {
|
&mut self,
|
||||||
|
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||||
|
) -> result::Result<(), Error> {
|
||||||
|
for aligned_op in self.aligned_operations.drain(..) {
|
||||||
// We need to perform the copy after the data has been read inside
|
// We need to perform the copy after the data has been read inside
|
||||||
// the aligned buffer in case we're reading data in.
|
// the aligned buffer in case we're reading data in.
|
||||||
if self.request_type == RequestType::In {
|
if self.request_type == RequestType::In {
|
||||||
// SAFETY: origin buffer has been allocated with the
|
mem.write_slice(aligned_op.as_bytes(), aligned_op.data_addr())
|
||||||
// proper size.
|
.map_err(Error::GuestMemory)?;
|
||||||
unsafe {
|
|
||||||
std::ptr::copy(
|
|
||||||
aligned_operation.aligned_ptr as *const u8,
|
|
||||||
aligned_operation.origin_ptr as *mut u8,
|
|
||||||
aligned_operation.size,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free the temporary aligned buffer.
|
|
||||||
// SAFETY: aligned_ptr was allocated by alloc_zeroed with the same
|
|
||||||
// layout
|
|
||||||
unsafe {
|
|
||||||
dealloc(
|
|
||||||
aligned_operation.aligned_ptr as *mut u8,
|
|
||||||
aligned_operation.layout,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -197,6 +197,14 @@ impl AsyncIo for RawFileAsync {
|
|||||||
let (submitter, mut sq, _) = self.io_uring.split();
|
let (submitter, mut sq, _) = self.io_uring.split();
|
||||||
let mut submitted = false;
|
let mut submitted = false;
|
||||||
|
|
||||||
|
// Refuse the whole batch if it can't fit in the SQ to avoid having to unroll a partially
|
||||||
|
// successful push.
|
||||||
|
if batch_request.len() > sq.capacity() - sq.len() {
|
||||||
|
return Err(AsyncIoError::SubmitBatchRequests(Error::other(
|
||||||
|
"io_uring submission queue is full",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
for req in batch_request {
|
for req in batch_request {
|
||||||
match req.request_type {
|
match req.request_type {
|
||||||
RequestType::In => {
|
RequestType::In => {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ edition = "2024"
|
|||||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||||
license = "Apache-2.0 AND BSD-3-Clause"
|
license = "Apache-2.0 AND BSD-3-Clause"
|
||||||
name = "cloud-hypervisor"
|
name = "cloud-hypervisor"
|
||||||
version = "51.0.0"
|
version = "51.2.0"
|
||||||
# Minimum buildable version:
|
# Minimum buildable version:
|
||||||
# Keep in sync with version in .github/workflows/build.yaml
|
# Keep in sync with version in .github/workflows/build.yaml
|
||||||
# Policy on MSRV (see #4318):
|
# Policy on MSRV (see #4318):
|
||||||
|
|||||||
@@ -95,15 +95,16 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
|
|||||||
reporting_queue_evt.write(1).unwrap();
|
reporting_queue_evt.write(1).unwrap();
|
||||||
|
|
||||||
balloon
|
balloon
|
||||||
.activate(
|
.activate(virtio_devices::ActivationContext {
|
||||||
guest_memory,
|
mem: guest_memory,
|
||||||
Arc::new(NoopVirtioInterrupt {}),
|
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
|
||||||
vec![
|
queues: vec![
|
||||||
(0, inflate_q, inflate_evt),
|
(0, inflate_q, inflate_evt),
|
||||||
(1, deflate_q, deflate_evt),
|
(1, deflate_q, deflate_evt),
|
||||||
(2, reporting_q, reporting_evt),
|
(2, reporting_q, reporting_evt),
|
||||||
],
|
],
|
||||||
)
|
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
|
||||||
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
// Wait for the events to finish and balloon device worker thread to return
|
// Wait for the events to finish and balloon device worker thread to return
|
||||||
|
|||||||
@@ -89,11 +89,12 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
|
|||||||
queue_evt.write(1).unwrap();
|
queue_evt.write(1).unwrap();
|
||||||
|
|
||||||
block
|
block
|
||||||
.activate(
|
.activate(virtio_devices::ActivationContext {
|
||||||
guest_memory,
|
mem: guest_memory,
|
||||||
Arc::new(NoopVirtioInterrupt {}),
|
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
|
||||||
vec![(0, q, evt)],
|
queues: vec![(0, q, evt)],
|
||||||
)
|
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
|
||||||
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
// Wait for the events to finish and block device worker thread to return
|
// Wait for the events to finish and block device worker thread to return
|
||||||
|
|||||||
@@ -128,11 +128,12 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
|
|||||||
pipe_tx.write_all(console_input_bytes).unwrap(); // To use fuzzed data;
|
pipe_tx.write_all(console_input_bytes).unwrap(); // To use fuzzed data;
|
||||||
|
|
||||||
console
|
console
|
||||||
.activate(
|
.activate(virtio_devices::ActivationContext {
|
||||||
guest_memory,
|
mem: guest_memory,
|
||||||
Arc::new(NoopVirtioInterrupt {}),
|
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
|
||||||
vec![(0, input_queue, input_evt), (1, output_queue, output_evt)],
|
queues: vec![(0, input_queue, input_evt), (1, output_queue, output_evt)],
|
||||||
)
|
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Wait for the events to finish and console device worker thread to return
|
// Wait for the events to finish and console device worker thread to return
|
||||||
|
|||||||
@@ -107,14 +107,15 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
|
|||||||
request_queue_evt.write(1).unwrap();
|
request_queue_evt.write(1).unwrap();
|
||||||
|
|
||||||
iommu
|
iommu
|
||||||
.activate(
|
.activate(virtio_devices::ActivationContext {
|
||||||
guest_memory,
|
mem: guest_memory,
|
||||||
Arc::new(NoopVirtioInterrupt {}),
|
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
|
||||||
vec![
|
queues: vec![
|
||||||
(0, request_queue, request_evt),
|
(0, request_queue, request_evt),
|
||||||
(0, _event_queue, _event_evt),
|
(0, _event_queue, _event_evt),
|
||||||
],
|
],
|
||||||
)
|
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
|
||||||
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
// Wait for the events to finish and vIOMMU device worker thread to return
|
// Wait for the events to finish and vIOMMU device worker thread to return
|
||||||
|
|||||||
@@ -105,11 +105,12 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
|
|||||||
queue_evt.write(1).unwrap();
|
queue_evt.write(1).unwrap();
|
||||||
|
|
||||||
virtio_mem
|
virtio_mem
|
||||||
.activate(
|
.activate(virtio_devices::ActivationContext {
|
||||||
guest_memory,
|
mem: guest_memory,
|
||||||
Arc::new(NoopVirtioInterrupt {}),
|
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
|
||||||
vec![(0, q, evt)],
|
queues: vec![(0, q, evt)],
|
||||||
)
|
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
|
||||||
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
// Wait for the events to finish and virtio-mem device worker thread to return
|
// Wait for the events to finish and virtio-mem device worker thread to return
|
||||||
|
|||||||
@@ -143,11 +143,12 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
|
|||||||
input_queue_evt.write(1).unwrap();
|
input_queue_evt.write(1).unwrap();
|
||||||
output_queue_evt.write(1).unwrap();
|
output_queue_evt.write(1).unwrap();
|
||||||
|
|
||||||
net.activate(
|
net.activate(virtio_devices::ActivationContext {
|
||||||
guest_memory,
|
mem: guest_memory,
|
||||||
Arc::new(NoopVirtioInterrupt {}),
|
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
|
||||||
vec![(0, input_queue, input_evt), (1, output_queue, output_evt)],
|
queues: vec![(0, input_queue, input_evt), (1, output_queue, output_evt)],
|
||||||
)
|
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Wait for the events to finish and net device worker thread to return
|
// Wait for the events to finish and net device worker thread to return
|
||||||
|
|||||||
@@ -61,11 +61,12 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
|
|||||||
// Kick the 'queue' event before activate the pmem device
|
// Kick the 'queue' event before activate the pmem device
|
||||||
queue_evt.write(1).unwrap();
|
queue_evt.write(1).unwrap();
|
||||||
|
|
||||||
pmem.activate(
|
pmem.activate(virtio_devices::ActivationContext {
|
||||||
guest_memory,
|
mem: guest_memory,
|
||||||
Arc::new(NoopVirtioInterrupt {}),
|
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
|
||||||
vec![(0, q, evt)],
|
queues: vec![(0, q, evt)],
|
||||||
)
|
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
|
||||||
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
// Wait for the events to finish and pmem device worker thread to return
|
// Wait for the events to finish and pmem device worker thread to return
|
||||||
|
|||||||
@@ -99,11 +99,12 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
|
|||||||
// Kick the 'queue' event before activate the rng device
|
// Kick the 'queue' event before activate the rng device
|
||||||
queue_evt.write(1).unwrap();
|
queue_evt.write(1).unwrap();
|
||||||
|
|
||||||
rng.activate(
|
rng.activate(virtio_devices::ActivationContext {
|
||||||
guest_memory,
|
mem: guest_memory,
|
||||||
Arc::new(NoopVirtioInterrupt {}),
|
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
|
||||||
vec![(0, q, evt)],
|
queues: vec![(0, q, evt)],
|
||||||
)
|
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
|
||||||
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
// Wait for the events to finish and rng device worker thread to return
|
// Wait for the events to finish and rng device worker thread to return
|
||||||
|
|||||||
@@ -108,11 +108,12 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
vsock
|
vsock
|
||||||
.activate(
|
.activate(virtio_devices::ActivationContext {
|
||||||
guest_memory,
|
mem: guest_memory,
|
||||||
Arc::new(NoopVirtioInterrupt {}),
|
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
|
||||||
vec![(0, q, evt)],
|
queues: vec![(0, q, evt)],
|
||||||
)
|
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
|
||||||
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
// Wait for the events to finish and vsock device worker thread to return
|
// Wait for the events to finish and vsock device worker thread to return
|
||||||
|
|||||||
@@ -64,11 +64,12 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
|
|||||||
queue_evt.write(1).unwrap();
|
queue_evt.write(1).unwrap();
|
||||||
|
|
||||||
watchdog
|
watchdog
|
||||||
.activate(
|
.activate(virtio_devices::ActivationContext {
|
||||||
guest_memory,
|
mem: guest_memory,
|
||||||
Arc::new(NoopVirtioInterrupt {}),
|
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
|
||||||
vec![(0, q, evt)],
|
queues: vec![(0, q, evt)],
|
||||||
)
|
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
|
||||||
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
// Wait for the events to finish and watchdog device worker thread to return
|
// Wait for the events to finish and watchdog device worker thread to return
|
||||||
|
|||||||
@@ -90,11 +90,11 @@ pub use kvm_bindings::kvm_vcpu_events as VcpuEvents;
|
|||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
use kvm_bindings::nested::KvmNestedStateBuffer;
|
use kvm_bindings::nested::KvmNestedStateBuffer;
|
||||||
pub use kvm_bindings::{
|
pub use kvm_bindings::{
|
||||||
KVM_GUESTDBG_ENABLE, KVM_GUESTDBG_SINGLESTEP, KVM_IRQ_ROUTING_IRQCHIP, KVM_IRQ_ROUTING_MSI,
|
self, KVM_GUESTDBG_ENABLE, KVM_GUESTDBG_SINGLESTEP, KVM_IRQ_ROUTING_IRQCHIP,
|
||||||
KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY, KVM_MSI_VALID_DEVID, kvm_clock_data,
|
KVM_IRQ_ROUTING_MSI, KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY, KVM_MSI_VALID_DEVID,
|
||||||
kvm_create_device, kvm_create_device as CreateDevice, kvm_device_attr as DeviceAttr,
|
kvm_clock_data, kvm_create_device, kvm_create_device as CreateDevice,
|
||||||
kvm_device_type_KVM_DEV_TYPE_VFIO, kvm_guest_debug, kvm_irq_routing, kvm_irq_routing_entry,
|
kvm_device_attr as DeviceAttr, kvm_device_type_KVM_DEV_TYPE_VFIO, kvm_guest_debug,
|
||||||
kvm_mp_state, kvm_run, kvm_userspace_memory_region,
|
kvm_irq_routing, kvm_irq_routing_entry, kvm_mp_state, kvm_run, kvm_userspace_memory_region,
|
||||||
};
|
};
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
use kvm_bindings::{
|
use kvm_bindings::{
|
||||||
@@ -109,14 +109,13 @@ use kvm_bindings::{KVM_REG_RISCV_CORE, kvm_riscv_core};
|
|||||||
use kvm_bindings::{KVM_X86_DEFAULT_VM, KVM_X86_SW_PROTECTED_VM, KVMIO, kvm_run__bindgen_ty_1};
|
use kvm_bindings::{KVM_X86_DEFAULT_VM, KVM_X86_SW_PROTECTED_VM, KVMIO, kvm_run__bindgen_ty_1};
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
use kvm_bindings::{Xsave as xsave2, kvm_xsave2};
|
use kvm_bindings::{Xsave as xsave2, kvm_xsave2};
|
||||||
pub use kvm_ioctls::{Cap, Kvm, VcpuExit};
|
pub use kvm_ioctls::{self, Cap, Kvm, VcpuExit};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vfio_ioctls::VfioDeviceFd;
|
use vfio_ioctls::VfioDeviceFd;
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
use vmm_sys_util::{fam::FamStruct, ioctl_io_nr};
|
use vmm_sys_util::{fam::FamStruct, ioctl_io_nr};
|
||||||
#[cfg(feature = "tdx")]
|
#[cfg(feature = "tdx")]
|
||||||
use vmm_sys_util::{ioctl::ioctl_with_val, ioctl_iowr_nr};
|
use vmm_sys_util::{ioctl::ioctl_with_val, ioctl_iowr_nr};
|
||||||
pub use {kvm_bindings, kvm_ioctls};
|
|
||||||
|
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||||
use crate::RegList;
|
use crate::RegList;
|
||||||
|
|||||||
@@ -51,7 +51,13 @@ impl TxVirtio {
|
|||||||
let mut retry_write = false;
|
let mut retry_write = false;
|
||||||
let mut rate_limit_reached = false;
|
let mut rate_limit_reached = false;
|
||||||
|
|
||||||
while let Some(mut desc_chain) = queue.pop_descriptor_chain(mem) {
|
loop {
|
||||||
|
let mut iter = queue
|
||||||
|
.iter(mem)
|
||||||
|
.map_err(NetQueuePairError::QueueIteratorFailed)?;
|
||||||
|
let Some(mut desc_chain) = iter.next() else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
if rate_limit_reached {
|
if rate_limit_reached {
|
||||||
queue.go_to_previous_position();
|
queue.go_to_previous_position();
|
||||||
break;
|
break;
|
||||||
@@ -180,7 +186,13 @@ impl RxVirtio {
|
|||||||
let mut exhausted_descs = true;
|
let mut exhausted_descs = true;
|
||||||
let mut rate_limit_reached = false;
|
let mut rate_limit_reached = false;
|
||||||
|
|
||||||
while let Some(mut desc_chain) = queue.pop_descriptor_chain(mem) {
|
loop {
|
||||||
|
let mut iter = queue
|
||||||
|
.iter(mem)
|
||||||
|
.map_err(NetQueuePairError::QueueIteratorFailed)?;
|
||||||
|
let Some(mut desc_chain) = iter.next() else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
if rate_limit_reached {
|
if rate_limit_reached {
|
||||||
exhausted_descs = false;
|
exhausted_descs = false;
|
||||||
queue.go_to_previous_position();
|
queue.go_to_previous_position();
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
- [v51.2](#v512)
|
||||||
|
- [v51.1](#v511)
|
||||||
- [v51.0](#v510)
|
- [v51.0](#v510)
|
||||||
- [Security Fixes](#security-fixes)
|
- [Security Fixes](#security-fixes)
|
||||||
- [Significant QCOW2 v3 Improvements](#significant-qcow2-v3-improvements)
|
- [Significant QCOW2 v3 Improvements](#significant-qcow2-v3-improvements)
|
||||||
@@ -418,6 +420,18 @@
|
|||||||
- [Unit testing](#unit-testing)
|
- [Unit testing](#unit-testing)
|
||||||
- [Integration tests parallelization](#integration-tests-parallelization)
|
- [Integration tests parallelization](#integration-tests-parallelization)
|
||||||
|
|
||||||
|
# v51.2
|
||||||
|
|
||||||
|
This is a point release containing security fixes to a use-after-free
|
||||||
|
vulnerability in the `virtio-block` async I/O completion path
|
||||||
|
(#8220). Details can be found in GHSA-f47p-p25q-83rh (CVE-2026-45782).
|
||||||
|
|
||||||
|
# v51.1
|
||||||
|
|
||||||
|
This is a bug fix release. The following issues have been addressed:
|
||||||
|
|
||||||
|
* Fix image_type in OpenAPI definition (#7734)
|
||||||
|
|
||||||
# v51.0
|
# v51.0
|
||||||
|
|
||||||
This release has been tracked in [v51.0
|
This release has been tracked in [v51.0
|
||||||
|
|||||||
@@ -590,12 +590,13 @@ impl VirtioDevice for Balloon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult {
|
||||||
&mut self,
|
let crate::device::ActivationContext {
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
mem,
|
||||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
interrupt_cb,
|
||||||
mut queues: Vec<(usize, Queue, EventFd)>,
|
mut queues,
|
||||||
) -> ActivateResult {
|
..
|
||||||
|
} = context;
|
||||||
self.common.activate(&queues, interrupt_cb.clone())?;
|
self.common.activate(&queues, interrupt_cb.clone())?;
|
||||||
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use std::num::Wrapping;
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering};
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::{io, result};
|
use std::{io, result};
|
||||||
|
|
||||||
@@ -161,6 +161,7 @@ struct BlockEpollHandler {
|
|||||||
host_cpus: Option<Vec<usize>>,
|
host_cpus: Option<Vec<usize>>,
|
||||||
acked_features: u64,
|
acked_features: u64,
|
||||||
disable_sector0_writes: bool,
|
disable_sector0_writes: bool,
|
||||||
|
device_status: Arc<AtomicU8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_feature(features: u64, feature_flag: u64) -> bool {
|
fn has_feature(features: u64, feature_flag: u64) -> bool {
|
||||||
@@ -168,6 +169,10 @@ fn has_feature(features: u64, feature_flag: u64) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BlockEpollHandler {
|
impl BlockEpollHandler {
|
||||||
|
fn needs_reset(&self) -> bool {
|
||||||
|
(self.device_status.load(Ordering::Acquire) & crate::DEVICE_NEEDS_RESET as u8) != 0
|
||||||
|
}
|
||||||
|
|
||||||
fn check_request(
|
fn check_request(
|
||||||
features: u64,
|
features: u64,
|
||||||
request: &Request,
|
request: &Request,
|
||||||
@@ -192,12 +197,71 @@ impl BlockEpollHandler {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_queue_iterator_error(&mut self, err: &virtio_queue::Error) {
|
||||||
|
// The guest submitted a corrupted VirtQ request, and the error
|
||||||
|
// was logged during queue processing. We cannot just ignore the
|
||||||
|
// error, as the guest could continue spamming the VMM with bad
|
||||||
|
// requests, triggering excessive error logging. So we mark
|
||||||
|
// the device "NEEDS_RESET", effectively stopping all request
|
||||||
|
// processing (see self.needs_reset() usage) until the guest
|
||||||
|
// resets and reactivates the device.
|
||||||
|
|
||||||
|
warn!(
|
||||||
|
"Corrupted request detected (virtqueue error: {err:?}). \
|
||||||
|
Setting device status to 'NEEDS_RESET' and stopping processing queues until reset."
|
||||||
|
);
|
||||||
|
|
||||||
|
self.set_needs_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_needs_reset(&mut self) {
|
||||||
|
self.device_status
|
||||||
|
.fetch_or(crate::DEVICE_NEEDS_RESET as u8, Ordering::SeqCst);
|
||||||
|
|
||||||
|
// Let the guest know that the device status has changed.
|
||||||
|
if let Err(e) = self.interrupt_cb.trigger(VirtioInterruptType::Config) {
|
||||||
|
error!("Failed to signal config interrupt: {e:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A spec-compliant driver never reuses a virtqueue head_index while the
|
||||||
|
// corresponding chain is still available (virtio 1.x §2.7.13.4).
|
||||||
|
// Double check the guest driver is behaving.
|
||||||
|
fn is_head_in_flight(
|
||||||
|
inflight: &VecDeque<(u16, Request)>,
|
||||||
|
batch: &[(u16, Request)],
|
||||||
|
head: u16,
|
||||||
|
) -> bool {
|
||||||
|
batch.iter().any(|(h, _)| *h == head) || inflight.iter().any(|(h, _)| *h == head)
|
||||||
|
}
|
||||||
|
|
||||||
fn process_queue_submit(&mut self) -> Result<()> {
|
fn process_queue_submit(&mut self) -> Result<()> {
|
||||||
|
if self.needs_reset() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let queue = &mut self.queue;
|
let queue = &mut self.queue;
|
||||||
let mut batch_requests = Vec::new();
|
let mut batch_requests = Vec::new();
|
||||||
let mut batch_inflight_requests = Vec::new();
|
let mut batch_inflight_requests = Vec::new();
|
||||||
|
|
||||||
while let Some(mut desc_chain) = queue.pop_descriptor_chain(self.mem.memory()) {
|
loop {
|
||||||
|
let mut desc_chain = match queue.iter(self.mem.memory()) {
|
||||||
|
Ok(mut iter) => match iter.next() {
|
||||||
|
Some(c) => c,
|
||||||
|
None => break,
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
self.handle_queue_iterator_error(&err);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let head = desc_chain.head_index();
|
||||||
|
if Self::is_head_in_flight(&self.inflight_requests, &batch_inflight_requests, head) {
|
||||||
|
warn!("Guest reused virtio-blk head_index {head} while the chain was used");
|
||||||
|
self.set_needs_reset();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let mut request = Request::parse(&mut desc_chain, self.access_platform.as_deref())
|
let mut request = Request::parse(&mut desc_chain, self.access_platform.as_deref())
|
||||||
.map_err(Error::RequestParsing)?;
|
.map_err(Error::RequestParsing)?;
|
||||||
|
|
||||||
@@ -282,8 +346,11 @@ impl BlockEpollHandler {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
batch_inflight_requests.push((desc_chain.head_index(), request));
|
||||||
|
} else {
|
||||||
|
self.inflight_requests
|
||||||
|
.push_back((desc_chain.head_index(), request));
|
||||||
}
|
}
|
||||||
batch_inflight_requests.push((desc_chain.head_index(), request));
|
|
||||||
} else {
|
} else {
|
||||||
let status = match result {
|
let status = match result {
|
||||||
Ok(_) => VIRTIO_BLK_S_OK,
|
Ok(_) => VIRTIO_BLK_S_OK,
|
||||||
@@ -380,6 +447,9 @@ impl BlockEpollHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn process_queue_complete(&mut self) -> Result<()> {
|
fn process_queue_complete(&mut self) -> Result<()> {
|
||||||
|
if self.needs_reset() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let mem = self.mem.memory();
|
let mem = self.mem.memory();
|
||||||
let mut read_bytes = Wrapping(0);
|
let mut read_bytes = Wrapping(0);
|
||||||
let mut write_bytes = Wrapping(0);
|
let mut write_bytes = Wrapping(0);
|
||||||
@@ -391,7 +461,9 @@ impl BlockEpollHandler {
|
|||||||
|
|
||||||
let mut request = self.find_inflight_request(desc_index)?;
|
let mut request = self.find_inflight_request(desc_index)?;
|
||||||
|
|
||||||
request.complete_async().map_err(Error::RequestCompleting)?;
|
request
|
||||||
|
.complete_async(&mem)
|
||||||
|
.map_err(Error::RequestCompleting)?;
|
||||||
|
|
||||||
let latency = request.start.elapsed().as_micros() as u64;
|
let latency = request.start.elapsed().as_micros() as u64;
|
||||||
let read_ops_last = self.counters.read_ops.load(Ordering::Relaxed);
|
let read_ops_last = self.counters.read_ops.load(Ordering::Relaxed);
|
||||||
@@ -662,6 +734,7 @@ pub struct Block {
|
|||||||
serial: Vec<u8>,
|
serial: Vec<u8>,
|
||||||
queue_affinity: BTreeMap<u16, Vec<usize>>,
|
queue_affinity: BTreeMap<u16, Vec<usize>>,
|
||||||
disable_sector0_writes: bool,
|
disable_sector0_writes: bool,
|
||||||
|
device_status: Arc<AtomicU8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -807,6 +880,7 @@ impl Block {
|
|||||||
serial,
|
serial,
|
||||||
queue_affinity,
|
queue_affinity,
|
||||||
disable_sector0_writes,
|
disable_sector0_writes,
|
||||||
|
device_status: Arc::new(AtomicU8::new(0)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -998,12 +1072,14 @@ impl VirtioDevice for Block {
|
|||||||
self.update_writeback();
|
self.update_writeback();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult {
|
||||||
&mut self,
|
let crate::device::ActivationContext {
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
mem,
|
||||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
interrupt_cb,
|
||||||
mut queues: Vec<(usize, Queue, EventFd)>,
|
mut queues,
|
||||||
) -> ActivateResult {
|
device_status,
|
||||||
|
} = context;
|
||||||
|
self.device_status = device_status;
|
||||||
// See if the guest didn't ack the device being read-only.
|
// See if the guest didn't ack the device being read-only.
|
||||||
// If so, warn and pretend it did.
|
// If so, warn and pretend it did.
|
||||||
let original_acked_features = self.common.acked_features;
|
let original_acked_features = self.common.acked_features;
|
||||||
@@ -1059,6 +1135,7 @@ impl VirtioDevice for Block {
|
|||||||
host_cpus: self.queue_affinity.get(&queue_idx).cloned(),
|
host_cpus: self.queue_affinity.get(&queue_idx).cloned(),
|
||||||
acked_features: self.common.acked_features,
|
acked_features: self.common.acked_features,
|
||||||
disable_sector0_writes: self.disable_sector0_writes,
|
disable_sector0_writes: self.disable_sector0_writes,
|
||||||
|
device_status: self.device_status.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let paused = self.common.paused.clone();
|
let paused = self.common.paused.clone();
|
||||||
|
|||||||
@@ -703,12 +703,13 @@ impl VirtioDevice for Console {
|
|||||||
self.read_config_from_slice(self.config.lock().unwrap().as_slice(), offset, data);
|
self.read_config_from_slice(self.config.lock().unwrap().as_slice(), offset, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult {
|
||||||
&mut self,
|
let crate::device::ActivationContext {
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
mem,
|
||||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
interrupt_cb,
|
||||||
mut queues: Vec<(usize, Queue, EventFd)>,
|
mut queues,
|
||||||
) -> ActivateResult {
|
..
|
||||||
|
} = context;
|
||||||
self.common.activate(&queues, interrupt_cb.clone())?;
|
self.common.activate(&queues, interrupt_cb.clone())?;
|
||||||
self.resizer
|
self.resizer
|
||||||
.acked_features
|
.acked_features
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::num::Wrapping;
|
use std::num::Wrapping;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
@@ -53,6 +53,13 @@ pub struct VirtioSharedMemoryList {
|
|||||||
pub region_list: Vec<VirtioSharedMemory>,
|
pub region_list: Vec<VirtioSharedMemory>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ActivationContext {
|
||||||
|
pub mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||||
|
pub interrupt_cb: Arc<dyn VirtioInterrupt>,
|
||||||
|
pub queues: Vec<(usize, Queue, EventFd)>,
|
||||||
|
pub device_status: Arc<AtomicU8>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Trait for virtio devices to be driven by a virtio transport.
|
/// Trait for virtio devices to be driven by a virtio transport.
|
||||||
///
|
///
|
||||||
/// The lifecycle of a virtio device is to be moved to a virtio transport, which will then query the
|
/// The lifecycle of a virtio device is to be moved to a virtio transport, which will then query the
|
||||||
@@ -94,12 +101,7 @@ pub trait VirtioDevice: Send {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Activates this device for real usage.
|
/// Activates this device for real usage.
|
||||||
fn activate(
|
fn activate(&mut self, context: ActivationContext) -> ActivateResult;
|
||||||
&mut self,
|
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
|
||||||
interrupt_evt: Arc<dyn VirtioInterrupt>,
|
|
||||||
queues: Vec<(usize, Queue, EventFd)>,
|
|
||||||
) -> ActivateResult;
|
|
||||||
|
|
||||||
/// Optionally deactivates this device and returns ownership of the guest memory map, interrupt
|
/// Optionally deactivates this device and returns ownership of the guest memory map, interrupt
|
||||||
/// event, and queue events.
|
/// event, and queue events.
|
||||||
|
|||||||
@@ -1075,12 +1075,13 @@ impl VirtioDevice for Iommu {
|
|||||||
self.update_bypass();
|
self.update_bypass();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult {
|
||||||
&mut self,
|
let crate::device::ActivationContext {
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
mem,
|
||||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
interrupt_cb,
|
||||||
mut queues: Vec<(usize, Queue, EventFd)>,
|
mut queues,
|
||||||
) -> ActivateResult {
|
..
|
||||||
|
} = context;
|
||||||
self.common.activate(&queues, interrupt_cb.clone())?;
|
self.common.activate(&queues, interrupt_cb.clone())?;
|
||||||
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ pub use self::balloon::Balloon;
|
|||||||
pub use self::block::{Block, BlockState};
|
pub use self::block::{Block, BlockState};
|
||||||
pub use self::console::{Console, ConsoleResizer, Endpoint};
|
pub use self::console::{Console, ConsoleResizer, Endpoint};
|
||||||
pub use self::device::{
|
pub use self::device::{
|
||||||
DmaRemapping, VirtioCommon, VirtioDevice, VirtioInterrupt, VirtioInterruptType,
|
ActivationContext, DmaRemapping, VirtioCommon, VirtioDevice, VirtioInterrupt,
|
||||||
VirtioSharedMemoryList,
|
VirtioInterruptType, VirtioSharedMemoryList,
|
||||||
};
|
};
|
||||||
pub use self::epoll_helper::{
|
pub use self::epoll_helper::{
|
||||||
EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler,
|
EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler,
|
||||||
@@ -66,6 +66,7 @@ const DEVICE_ACKNOWLEDGE: u32 = 0x01;
|
|||||||
const DEVICE_DRIVER: u32 = 0x02;
|
const DEVICE_DRIVER: u32 = 0x02;
|
||||||
const DEVICE_DRIVER_OK: u32 = 0x04;
|
const DEVICE_DRIVER_OK: u32 = 0x04;
|
||||||
const DEVICE_FEATURES_OK: u32 = 0x08;
|
const DEVICE_FEATURES_OK: u32 = 0x08;
|
||||||
|
const DEVICE_NEEDS_RESET: u32 = 0x40;
|
||||||
const DEVICE_FAILED: u32 = 0x80;
|
const DEVICE_FAILED: u32 = 0x80;
|
||||||
|
|
||||||
const VIRTIO_F_RING_INDIRECT_DESC: u32 = 28;
|
const VIRTIO_F_RING_INDIRECT_DESC: u32 = 28;
|
||||||
|
|||||||
@@ -950,12 +950,13 @@ impl VirtioDevice for Mem {
|
|||||||
self.read_config_from_slice(self.config.lock().unwrap().as_slice(), offset, data);
|
self.read_config_from_slice(self.config.lock().unwrap().as_slice(), offset, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult {
|
||||||
&mut self,
|
let crate::device::ActivationContext {
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
mem,
|
||||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
interrupt_cb,
|
||||||
mut queues: Vec<(usize, Queue, EventFd)>,
|
mut queues,
|
||||||
) -> ActivateResult {
|
..
|
||||||
|
} = context;
|
||||||
self.common.activate(&queues, interrupt_cb.clone())?;
|
self.common.activate(&queues, interrupt_cb.clone())?;
|
||||||
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ use std::net::IpAddr;
|
|||||||
use std::num::Wrapping;
|
use std::num::Wrapping;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::{result, thread};
|
use std::{result, thread};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use event_monitor::event;
|
use event_monitor::event;
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info, warn};
|
||||||
#[cfg(not(fuzzing))]
|
#[cfg(not(fuzzing))]
|
||||||
use net_util::virtio_features_to_tap_offload;
|
use net_util::virtio_features_to_tap_offload;
|
||||||
use net_util::{
|
use net_util::{
|
||||||
@@ -179,6 +179,7 @@ struct NetEpollHandler {
|
|||||||
// a restore as the vCPU thread isn't ready to handle the interrupt. This causes
|
// a restore as the vCPU thread isn't ready to handle the interrupt. This causes
|
||||||
// issues when combined with VIRTIO_RING_F_EVENT_IDX interrupt suppression.
|
// issues when combined with VIRTIO_RING_F_EVENT_IDX interrupt suppression.
|
||||||
driver_awake: bool,
|
driver_awake: bool,
|
||||||
|
device_status: Arc<AtomicU8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetEpollHandler {
|
impl NetEpollHandler {
|
||||||
@@ -192,6 +193,9 @@ impl NetEpollHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_rx_event(&mut self) -> result::Result<(), DeviceError> {
|
fn handle_rx_event(&mut self) -> result::Result<(), DeviceError> {
|
||||||
|
if self.needs_reset() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let queue_evt = &self.queue_evt_pair.0;
|
let queue_evt = &self.queue_evt_pair.0;
|
||||||
if let Err(e) = queue_evt.read() {
|
if let Err(e) = queue_evt.read() {
|
||||||
error!("Failed to get rx queue event: {e:?}");
|
error!("Failed to get rx queue event: {e:?}");
|
||||||
@@ -220,13 +224,43 @@ impl NetEpollHandler {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_queue_iterator_error(&mut self, err: &virtio_queue::Error) {
|
||||||
|
// The guest submitted a corrupted VirtQ request, and the error
|
||||||
|
// was logged during queue processing. We cannot just ignore the
|
||||||
|
// error, as the guest could continue spamming the VMM with bad
|
||||||
|
// requests, triggering excessive error logging. So we mark
|
||||||
|
// the device "NEEDS_RESET", effectively stopping all request
|
||||||
|
// processing (see self.needs_reset() usage) until the guest
|
||||||
|
// resets and reactivates the device.
|
||||||
|
|
||||||
|
warn!(
|
||||||
|
"Corrupted request detected (virtqueue error: {err:?}). \
|
||||||
|
Setting device status to 'NEEDS_RESET' and stopping processing queues until reset."
|
||||||
|
);
|
||||||
|
|
||||||
|
self.device_status
|
||||||
|
.fetch_or(crate::DEVICE_NEEDS_RESET as u8, Ordering::SeqCst);
|
||||||
|
|
||||||
|
// Let the guest know that the device status has changed.
|
||||||
|
if let Err(e) = self.interrupt_cb.trigger(VirtioInterruptType::Config) {
|
||||||
|
error!("Failed to signal config interrupt: {e:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn process_tx(&mut self) -> result::Result<(), DeviceError> {
|
fn process_tx(&mut self) -> result::Result<(), DeviceError> {
|
||||||
if self
|
if self.needs_reset() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let res = self
|
||||||
.net
|
.net
|
||||||
.process_tx(&self.mem.memory(), &mut self.queue_pair.1)
|
.process_tx(&self.mem.memory(), &mut self.queue_pair.1);
|
||||||
.map_err(DeviceError::NetQueuePair)?
|
|
||||||
|| !self.driver_awake
|
if let Err(net_util::NetQueuePairError::QueueIteratorFailed(err)) = res {
|
||||||
{
|
self.handle_queue_iterator_error(&err);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.map_err(DeviceError::NetQueuePair)? || !self.driver_awake {
|
||||||
self.signal_used_queue(self.queue_index_base + 1)?;
|
self.signal_used_queue(self.queue_index_base + 1)?;
|
||||||
debug!("Signalling TX queue");
|
debug!("Signalling TX queue");
|
||||||
} else {
|
} else {
|
||||||
@@ -250,12 +284,19 @@ impl NetEpollHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_rx_tap_event(&mut self) -> result::Result<(), DeviceError> {
|
fn handle_rx_tap_event(&mut self) -> result::Result<(), DeviceError> {
|
||||||
if self
|
if self.needs_reset() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let res = self
|
||||||
.net
|
.net
|
||||||
.process_rx(&self.mem.memory(), &mut self.queue_pair.0)
|
.process_rx(&self.mem.memory(), &mut self.queue_pair.0);
|
||||||
.map_err(DeviceError::NetQueuePair)?
|
|
||||||
|| !self.driver_awake
|
if let Err(net_util::NetQueuePairError::QueueIteratorFailed(err)) = res {
|
||||||
{
|
self.handle_queue_iterator_error(&err);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.map_err(DeviceError::NetQueuePair)? || !self.driver_awake {
|
||||||
self.signal_used_queue(self.queue_index_base)?;
|
self.signal_used_queue(self.queue_index_base)?;
|
||||||
debug!("Signalling RX queue");
|
debug!("Signalling RX queue");
|
||||||
} else {
|
} else {
|
||||||
@@ -305,6 +346,10 @@ impl NetEpollHandler {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn needs_reset(&self) -> bool {
|
||||||
|
(self.device_status.load(Ordering::Acquire) & crate::DEVICE_NEEDS_RESET as u8) != 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EpollHelperHandler for NetEpollHandler {
|
impl EpollHelperHandler for NetEpollHandler {
|
||||||
@@ -414,6 +459,7 @@ pub struct Net {
|
|||||||
seccomp_action: SeccompAction,
|
seccomp_action: SeccompAction,
|
||||||
rate_limiter_config: Option<RateLimiterConfig>,
|
rate_limiter_config: Option<RateLimiterConfig>,
|
||||||
exit_evt: EventFd,
|
exit_evt: EventFd,
|
||||||
|
device_status: Arc<AtomicU8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -535,6 +581,7 @@ impl Net {
|
|||||||
seccomp_action,
|
seccomp_action,
|
||||||
rate_limiter_config,
|
rate_limiter_config,
|
||||||
exit_evt,
|
exit_evt,
|
||||||
|
device_status: Arc::new(AtomicU8::new(0)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,12 +740,14 @@ impl VirtioDevice for Net {
|
|||||||
self.read_config_from_slice(self.config.as_slice(), offset, data);
|
self.read_config_from_slice(self.config.as_slice(), offset, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult {
|
||||||
&mut self,
|
let crate::device::ActivationContext {
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
mem,
|
||||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
interrupt_cb,
|
||||||
mut queues: Vec<(usize, Queue, EventFd)>,
|
mut queues,
|
||||||
) -> ActivateResult {
|
device_status,
|
||||||
|
} = context;
|
||||||
|
self.device_status = device_status;
|
||||||
self.common.activate(&queues, interrupt_cb.clone())?;
|
self.common.activate(&queues, interrupt_cb.clone())?;
|
||||||
|
|
||||||
let num_queues = queues.len();
|
let num_queues = queues.len();
|
||||||
@@ -803,6 +852,7 @@ impl VirtioDevice for Net {
|
|||||||
kill_evt,
|
kill_evt,
|
||||||
pause_evt,
|
pause_evt,
|
||||||
driver_awake: false,
|
driver_awake: false,
|
||||||
|
device_status: self.device_status.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let paused = self.common.paused.clone();
|
let paused = self.common.paused.clone();
|
||||||
|
|||||||
@@ -377,12 +377,13 @@ impl VirtioDevice for Pmem {
|
|||||||
self.read_config_from_slice(self.config.as_slice(), offset, data);
|
self.read_config_from_slice(self.config.as_slice(), offset, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult {
|
||||||
&mut self,
|
let crate::device::ActivationContext {
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
mem,
|
||||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
interrupt_cb,
|
||||||
mut queues: Vec<(usize, Queue, EventFd)>,
|
mut queues,
|
||||||
) -> ActivateResult {
|
..
|
||||||
|
} = context;
|
||||||
self.common.activate(&queues, interrupt_cb.clone())?;
|
self.common.activate(&queues, interrupt_cb.clone())?;
|
||||||
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
||||||
if let Some(disk) = self.disk.as_ref() {
|
if let Some(disk) = self.disk.as_ref() {
|
||||||
|
|||||||
@@ -244,12 +244,13 @@ impl VirtioDevice for Rng {
|
|||||||
self.common.ack_features(value);
|
self.common.ack_features(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult {
|
||||||
&mut self,
|
let crate::device::ActivationContext {
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
mem,
|
||||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
interrupt_cb,
|
||||||
mut queues: Vec<(usize, Queue, EventFd)>,
|
mut queues,
|
||||||
) -> ActivateResult {
|
..
|
||||||
|
} = context;
|
||||||
self.common.activate(&queues, interrupt_cb.clone())?;
|
self.common.activate(&queues, interrupt_cb.clone())?;
|
||||||
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicU16, Ordering};
|
use std::sync::atomic::{AtomicU8, AtomicU16, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
@@ -125,7 +125,7 @@ pub fn get_vring_size(t: VringType, queue_size: u16) -> u64 {
|
|||||||
/// le64 queue_used; // 0x30 // read-write
|
/// le64 queue_used; // 0x30 // read-write
|
||||||
pub struct VirtioPciCommonConfig {
|
pub struct VirtioPciCommonConfig {
|
||||||
pub access_platform: Option<Arc<dyn AccessPlatform>>,
|
pub access_platform: Option<Arc<dyn AccessPlatform>>,
|
||||||
pub driver_status: u8,
|
pub driver_status: Arc<AtomicU8>,
|
||||||
pub config_generation: u8,
|
pub config_generation: u8,
|
||||||
pub device_feature_select: u32,
|
pub device_feature_select: u32,
|
||||||
pub driver_feature_select: u32,
|
pub driver_feature_select: u32,
|
||||||
@@ -141,7 +141,7 @@ impl VirtioPciCommonConfig {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
VirtioPciCommonConfig {
|
VirtioPciCommonConfig {
|
||||||
access_platform,
|
access_platform,
|
||||||
driver_status: state.driver_status,
|
driver_status: Arc::new(AtomicU8::new(state.driver_status)),
|
||||||
config_generation: state.config_generation,
|
config_generation: state.config_generation,
|
||||||
device_feature_select: state.device_feature_select,
|
device_feature_select: state.device_feature_select,
|
||||||
driver_feature_select: state.driver_feature_select,
|
driver_feature_select: state.driver_feature_select,
|
||||||
@@ -153,7 +153,7 @@ impl VirtioPciCommonConfig {
|
|||||||
|
|
||||||
fn state(&self) -> VirtioPciCommonConfigState {
|
fn state(&self) -> VirtioPciCommonConfigState {
|
||||||
VirtioPciCommonConfigState {
|
VirtioPciCommonConfigState {
|
||||||
driver_status: self.driver_status,
|
driver_status: self.driver_status.load(Ordering::Acquire),
|
||||||
config_generation: self.config_generation,
|
config_generation: self.config_generation,
|
||||||
device_feature_select: self.device_feature_select,
|
device_feature_select: self.device_feature_select,
|
||||||
driver_feature_select: self.driver_feature_select,
|
driver_feature_select: self.driver_feature_select,
|
||||||
@@ -223,7 +223,7 @@ impl VirtioPciCommonConfig {
|
|||||||
debug!("read_common_config_byte: offset 0x{offset:x}");
|
debug!("read_common_config_byte: offset 0x{offset:x}");
|
||||||
// The driver is only allowed to do aligned, properly sized access.
|
// The driver is only allowed to do aligned, properly sized access.
|
||||||
match offset {
|
match offset {
|
||||||
0x14 => self.driver_status,
|
0x14 => self.driver_status.load(Ordering::Acquire),
|
||||||
0x15 => self.config_generation,
|
0x15 => self.config_generation,
|
||||||
_ => {
|
_ => {
|
||||||
warn!("invalid virtio config byte read: 0x{offset:x}");
|
warn!("invalid virtio config byte read: 0x{offset:x}");
|
||||||
@@ -235,7 +235,7 @@ impl VirtioPciCommonConfig {
|
|||||||
fn write_common_config_byte(&mut self, offset: u64, value: u8) {
|
fn write_common_config_byte(&mut self, offset: u64, value: u8) {
|
||||||
debug!("write_common_config_byte: offset 0x{offset:x}");
|
debug!("write_common_config_byte: offset 0x{offset:x}");
|
||||||
match offset {
|
match offset {
|
||||||
0x14 => self.driver_status = value,
|
0x14 => self.driver_status.store(value, Ordering::Release),
|
||||||
_ => {
|
_ => {
|
||||||
warn!("invalid virtio config byte write: 0x{offset:x}");
|
warn!("invalid virtio config byte write: 0x{offset:x}");
|
||||||
}
|
}
|
||||||
@@ -404,11 +404,8 @@ impl Snapshottable for VirtioPciCommonConfig {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_tests {
|
mod unit_tests {
|
||||||
use vm_memory::GuestMemoryAtomic;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{ActivateResult, GuestMemoryMmap, VirtioInterrupt};
|
use crate::{ActivateResult, ActivationContext};
|
||||||
|
|
||||||
struct DummyDevice(u32);
|
struct DummyDevice(u32);
|
||||||
const QUEUE_SIZE: u16 = 256;
|
const QUEUE_SIZE: u16 = 256;
|
||||||
@@ -421,12 +418,7 @@ mod unit_tests {
|
|||||||
fn queue_max_sizes(&self) -> &[u16] {
|
fn queue_max_sizes(&self) -> &[u16] {
|
||||||
QUEUE_SIZES
|
QUEUE_SIZES
|
||||||
}
|
}
|
||||||
fn activate(
|
fn activate(&mut self, _context: ActivationContext) -> ActivateResult {
|
||||||
&mut self,
|
|
||||||
_mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
|
||||||
_interrupt_evt: Arc<dyn VirtioInterrupt>,
|
|
||||||
_queues: Vec<(usize, Queue, EventFd)>,
|
|
||||||
) -> ActivateResult {
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,7 +437,7 @@ mod unit_tests {
|
|||||||
fn write_base_regs() {
|
fn write_base_regs() {
|
||||||
let mut regs = VirtioPciCommonConfig {
|
let mut regs = VirtioPciCommonConfig {
|
||||||
access_platform: None,
|
access_platform: None,
|
||||||
driver_status: 0xaa,
|
driver_status: Arc::new(AtomicU8::new(0xaa)),
|
||||||
config_generation: 0x55,
|
config_generation: 0x55,
|
||||||
device_feature_select: 0x0,
|
device_feature_select: 0x0,
|
||||||
driver_feature_select: 0x0,
|
driver_feature_select: 0x0,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use std::any::Any;
|
|||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU16, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU16, AtomicUsize, Ordering};
|
||||||
use std::sync::{Arc, Barrier, Mutex};
|
use std::sync::{Arc, Barrier, Mutex};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
@@ -288,15 +288,18 @@ pub struct VirtioPciDeviceActivator {
|
|||||||
queues: Option<Vec<(usize, Queue, EventFd)>>,
|
queues: Option<Vec<(usize, Queue, EventFd)>>,
|
||||||
barrier: Option<Arc<Barrier>>,
|
barrier: Option<Arc<Barrier>>,
|
||||||
id: String,
|
id: String,
|
||||||
|
status: Arc<AtomicU8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtioPciDeviceActivator {
|
impl VirtioPciDeviceActivator {
|
||||||
pub fn activate(&mut self) -> ActivateResult {
|
pub fn activate(mut self) -> ActivateResult {
|
||||||
self.device.lock().unwrap().activate(
|
let mut locked_device = self.device.lock().unwrap();
|
||||||
self.memory.take().unwrap(),
|
locked_device.activate(crate::device::ActivationContext {
|
||||||
self.interrupt.take().unwrap(),
|
mem: self.memory.take().unwrap(),
|
||||||
self.queues.take().unwrap(),
|
interrupt_cb: self.interrupt.take().unwrap(),
|
||||||
)?;
|
queues: self.queues.take().unwrap(),
|
||||||
|
device_status: self.status,
|
||||||
|
})?;
|
||||||
self.device_activated.store(true, Ordering::SeqCst);
|
self.device_activated.store(true, Ordering::SeqCst);
|
||||||
|
|
||||||
if let Some(barrier) = self.barrier.take() {
|
if let Some(barrier) = self.barrier.take() {
|
||||||
@@ -641,13 +644,13 @@ impl VirtioPciDevice {
|
|||||||
fn is_driver_ready(&self) -> bool {
|
fn is_driver_ready(&self) -> bool {
|
||||||
let ready_bits =
|
let ready_bits =
|
||||||
(DEVICE_ACKNOWLEDGE | DEVICE_DRIVER | DEVICE_DRIVER_OK | DEVICE_FEATURES_OK) as u8;
|
(DEVICE_ACKNOWLEDGE | DEVICE_DRIVER | DEVICE_DRIVER_OK | DEVICE_FEATURES_OK) as u8;
|
||||||
self.common_config.driver_status == ready_bits
|
let driver_status = self.common_config.driver_status.load(Ordering::SeqCst);
|
||||||
&& self.common_config.driver_status & DEVICE_FAILED as u8 == 0
|
driver_status == ready_bits && (driver_status & DEVICE_FAILED as u8) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines if the driver has requested the device (re)init / reset itself
|
/// Determines if the driver has requested the device (re)init / reset itself
|
||||||
fn is_driver_init(&self) -> bool {
|
fn is_driver_init(&self) -> bool {
|
||||||
self.common_config.driver_status == DEVICE_INIT as u8
|
self.common_config.driver_status.load(Ordering::SeqCst) == DEVICE_INIT as u8
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config_bar_addr(&self) -> u64 {
|
pub fn config_bar_addr(&self) -> u64 {
|
||||||
@@ -801,6 +804,7 @@ impl VirtioPciDevice {
|
|||||||
device_activated: self.device_activated.clone(),
|
device_activated: self.device_activated.clone(),
|
||||||
barrier,
|
barrier,
|
||||||
id: self.id.clone(),
|
id: self.id.clone(),
|
||||||
|
status: self.common_config.driver_status.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1219,7 +1223,9 @@ impl PciDevice for VirtioPciDevice {
|
|||||||
self.common_config.queue_select = 0;
|
self.common_config.queue_select = 0;
|
||||||
} else {
|
} else {
|
||||||
error!("Attempt to reset device when not implemented in underlying device");
|
error!("Attempt to reset device when not implemented in underlying device");
|
||||||
self.common_config.driver_status = crate::DEVICE_FAILED as u8;
|
self.common_config
|
||||||
|
.driver_status
|
||||||
|
.store(crate::DEVICE_FAILED as u8, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -428,12 +428,13 @@ impl VirtioDevice for Vdpa {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult {
|
||||||
&mut self,
|
let crate::device::ActivationContext {
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
mem,
|
||||||
virtio_interrupt: Arc<dyn VirtioInterrupt>,
|
interrupt_cb: virtio_interrupt,
|
||||||
queues: Vec<(usize, Queue, EventFd)>,
|
queues,
|
||||||
) -> ActivateResult {
|
..
|
||||||
|
} = context;
|
||||||
self.activate_vdpa(&mem.memory(), virtio_interrupt.as_ref(), &queues)
|
self.activate_vdpa(&mem.memory(), virtio_interrupt.as_ref(), &queues)
|
||||||
.map_err(ActivateError::ActivateVdpa)?;
|
.map_err(ActivateError::ActivateVdpa)?;
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ use virtio_bindings::virtio_blk::{
|
|||||||
VIRTIO_BLK_F_GEOMETRY, VIRTIO_BLK_F_MQ, VIRTIO_BLK_F_RO, VIRTIO_BLK_F_SEG_MAX,
|
VIRTIO_BLK_F_GEOMETRY, VIRTIO_BLK_F_MQ, VIRTIO_BLK_F_RO, VIRTIO_BLK_F_SEG_MAX,
|
||||||
VIRTIO_BLK_F_SIZE_MAX, VIRTIO_BLK_F_TOPOLOGY, VIRTIO_BLK_F_WRITE_ZEROES,
|
VIRTIO_BLK_F_SIZE_MAX, VIRTIO_BLK_F_TOPOLOGY, VIRTIO_BLK_F_WRITE_ZEROES,
|
||||||
};
|
};
|
||||||
use virtio_queue::Queue;
|
|
||||||
use vm_memory::{ByteValued, GuestMemoryAtomic};
|
use vm_memory::{ByteValued, GuestMemoryAtomic};
|
||||||
use vm_migration::protocol::MemoryRangeTable;
|
use vm_migration::protocol::MemoryRangeTable;
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
@@ -279,12 +278,13 @@ impl VirtioDevice for Blk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult {
|
||||||
&mut self,
|
let crate::device::ActivationContext {
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
mem,
|
||||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
interrupt_cb,
|
||||||
queues: Vec<(usize, Queue, EventFd)>,
|
queues,
|
||||||
) -> ActivateResult {
|
..
|
||||||
|
} = context;
|
||||||
self.common.activate(&queues, interrupt_cb.clone())?;
|
self.common.activate(&queues, interrupt_cb.clone())?;
|
||||||
self.guest_memory = Some(mem.clone());
|
self.guest_memory = Some(mem.clone());
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
use serde_with::{Bytes, serde_as};
|
use serde_with::{Bytes, serde_as};
|
||||||
use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
|
use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
|
||||||
use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler};
|
use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler};
|
||||||
use virtio_queue::Queue;
|
|
||||||
use vm_device::UserspaceMapping;
|
use vm_device::UserspaceMapping;
|
||||||
use vm_memory::{ByteValued, GuestMemoryAtomic};
|
use vm_memory::{ByteValued, GuestMemoryAtomic};
|
||||||
use vm_migration::protocol::MemoryRangeTable;
|
use vm_migration::protocol::MemoryRangeTable;
|
||||||
@@ -261,12 +260,13 @@ impl VirtioDevice for Fs {
|
|||||||
self.read_config_from_slice(self.config.as_slice(), offset, data);
|
self.read_config_from_slice(self.config.as_slice(), offset, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult {
|
||||||
&mut self,
|
let crate::device::ActivationContext {
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
mem,
|
||||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
interrupt_cb,
|
||||||
queues: Vec<(usize, Queue, EventFd)>,
|
queues,
|
||||||
) -> ActivateResult {
|
..
|
||||||
|
} = context;
|
||||||
self.common.activate(&queues, interrupt_cb.clone())?;
|
self.common.activate(&queues, interrupt_cb.clone())?;
|
||||||
self.guest_memory = Some(mem.clone());
|
self.guest_memory = Some(mem.clone());
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use virtio_bindings::virtio_net::{
|
|||||||
VIRTIO_NET_F_MAC, VIRTIO_NET_F_MRG_RXBUF, VIRTIO_NET_F_MTU,
|
VIRTIO_NET_F_MAC, VIRTIO_NET_F_MRG_RXBUF, VIRTIO_NET_F_MTU,
|
||||||
};
|
};
|
||||||
use virtio_bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
use virtio_bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
||||||
use virtio_queue::{Queue, QueueT};
|
use virtio_queue::QueueT;
|
||||||
use vm_memory::{ByteValued, GuestMemoryAtomic};
|
use vm_memory::{ByteValued, GuestMemoryAtomic};
|
||||||
use vm_migration::protocol::MemoryRangeTable;
|
use vm_migration::protocol::MemoryRangeTable;
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
@@ -288,12 +288,13 @@ impl VirtioDevice for Net {
|
|||||||
self.read_config_from_slice(self.config.as_slice(), offset, data);
|
self.read_config_from_slice(self.config.as_slice(), offset, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult {
|
||||||
&mut self,
|
let crate::device::ActivationContext {
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
mem,
|
||||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
interrupt_cb,
|
||||||
mut queues: Vec<(usize, Queue, EventFd)>,
|
mut queues,
|
||||||
) -> ActivateResult {
|
..
|
||||||
|
} = context;
|
||||||
self.common.activate(&queues, interrupt_cb.clone())?;
|
self.common.activate(&queues, interrupt_cb.clone())?;
|
||||||
self.guest_memory = Some(mem.clone());
|
self.guest_memory = Some(mem.clone());
|
||||||
|
|
||||||
|
|||||||
@@ -435,12 +435,13 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult {
|
||||||
&mut self,
|
let crate::device::ActivationContext {
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
mem,
|
||||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
interrupt_cb,
|
||||||
queues: Vec<(usize, Queue, EventFd)>,
|
queues,
|
||||||
) -> ActivateResult {
|
..
|
||||||
|
} = context;
|
||||||
self.common.activate(&queues, interrupt_cb.clone())?;
|
self.common.activate(&queues, interrupt_cb.clone())?;
|
||||||
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
||||||
|
|
||||||
@@ -593,9 +594,12 @@ mod unit_tests {
|
|||||||
let memory = GuestMemoryAtomic::new(ctx.mem.clone());
|
let memory = GuestMemoryAtomic::new(ctx.mem.clone());
|
||||||
|
|
||||||
// Test a bad activation.
|
// Test a bad activation.
|
||||||
let bad_activate =
|
let bad_activate = ctx.device.activate(crate::device::ActivationContext {
|
||||||
ctx.device
|
mem: memory.clone(),
|
||||||
.activate(memory.clone(), Arc::new(NoopVirtioInterrupt {}), Vec::new());
|
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
|
||||||
|
queues: Vec::new(),
|
||||||
|
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
|
||||||
|
});
|
||||||
match bad_activate {
|
match bad_activate {
|
||||||
Err(ActivateError::BadActivate) => (),
|
Err(ActivateError::BadActivate) => (),
|
||||||
other => panic!("{other:?}"),
|
other => panic!("{other:?}"),
|
||||||
@@ -603,10 +607,10 @@ mod unit_tests {
|
|||||||
|
|
||||||
// Test a correct activation.
|
// Test a correct activation.
|
||||||
ctx.device
|
ctx.device
|
||||||
.activate(
|
.activate(crate::device::ActivationContext {
|
||||||
memory,
|
mem: memory,
|
||||||
Arc::new(NoopVirtioInterrupt {}),
|
interrupt_cb: Arc::new(NoopVirtioInterrupt {}),
|
||||||
vec![
|
queues: vec![
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
Queue::new(256).unwrap(),
|
Queue::new(256).unwrap(),
|
||||||
@@ -623,7 +627,8 @@ mod unit_tests {
|
|||||||
EventFd::new(EFD_NONBLOCK).unwrap(),
|
EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
device_status: Arc::new(std::sync::atomic::AtomicU8::new(0)),
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -407,9 +407,11 @@ impl VsockMuxer {
|
|||||||
Some(EpollListener::HostSock) => {
|
Some(EpollListener::HostSock) => {
|
||||||
if self.conn_map.len() == defs::MAX_CONNECTIONS {
|
if self.conn_map.len() == defs::MAX_CONNECTIONS {
|
||||||
// If we're already maxed-out on connections, we'll just accept and
|
// If we're already maxed-out on connections, we'll just accept and
|
||||||
// immediately discard this potentially new one.
|
// immediately discard this potentially new one. Dropping the returned
|
||||||
|
// `UnixStream` closes the new connection; we don't care if `accept()`
|
||||||
|
// itself failed.
|
||||||
warn!("vsock: connection limit reached; refusing new host connection");
|
warn!("vsock: connection limit reached; refusing new host connection");
|
||||||
self.host_sock.accept().map(|_| 0).unwrap_or(0);
|
let _ = self.host_sock.accept();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.host_sock
|
self.host_sock
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ impl MuxerKillQ {
|
|||||||
/// This will succeed and return a connection key, only if the connection at the front of
|
/// This will succeed and return a connection key, only if the connection at the front of
|
||||||
/// the queue has expired. Otherwise, `None` is returned.
|
/// the queue has expired. Otherwise, `None` is returned.
|
||||||
///
|
///
|
||||||
|
// `VecDeque::pop_front_if` is unstable on the project MSRV; allow the
|
||||||
|
// beta clippy lint that asks for it. `unknown_lints` is needed because
|
||||||
|
// the lint does not exist on stable clippy.
|
||||||
|
#[allow(unknown_lints, clippy::manual_pop_if)]
|
||||||
pub fn pop(&mut self) -> Option<ConnMapKey> {
|
pub fn pop(&mut self) -> Option<ConnMapKey> {
|
||||||
if let Some(item) = self.q.front()
|
if let Some(item) = self.q.front()
|
||||||
&& Instant::now() > item.kill_time
|
&& Instant::now() > item.kill_time
|
||||||
|
|||||||
@@ -326,12 +326,13 @@ impl VirtioDevice for Watchdog {
|
|||||||
self.common.ack_features(value);
|
self.common.ack_features(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult {
|
||||||
&mut self,
|
let crate::device::ActivationContext {
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
mem,
|
||||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
interrupt_cb,
|
||||||
mut queues: Vec<(usize, Queue, EventFd)>,
|
mut queues,
|
||||||
) -> ActivateResult {
|
..
|
||||||
|
} = context;
|
||||||
self.common.activate(&queues, interrupt_cb.clone())?;
|
self.common.activate(&queues, interrupt_cb.clone())?;
|
||||||
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
||||||
|
|
||||||
|
|||||||
@@ -948,7 +948,8 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: true
|
default: true
|
||||||
image_type:
|
image_type:
|
||||||
type: enum ["FixedVhd", "Qcow2", "Raw", "Vhdx"]
|
type: string
|
||||||
|
enum: [FixedVhd, Qcow2, Raw, Vhdx, Unknown]
|
||||||
|
|
||||||
|
|
||||||
NetConfig:
|
NetConfig:
|
||||||
|
|||||||
@@ -4477,7 +4477,7 @@ impl DeviceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn activate_virtio_devices(&self) -> DeviceManagerResult<()> {
|
pub fn activate_virtio_devices(&self) -> DeviceManagerResult<()> {
|
||||||
for mut activator in self.pending_activations.lock().unwrap().drain(..) {
|
for activator in self.pending_activations.lock().unwrap().drain(..) {
|
||||||
activator
|
activator
|
||||||
.activate()
|
.activate()
|
||||||
.map_err(DeviceManagerError::VirtioActivate)?;
|
.map_err(DeviceManagerError::VirtioActivate)?;
|
||||||
|
|||||||
@@ -484,7 +484,7 @@ impl run_blocking::BlockingEventLoop for GdbEventLoop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if conn.peek().map(|b| b.is_some()).unwrap_or(true) {
|
if conn.peek().map_or(true, |b| b.is_some()) {
|
||||||
let byte = conn
|
let byte = conn
|
||||||
.read()
|
.read()
|
||||||
.map_err(run_blocking::WaitForStopReasonError::Connection)?;
|
.map_err(run_blocking::WaitForStopReasonError::Connection)?;
|
||||||
|
|||||||
@@ -433,7 +433,7 @@ pub fn load_igvm(
|
|||||||
let mut now = Instant::now();
|
let mut now = Instant::now();
|
||||||
|
|
||||||
// Sort the gpas to group them by the page type
|
// Sort the gpas to group them by the page type
|
||||||
gpas.sort_by(|a, b| a.gpa.cmp(&b.gpa));
|
gpas.sort_by_key(|a| a.gpa);
|
||||||
|
|
||||||
let gpas_grouped = gpas
|
let gpas_grouped = gpas
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -735,22 +735,20 @@ impl Vmm {
|
|||||||
|
|
||||||
for signal in signals.forever() {
|
for signal in signals.forever() {
|
||||||
match signal {
|
match signal {
|
||||||
SIGTERM | SIGINT => {
|
SIGTERM | SIGINT if exit_evt.write(1).is_err() => {
|
||||||
if exit_evt.write(1).is_err() {
|
// Resetting the terminal is usually done as the VMM exits
|
||||||
// Resetting the terminal is usually done as the VMM exits
|
if let Ok(lock) = original_termios_opt.lock() {
|
||||||
if let Ok(lock) = original_termios_opt.lock() {
|
if let Some(termios) = *lock {
|
||||||
if let Some(termios) = *lock {
|
// SAFETY: FFI call
|
||||||
// SAFETY: FFI call
|
let _ = unsafe {
|
||||||
let _ = unsafe {
|
tcsetattr(stdout().lock().as_raw_fd(), TCSANOW, &termios)
|
||||||
tcsetattr(stdout().lock().as_raw_fd(), TCSANOW, &termios)
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn!("Failed to lock original termios");
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
std::process::exit(1);
|
warn!("Failed to lock original termios");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,33 +132,29 @@ impl SerialManager {
|
|||||||
|
|
||||||
let in_fd = match output {
|
let in_fd = match output {
|
||||||
ConsoleOutput::Pty(ref fd) => fd.as_raw_fd(),
|
ConsoleOutput::Pty(ref fd) => fd.as_raw_fd(),
|
||||||
ConsoleOutput::Tty(_) => {
|
// If running on an interactive TTY then accept input.
|
||||||
// If running on an interactive TTY then accept input
|
// SAFETY: trivially safe
|
||||||
// SAFETY: trivially safe
|
ConsoleOutput::Tty(_) if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } => {
|
||||||
if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } {
|
// SAFETY: STDIN_FILENO is a valid fd
|
||||||
// SAFETY: STDIN_FILENO is a valid fd
|
let fd = unsafe { libc::dup(libc::STDIN_FILENO) };
|
||||||
let fd = unsafe { libc::dup(libc::STDIN_FILENO) };
|
if fd == -1 {
|
||||||
if fd == -1 {
|
return Err(Error::DupFd(std::io::Error::last_os_error()));
|
||||||
return Err(Error::DupFd(std::io::Error::last_os_error()));
|
|
||||||
}
|
|
||||||
// SAFETY: fd is valid and owned by us
|
|
||||||
let stdin_clone = unsafe { File::from_raw_fd(fd) };
|
|
||||||
// SAFETY: FFI calls with correct arguments
|
|
||||||
let ret = unsafe {
|
|
||||||
let mut flags = libc::fcntl(stdin_clone.as_raw_fd(), libc::F_GETFL);
|
|
||||||
flags |= libc::O_NONBLOCK;
|
|
||||||
libc::fcntl(stdin_clone.as_raw_fd(), libc::F_SETFL, flags)
|
|
||||||
};
|
|
||||||
|
|
||||||
if ret < 0 {
|
|
||||||
return Err(Error::SetNonBlocking(std::io::Error::last_os_error()));
|
|
||||||
}
|
|
||||||
|
|
||||||
output = ConsoleOutput::Tty(Arc::new(stdin_clone));
|
|
||||||
fd
|
|
||||||
} else {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
}
|
||||||
|
// SAFETY: fd is valid and owned by us
|
||||||
|
let stdin_clone = unsafe { File::from_raw_fd(fd) };
|
||||||
|
// SAFETY: FFI calls with correct arguments
|
||||||
|
let ret = unsafe {
|
||||||
|
let mut flags = libc::fcntl(stdin_clone.as_raw_fd(), libc::F_GETFL);
|
||||||
|
flags |= libc::O_NONBLOCK;
|
||||||
|
libc::fcntl(stdin_clone.as_raw_fd(), libc::F_SETFL, flags)
|
||||||
|
};
|
||||||
|
|
||||||
|
if ret < 0 {
|
||||||
|
return Err(Error::SetNonBlocking(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
output = ConsoleOutput::Tty(Arc::new(stdin_clone));
|
||||||
|
fd
|
||||||
}
|
}
|
||||||
ConsoleOutput::Socket(ref fd) => {
|
ConsoleOutput::Socket(ref fd) => {
|
||||||
if let Some(path_in_socket) = socket {
|
if let Some(path_in_socket) = socket {
|
||||||
|
|||||||
Reference in New Issue
Block a user