Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

355 changes: 323 additions & 32 deletions arch/src/x86_64/mod.rs

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion block/src/formats/raw/engine_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ impl AsyncIo for RawSync {
}

fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
// A guest flush needs the data and the metadata to read it back, not the timestamps.
// SAFETY: FFI call
let result = unsafe { libc::fsync(self.raw_file.as_raw_fd() as libc::c_int) };
let result = unsafe { libc::fdatasync(self.raw_file.as_raw_fd() as libc::c_int) };
if result < 0 {
return Err(AsyncIoError::Fsync(io::Error::last_os_error()));
}
Expand Down
5 changes: 3 additions & 2 deletions block/src/io/async_io/aio_data_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,16 @@ impl AioDataIo {
Ok(())
}

/// Submits an fsync operation carrying `user_data`.
/// Submits an fdatasync operation carrying `user_data`: a guest flush needs the data and the
/// metadata to read it back, not the timestamps.
pub fn submit_fsync(&mut self, fd: RawFd, user_data: u64) -> io::Result<()> {
if self.in_flight.contains_key(&user_data) {
return Err(duplicate_user_data_error(user_data));
}

let mut iocb = aio::IoControlBlock {
aio_fildes: fd.as_raw_fd() as u32,
aio_lio_opcode: aio::IOCB_CMD_FSYNC as u16,
aio_lio_opcode: aio::IOCB_CMD_FDSYNC as u16,
aio_data: user_data,
aio_flags: aio::IOCB_FLAG_RESFD,
aio_resfd: self.completions.notifier().as_raw_fd() as u32,
Expand Down
5 changes: 4 additions & 1 deletion block/src/io/async_io/uring_data_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,14 @@ impl UringDataIo {
self.submit_kernel_entry(user_data, &opcode::Nop::new().build().user_data(user_data))
}

/// Submits an fsync operation carrying `user_data`.
/// Submits an fdatasync operation carrying `user_data`: a guest flush needs the data and the
/// metadata to read it back, not the timestamps, and a full fsync of a file whose mtime every
/// write touches forces a host journal commit per flush.
pub fn submit_fsync(&mut self, fd: RawFd, user_data: u64) -> io::Result<()> {
self.submit_kernel_entry(
user_data,
&opcode::Fsync::new(types::Fd(fd))
.flags(types::FsyncFlags::DATASYNC)
.build()
.user_data(user_data),
)
Expand Down
23 changes: 21 additions & 2 deletions cloud-hypervisor/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1154,14 +1154,16 @@ mod tests {
pmem: None,
serial: SerialConfig {
common: CommonConsoleConfig {
file: None,
output_file: None,
input_file: None,
mode: ConsoleOutputMode::Null,
socket: None,
},
},
console: ConsoleConfig {
common: CommonConsoleConfig {
file: None,
output_file: None,
input_file: None,
mode: ConsoleOutputMode::Tty,
socket: None,
},
Expand Down Expand Up @@ -1860,6 +1862,23 @@ mod tests {
}"#,
true,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--serial",
"pty",
"--console",
"file=/path/to/console",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"serial": {"mode": "Pty"},
"console": {"mode": "File", "output_file": "/path/to/console"}
}"#,
true,
),
(
vec![
"cloud-hypervisor",
Expand Down
6 changes: 4 additions & 2 deletions fuzz/fuzz_targets/http_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +180,16 @@ impl RequestHandler for StubApiRequestHandler {
pmem: None,
serial: SerialConfig {
common: CommonConsoleConfig {
file: None,
output_file: None,
input_file: None,
mode: ConsoleOutputMode::Tty,
socket: None,
},
},
console: ConsoleConfig {
common: CommonConsoleConfig {
file: None,
output_file: None,
input_file: None,
mode: ConsoleOutputMode::Tty,
socket: None,
},
Expand Down
2 changes: 1 addition & 1 deletion vmm/src/acpi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ fn create_srat_table(

for cpu in &node.cpus {
#[cfg(target_arch = "x86_64")]
let x2apic_id = arch::x86_64::get_x2apic_id(*cpu, topology);
let x2apic_id = arch::x86_64::vcpu_x2apic_id(*cpu, topology, numa_nodes);
#[cfg(target_arch = "aarch64")]
let x2apic_id = *cpu;

Expand Down
105 changes: 86 additions & 19 deletions vmm/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ pub enum ValidationError {
/// Missing file value for console
#[error("Path missing when using file console mode")]
ConsoleFileMissing,
#[error("Console input is not supported by this endpoint")]
ConsoleInputUnsupported,
/// Missing socket path for console
#[error("Path missing when using socket console mode")]
ConsoleSocketPathMissing,
Expand Down Expand Up @@ -2449,7 +2451,7 @@ impl PmemConfig {

impl CommonConsoleConfig {
const VALUELESS_OPTIONS: &[&str] = &["off", "pty", "tty", "null"];
const VALUE_OPTIONS: &[&str] = &["file", "socket"];
const VALUE_OPTIONS: &[&str] = &["file", "socket", "input_file"];

fn parse(console: &str, map_err: impl Fn(OptionParserError) -> Error) -> Result<Self> {
let mut parser = OptionParser::new();
Expand All @@ -2458,7 +2460,8 @@ impl CommonConsoleConfig {
.add_all(Self::VALUE_OPTIONS);
parser.parse_subset(console).map_err(map_err)?;

let mut file: Option<PathBuf> = None;
let mut output_file: Option<PathBuf> = None;
let mut input_file: Option<PathBuf> = None;
let mut socket: Option<PathBuf> = None;
let mut mode: ConsoleOutputMode = ConsoleOutputMode::Off;

Expand All @@ -2471,10 +2474,11 @@ impl CommonConsoleConfig {
mode = ConsoleOutputMode::Null;
} else if parser.is_set("file") {
mode = ConsoleOutputMode::File;
file =
output_file =
Some(PathBuf::from(parser.get("file").ok_or(
Error::Validation(ValidationError::ConsoleFileMissing),
)?));
input_file = parser.get("input_file").map(PathBuf::from);
} else if parser.is_set("socket") {
mode = ConsoleOutputMode::Socket;
socket = Some(PathBuf::from(parser.get("socket").ok_or(
Expand All @@ -2484,7 +2488,12 @@ impl CommonConsoleConfig {
return Err(Error::ParseConsoleInvalidModeGiven);
}

Ok(Self { mode, file, socket })
Ok(Self {
mode,
output_file,
input_file,
socket,
})
}

pub fn validate(&self) -> ValidationResult<()> {
Expand Down Expand Up @@ -3531,13 +3540,24 @@ impl VmConfig {
warn!("Using TTY output for multiple consoles: {tty_consoles:?}");
}

if self.console.common.mode == ConsoleOutputMode::File && self.console.common.file.is_none()
if self.console.common.mode == ConsoleOutputMode::File
&& self.console.common.output_file.is_none()
{
return Err(ValidationError::ConsoleFileMissing);
} else if self.console.common.mode != ConsoleOutputMode::File
&& self.console.common.input_file.is_some()
{
return Err(ValidationError::ConsoleInputUnsupported);
}

if self.serial.common.mode == ConsoleOutputMode::File && self.serial.common.file.is_none() {
if self.serial.common.mode == ConsoleOutputMode::File
&& self.serial.common.output_file.is_none()
{
return Err(ValidationError::ConsoleFileMissing);
} else if self.serial.common.mode != ConsoleOutputMode::File
&& self.serial.common.input_file.is_some()
{
return Err(ValidationError::ConsoleInputUnsupported);
}

if self.cpus.max_vcpus < self.cpus.boot_vcpus {
Expand Down Expand Up @@ -3694,11 +3714,16 @@ impl VmConfig {
return Err(ValidationError::CpuTopologyDiesPerPackage);
}

// The topology must describe at least as many slots as there are
// vCPUs. It may describe more: a vNUMA layout sizes cores_per_die to
// the largest node and gives every node a package window of that
// size, so unbalanced nodes leave the tail of their window empty
// (their APIC IDs are placed accordingly -- see vcpu_x2apic_id).
let total: u32 = (t.threads_per_core as u32)
* (t.cores_per_die as u32)
* (t.dies_per_package as u32)
* (t.packages as u32);
if total != self.cpus.max_vcpus {
if total < self.cpus.max_vcpus {
return Err(ValidationError::CpuTopologyCount);
}
}
Expand Down Expand Up @@ -5146,8 +5171,13 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"

#[test]
fn test_console_parsing() -> Result<()> {
let console_config = |mode, file, socket, iommu| ConsoleConfig {
common: CommonConsoleConfig { file, mode, socket },
let console_config = |mode, output_file, input_file, socket, iommu| ConsoleConfig {
common: CommonConsoleConfig {
output_file,
input_file,
mode,
socket,
},
pci_common: PciDeviceCommonConfig {
iommu,
..Default::default()
Expand All @@ -5158,39 +5188,51 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
ConsoleConfig::parse("badmode").unwrap_err();
assert_eq!(
ConsoleConfig::parse("off")?,
console_config(ConsoleOutputMode::Off, None, None, IommuType::Off)
console_config(ConsoleOutputMode::Off, None, None, None, IommuType::Off)
);
assert_eq!(
ConsoleConfig::parse("pty")?,
console_config(ConsoleOutputMode::Pty, None, None, IommuType::Off)
console_config(ConsoleOutputMode::Pty, None, None, None, IommuType::Off)
);
assert_eq!(
ConsoleConfig::parse("tty")?,
console_config(ConsoleOutputMode::Tty, None, None, IommuType::Off)
console_config(ConsoleOutputMode::Tty, None, None, None, IommuType::Off)
);
assert_eq!(
ConsoleConfig::parse("null")?,
console_config(ConsoleOutputMode::Null, None, None, IommuType::Off)
console_config(ConsoleOutputMode::Null, None, None, None, IommuType::Off)
);
assert_eq!(
ConsoleConfig::parse("file=/tmp/console")?,
console_config(
ConsoleOutputMode::File,
Some(PathBuf::from("/tmp/console")),
None,
None,
IommuType::Off
)
);
assert_eq!(
ConsoleConfig::parse("file=/tmp/console_out,input_file=/tmp/console_in")?,
console_config(
ConsoleOutputMode::File,
Some(PathBuf::from("/tmp/console_out")),
Some(PathBuf::from("/tmp/console_in")),
None,
IommuType::Off
)
);
assert_eq!(
ConsoleConfig::parse("null,iommu=on")?,
console_config(ConsoleOutputMode::Null, None, None, IommuType::Virtio)
console_config(ConsoleOutputMode::Null, None, None, None, IommuType::Virtio)
);
assert_eq!(
ConsoleConfig::parse("file=/tmp/console,iommu=on")?,
console_config(
ConsoleOutputMode::File,
Some(PathBuf::from("/tmp/console")),
None,
None,
IommuType::Virtio
)
);
Expand All @@ -5199,6 +5241,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
console_config(
ConsoleOutputMode::Socket,
None,
None,
Some(PathBuf::from("/tmp/serial.sock")),
IommuType::Virtio
)
Expand Down Expand Up @@ -6140,14 +6183,16 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
pmem: None,
serial: SerialConfig {
common: CommonConsoleConfig {
file: None,
output_file: None,
input_file: None,
mode: ConsoleOutputMode::Null,
socket: None,
},
},
console: ConsoleConfig {
common: CommonConsoleConfig {
file: None,
output_file: None,
input_file: None,
mode: ConsoleOutputMode::Tty,
socket: None,
},
Expand Down Expand Up @@ -6266,12 +6311,19 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"

let mut invalid_config = valid_config.clone();
invalid_config.serial.common.mode = ConsoleOutputMode::File;
invalid_config.serial.common.file = None;
invalid_config.serial.common.output_file = None;
assert_eq!(
invalid_config.validate(),
Err(ValidationError::ConsoleFileMissing)
);

invalid_config.serial.common.mode = ConsoleOutputMode::Pty;
invalid_config.serial.common.input_file = Some(PathBuf::from("/dev/null"));
assert_eq!(
invalid_config.validate(),
Err(ValidationError::ConsoleInputUnsupported)
);

let mut invalid_config = valid_config.clone();
invalid_config.serial.common.mode = ConsoleOutputMode::Socket;
invalid_config.serial.common.socket = None;
Expand All @@ -6296,12 +6348,15 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
Err(ValidationError::CpusMaxLowerThanBoot(16, 32))
);

// A topology describing fewer slots than max_vcpus is rejected. (More
// slots than vCPUs is allowed: a vNUMA layout over-provisions package
// windows for unbalanced nodes.)
let mut invalid_config = valid_config.clone();
invalid_config.cpus.max_vcpus = 16;
invalid_config.cpus.boot_vcpus = 16;
invalid_config.cpus.topology = Some(CpuTopology {
threads_per_core: 2,
cores_per_die: 8,
threads_per_core: 1,
cores_per_die: 4,
dies_per_package: 1,
packages: 2,
});
Expand All @@ -6310,6 +6365,18 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
Err(ValidationError::CpuTopologyCount)
);

// Over-provisioned topology (more slots than vCPUs) is now valid.
let mut over_provisioned = valid_config.clone();
over_provisioned.cpus.max_vcpus = 16;
over_provisioned.cpus.boot_vcpus = 16;
over_provisioned.cpus.topology = Some(CpuTopology {
threads_per_core: 2,
cores_per_die: 8,
dies_per_package: 1,
packages: 2,
});
over_provisioned.validate().unwrap();

let mut still_valid_config = valid_config.clone();
still_valid_config.cpus.max_vcpus = 8;
still_valid_config.cpus.boot_vcpus = 8;
Expand Down
Loading
Loading