mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
157 Commits
stable/v50
...
v51.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00e106e53e | ||
|
|
a63315df54 | ||
|
|
76e233504b | ||
|
|
b3e8e2abc5 | ||
|
|
6f2357c14e | ||
|
|
e36096db3e | ||
|
|
4d30ba12c8 | ||
|
|
94368c622e | ||
|
|
68f746fbea | ||
|
|
003ef780e0 | ||
|
|
17b0749e1c | ||
|
|
924baa9c0f | ||
|
|
0a5e79afce | ||
|
|
08f338bcf6 | ||
|
|
2a5c4f0e4d | ||
|
|
5dd5659440 | ||
|
|
16d6a16e5c | ||
|
|
333db1acb3 | ||
|
|
ecb971a491 | ||
|
|
20f5cadb6f | ||
|
|
ce93686ad2 | ||
|
|
80ea1fe62d | ||
|
|
ff4c8f7480 | ||
|
|
30c86d5999 | ||
|
|
adfcd17bfc | ||
|
|
8e51c57aea | ||
|
|
4676fdb494 | ||
|
|
45b115aeb0 | ||
|
|
6c94975c80 | ||
|
|
81c075b317 | ||
|
|
e913d0d9cb | ||
|
|
49a30cbbaf | ||
|
|
0a287793df | ||
|
|
46e6ecddfe | ||
|
|
7dfcbff309 | ||
|
|
7f4b56b217 | ||
|
|
d5dad48618 | ||
|
|
7095605d84 | ||
|
|
41b23229a5 | ||
|
|
03ef7d1991 | ||
|
|
9babad0114 | ||
|
|
3af9ac78a9 | ||
|
|
18768e9bf8 | ||
|
|
df67c3690e | ||
|
|
dc0c306dd9 | ||
|
|
fa43548975 | ||
|
|
6d4827b5ff | ||
|
|
231bbe2d5d | ||
|
|
9ba9c0819a | ||
|
|
99493c728e | ||
|
|
629c117ff3 | ||
|
|
caa362c31f | ||
|
|
2882ccd00a | ||
|
|
92b58ba94a | ||
|
|
dde5f6ef38 | ||
|
|
638848fcff | ||
|
|
c2add07476 | ||
|
|
bf6f0f8352 | ||
|
|
97c5d837ab | ||
|
|
509832298b | ||
|
|
a702bf1d10 | ||
|
|
279344800e | ||
|
|
c569a4cbd4 | ||
|
|
2c49f4f4f3 | ||
|
|
7157e97083 | ||
|
|
a00189cf72 | ||
|
|
e3a2bf0870 | ||
|
|
7314a77d43 | ||
|
|
1e0eba60af | ||
|
|
2c2f5d2431 | ||
|
|
258f826027 | ||
|
|
f391a37a35 | ||
|
|
6042eb969e | ||
|
|
12f66b7ddc | ||
|
|
96f663b5f9 | ||
|
|
093a8497d0 | ||
|
|
8c168d928f | ||
|
|
7667e8c0bc | ||
|
|
9bc367a27b | ||
|
|
51662159c0 | ||
|
|
e690d258cc | ||
|
|
591aeb8ed8 | ||
|
|
512c3ea58d | ||
|
|
4ba0db5948 | ||
|
|
4545fe113e | ||
|
|
57fd672db6 | ||
|
|
009af915bb | ||
|
|
01bd6b8b1a | ||
|
|
28686bba46 | ||
|
|
2da05d4258 | ||
|
|
323b8230a3 | ||
|
|
edaeaed5f7 | ||
|
|
9baa904a5c | ||
|
|
2d86fc8422 | ||
|
|
c2fcb9bac9 | ||
|
|
771ab1d5a3 | ||
|
|
111f37f926 | ||
|
|
929df76e1a | ||
|
|
87e8ac3f1f | ||
|
|
8cdc3b53c0 | ||
|
|
79953fece0 | ||
|
|
76822d853a | ||
|
|
919cce9b58 | ||
|
|
82dc9bfaee | ||
|
|
cc96fc14b4 | ||
|
|
a6aecad635 | ||
|
|
6e7f888f5d | ||
|
|
f8008191d2 | ||
|
|
e61901dfdc | ||
|
|
7c99c169ba | ||
|
|
eaafe426a6 | ||
|
|
13198777dd | ||
|
|
d58e421eea | ||
|
|
298da55902 | ||
|
|
c9cd82b52b | ||
|
|
25e8e64a01 | ||
|
|
15d34ffebb | ||
|
|
cda1ec5fbc | ||
|
|
cdf14956e0 | ||
|
|
e4f9610b5b | ||
|
|
d5e35d9e5c | ||
|
|
ccf0b9ec7d | ||
|
|
cde7856d9e | ||
|
|
dee7993538 | ||
|
|
4f69cf604a | ||
|
|
3fed706d6a | ||
|
|
9eb2b9b0e5 | ||
|
|
10394da0c4 | ||
|
|
b3922dbd2c | ||
|
|
cb495959a8 | ||
|
|
de2d8f486b | ||
|
|
5aba9b4308 | ||
|
|
03252f5851 | ||
|
|
34b8aed662 | ||
|
|
2c2dec50f9 | ||
|
|
e7a151e953 | ||
|
|
7b92a36c4c | ||
|
|
34ee973ee0 | ||
|
|
c19ee037a2 | ||
|
|
11edb28069 | ||
|
|
01d711e893 | ||
|
|
c87ca39219 | ||
|
|
079d94ecae | ||
|
|
847de7e86e | ||
|
|
c396f1ad2a | ||
|
|
2e05836669 | ||
|
|
f6f0d49c62 | ||
|
|
3657db7843 | ||
|
|
dccdb223b7 | ||
|
|
de618ec35e | ||
|
|
379e2d4647 | ||
|
|
14911a9518 | ||
|
|
f11682feea | ||
|
|
db205150f9 | ||
|
|
aca7b01c6b | ||
|
|
d741cd53de | ||
|
|
2278a5236a |
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Cloud Hypervisor Build
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/docker-image.yaml
vendored
2
.github/workflows/docker-image.yaml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
pull_request:
|
||||
paths: resources/Dockerfile
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
|
||||
2
.github/workflows/formatting.yaml
vendored
2
.github/workflows/formatting.yaml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Cloud Hypervisor Code Formatting
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/fuzz-build.yaml
vendored
2
.github/workflows/fuzz-build.yaml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Cloud Hypervisor Cargo Fuzz Build
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/integration-arm64.yaml
vendored
2
.github/workflows/integration-arm64.yaml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Cloud Hypervisor Tests (ARM64)
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Cloud Hypervisor Tests (Rate-Limiter)
|
||||
on: [merge_group, pull_request]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/integration-vfio.yaml
vendored
2
.github/workflows/integration-vfio.yaml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Cloud Hypervisor Tests (VFIO)
|
||||
on: [merge_group, pull_request]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/integration-windows.yaml
vendored
2
.github/workflows/integration-windows.yaml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Cloud Hypervisor Tests (Windows Guest)
|
||||
on: [merge_group, pull_request]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/integration-x86-64.yaml
vendored
2
.github/workflows/integration-x86-64.yaml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Cloud Hypervisor Tests (x86-64)
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
5
.github/workflows/mshv-infra.yaml
vendored
5
.github/workflows/mshv-infra.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
name: MSHV Infra Setup
|
||||
on:
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
ARCH:
|
||||
@@ -44,13 +44,12 @@ on:
|
||||
description: 'Private IP of the VM'
|
||||
value: ${{ jobs.infra-setup.outputs.PRIVATE_IP }}
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
infra-setup:
|
||||
name: ${{ inputs.ARCH }} VM Provision
|
||||
runs-on: mshv
|
||||
continue-on-error: true
|
||||
outputs:
|
||||
RG_NAME: ${{ steps.rg-setup.outputs.RG_NAME }}
|
||||
VM_NAME: ${{ steps.vm-setup.outputs.VM_NAME }}
|
||||
|
||||
1
.github/workflows/mshv-integration.yaml
vendored
1
.github/workflows/mshv-integration.yaml
vendored
@@ -23,7 +23,6 @@ jobs:
|
||||
needs: infra-setup
|
||||
if: ${{ always() && needs.infra-setup.result == 'success' }}
|
||||
runs-on: mshv
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Run integration tests
|
||||
timeout-minutes: 60
|
||||
|
||||
2
.github/workflows/package-consistency.yaml
vendored
2
.github/workflows/package-consistency.yaml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Cloud Hypervisor Consistency
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/preview-riscv64-build.yaml
vendored
2
.github/workflows/preview-riscv64-build.yaml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Cloud Hypervisor RISC-V 64-bit kvm build Preview
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Cloud Hypervisor RISC-V 64-bit Preview
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
4
.github/workflows/quality.yaml
vendored
4
.github/workflows/quality.yaml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Cloud Hypervisor Quality Checks
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
@@ -167,4 +167,4 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
# Executes "typos ."
|
||||
- uses: crate-ci/typos@v1.40.0
|
||||
- uses: crate-ci/typos@v1.43.5
|
||||
|
||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Cloud Hypervisor Release
|
||||
on: [create, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
verbose = "info"
|
||||
|
||||
exclude_path = [".lychee.toml"]
|
||||
|
||||
exclude = [
|
||||
# Availability of links below should be manually verified.
|
||||
# Page for intel TDX support, returns 403 while querying.
|
||||
@@ -16,6 +18,13 @@ exclude = [
|
||||
"\\$.*",
|
||||
# Exclude local files
|
||||
"file://.*",
|
||||
|
||||
# ARM documentation returns 403 Forbidden for automated CI checks.
|
||||
'^http://infocenter\.arm\.com',
|
||||
'^https://developer\.arm\.com',
|
||||
|
||||
# Ignore internal/unsupported protocols seen in logs
|
||||
'^tcp://192\.168\.1\.10',
|
||||
]
|
||||
|
||||
# Exclude loopback addresses
|
||||
|
||||
@@ -21,5 +21,8 @@ liness = "liness"
|
||||
outout = "outout"
|
||||
|
||||
[default.extend-identifiers]
|
||||
consts = "consts"
|
||||
fo = "fo"
|
||||
fpr = "fpr"
|
||||
# Public Linux API
|
||||
msg_controllen = "msg_controllen"
|
||||
|
||||
661
Cargo.lock
generated
661
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
29
Cargo.toml
29
Cargo.toml
@@ -45,12 +45,12 @@ resolver = "3"
|
||||
|
||||
[workspace.dependencies]
|
||||
# rust-vmm crates
|
||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||
acpi_tables = "0.2.0"
|
||||
kvm-bindings = "0.12.1"
|
||||
kvm-ioctls = "0.22.1"
|
||||
linux-loader = "0.13.1"
|
||||
mshv-bindings = "0.6.5"
|
||||
mshv-ioctls = "0.6.5"
|
||||
mshv-bindings = "0.6.7"
|
||||
mshv-ioctls = "0.6.7"
|
||||
seccompiler = "0.5.0"
|
||||
vfio-bindings = { version = "0.6.0", default-features = false }
|
||||
vfio-ioctls = { version = "0.5.1", default-features = false }
|
||||
@@ -64,34 +64,33 @@ vm-memory = "0.16.1"
|
||||
vmm-sys-util = "0.14.0"
|
||||
|
||||
# igvm crates
|
||||
# TODO: bump to 0.3.5 release
|
||||
igvm = { git = "https://github.com/microsoft/igvm", branch = "main" }
|
||||
igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" }
|
||||
igvm = "0.4.0"
|
||||
igvm_defs = "0.4.0"
|
||||
|
||||
# serde crates
|
||||
serde = "1.0.228"
|
||||
serde_json = "1.0.145"
|
||||
serde_json = "1.0.149"
|
||||
serde_with = { version = "3.16.1", default-features = false }
|
||||
|
||||
# other crates
|
||||
anyhow = "1.0.99"
|
||||
bitflags = "2.10.0"
|
||||
anyhow = "1.0.101"
|
||||
bitflags = "2.11.0"
|
||||
byteorder = "1.5.0"
|
||||
cfg-if = "1.0.4"
|
||||
clap = "4.5.53"
|
||||
clap = "4.5.59"
|
||||
dhat = "0.3.3"
|
||||
dirs = "6.0.0"
|
||||
env_logger = "0.11.8"
|
||||
epoll = "4.4.0"
|
||||
flume = "0.12.0"
|
||||
itertools = "0.14.0"
|
||||
libc = "0.2.178"
|
||||
libc = "0.2.182"
|
||||
log = "0.4.29"
|
||||
signal-hook = "0.3.18"
|
||||
thiserror = "2.0.17"
|
||||
uuid = { version = "1.19.0" }
|
||||
signal-hook = "0.4.3"
|
||||
thiserror = "2.0.18"
|
||||
uuid = { version = "1.21.0" }
|
||||
wait-timeout = "0.2.1"
|
||||
zerocopy = { version = "0.8.31", default-features = false }
|
||||
zerocopy = { version = "0.8.39", default-features = false }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
# Any clippy lint (group) in alphabetical order:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[package]
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
name = "api_client"
|
||||
version = "0.1.0"
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ use hypervisor::arch::aarch64::regs::{
|
||||
AARCH64_ARCH_TIMER_HYP_IRQ, AARCH64_ARCH_TIMER_PHYS_NONSECURE_IRQ,
|
||||
AARCH64_ARCH_TIMER_PHYS_SECURE_IRQ, AARCH64_ARCH_TIMER_VIRT_IRQ, AARCH64_PMU_IRQ,
|
||||
};
|
||||
use log::{debug, warn};
|
||||
use log::{debug, info, warn};
|
||||
use thiserror::Error;
|
||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
||||
@@ -345,6 +345,17 @@ fn create_cpu_nodes(
|
||||
warn!("cache sysfs system does not exist.");
|
||||
}
|
||||
|
||||
// Arm boot protocol requires a minimal Device Tree
|
||||
// https://docs.kernel.org/arch/arm64/booting.html
|
||||
// As Generic initiators are supported only in ACPI
|
||||
// When a guest kernel does not boot under "acpi=force" mode it can
|
||||
// hang due to conflicting numa information present in FDT which
|
||||
// does not support Generic Initiators
|
||||
let has_generic_initiator = numa_nodes.values().any(|node| node.device_id.is_some());
|
||||
if has_generic_initiator {
|
||||
info!("Skipping NUMA CPU node encoding in FDT with Generic Initiator devices");
|
||||
}
|
||||
|
||||
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
|
||||
let cpu_name = format!("cpu@{cpu_id:x}");
|
||||
let cpu_node = fdt.begin_node(&cpu_name)?;
|
||||
@@ -359,8 +370,10 @@ fn create_cpu_nodes(
|
||||
fdt.property_u32("reg", (mpidr & 0x7FFFFF) as u32)?;
|
||||
fdt.property_u32("phandle", cpu_id as u32 + FIRST_VCPU_PHANDLE)?;
|
||||
|
||||
// Add `numa-node-id` property if there is any numa config.
|
||||
if numa_nodes.len() > 1 {
|
||||
// Skipping NUMA encoding in FDT when Generic Initiator devices
|
||||
// are present allowed such guest kernels to boot properly and
|
||||
// rely solely on ACPI tables to setup NUMA
|
||||
if numa_nodes.len() > 1 && !has_generic_initiator {
|
||||
for numa_node_idx in 0..numa_nodes.len() {
|
||||
let numa_node = numa_nodes.get(&(numa_node_idx as u32));
|
||||
if numa_node.unwrap().cpus.contains(&(cpu_id as u32)) {
|
||||
@@ -501,7 +514,14 @@ fn create_memory_node(
|
||||
) -> FdtWriterResult<()> {
|
||||
// See https://github.com/torvalds/linux/blob/58ae0b51506802713aa0e9956d1853ba4c722c98/Documentation/devicetree/bindings/numa.txt
|
||||
// for NUMA setting in memory node.
|
||||
if numa_nodes.len() > 1 {
|
||||
let has_generic_initiator = numa_nodes.values().any(|node| node.device_id.is_some());
|
||||
if has_generic_initiator {
|
||||
info!("Skipping NUMA memory node encoding in FDT with Generic Initiator devices");
|
||||
}
|
||||
// Skipping NUMA encoding in FDT when Generic Initiator devices
|
||||
// are present allowed guest kernels to boot and
|
||||
// rely solely on ACPI tables to setup NUMA
|
||||
if numa_nodes.len() > 1 && !has_generic_initiator {
|
||||
for numa_node_idx in 0..numa_nodes.len() {
|
||||
let numa_node = numa_nodes.get(&(numa_node_idx as u32));
|
||||
let mut mem_reg_prop: Vec<u64> = Vec::new();
|
||||
@@ -518,12 +538,15 @@ fn create_memory_node(
|
||||
node_memory_addr = memory_region_start_addr;
|
||||
}
|
||||
}
|
||||
let memory_node_name = format!("memory@{node_memory_addr:x}");
|
||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||
fdt.property_string("device_type", "memory")?;
|
||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||
fdt.property_u32("numa-node-id", numa_node_idx as u32)?;
|
||||
fdt.end_node(memory_node)?;
|
||||
// Only create a memory node if this NUMA node has memory regions
|
||||
if !mem_reg_prop.is_empty() {
|
||||
let memory_node_name = format!("memory@{node_memory_addr:x}");
|
||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||
fdt.property_string("device_type", "memory")?;
|
||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||
fdt.property_u32("numa-node-id", numa_node_idx as u32)?;
|
||||
fdt.end_node(memory_node)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
|
||||
@@ -1044,6 +1067,22 @@ fn create_pci_nodes(
|
||||
}
|
||||
|
||||
fn create_distance_map_node(fdt: &mut FdtWriter, numa_nodes: &NumaNodes) -> FdtWriterResult<()> {
|
||||
// When Generic Initiator nodes are present, skip ALL FDT NUMA information.
|
||||
// Let ACPI (which supports Generic Initiator via SRAT Type 5) handle the entire NUMA topology.
|
||||
// FDT cannot represent Generic Initiator nodes, and mixing FDT + ACPI NUMA info causes conflicts.
|
||||
let has_generic_initiator = numa_nodes.values().any(|node| node.device_id.is_some());
|
||||
if has_generic_initiator {
|
||||
info!("Skipping NUMA distance map encoding in FDT with Generic Initiator devices");
|
||||
return Ok(());
|
||||
}
|
||||
// At this point, we know there are no Generic Initiator nodes
|
||||
let mut numa_ids: Vec<u32> = numa_nodes.keys().cloned().collect();
|
||||
|
||||
// If we only have one node, no distance map is needed
|
||||
if numa_ids.len() <= 1 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let distance_map_node = fdt.begin_node("distance-map")?;
|
||||
fdt.property_string("compatible", "numa-distance-map-v1")?;
|
||||
// Construct the distance matrix.
|
||||
@@ -1056,26 +1095,33 @@ fn create_distance_map_node(fdt: &mut FdtWriter, numa_nodes: &NumaNodes) -> FdtW
|
||||
// a value greater than 10.
|
||||
// 4. distance-matrix should have entries in lexicographical ascending
|
||||
// order of nodes.
|
||||
numa_ids.sort_unstable(); // lexicographical order
|
||||
let mut distance_matrix = Vec::new();
|
||||
for numa_node_idx in 0..numa_nodes.len() {
|
||||
let numa_node = numa_nodes.get(&(numa_node_idx as u32));
|
||||
for dest_numa_node in 0..numa_node.unwrap().distances.len() + 1 {
|
||||
if numa_node_idx == dest_numa_node {
|
||||
distance_matrix.push(numa_node_idx as u32);
|
||||
distance_matrix.push(dest_numa_node as u32);
|
||||
// Iterate over actual numa IDs instead of 0..len()
|
||||
for numa_id in numa_ids.iter() {
|
||||
let numa_node = &numa_nodes[numa_id];
|
||||
for dest_numa_id in numa_ids.iter() {
|
||||
if *numa_id == *dest_numa_id {
|
||||
distance_matrix.push(*numa_id);
|
||||
distance_matrix.push(*dest_numa_id);
|
||||
distance_matrix.push(10_u32);
|
||||
continue;
|
||||
}
|
||||
|
||||
distance_matrix.push(numa_node_idx as u32);
|
||||
distance_matrix.push(dest_numa_node as u32);
|
||||
distance_matrix.push(
|
||||
*numa_node
|
||||
.unwrap()
|
||||
.distances
|
||||
.get(&(dest_numa_node as u32))
|
||||
.unwrap() as u32,
|
||||
);
|
||||
distance_matrix.push(*numa_id);
|
||||
distance_matrix.push(*dest_numa_id);
|
||||
// Use user-specified distance, checking both directions for symmetry
|
||||
let distance = if let Some(&dist) = numa_node.distances.get(dest_numa_id) {
|
||||
// Forward direction: current node -> dest node
|
||||
dist
|
||||
} else if let Some(dest_node) = numa_nodes.get(dest_numa_id) {
|
||||
// Reverse direction for symmetry: dest node -> current node
|
||||
dest_node.distances.get(numa_id).copied().unwrap_or(20)
|
||||
} else {
|
||||
// Default distance when neither direction is specified
|
||||
20
|
||||
};
|
||||
distance_matrix.push(distance as u32);
|
||||
}
|
||||
}
|
||||
fdt.property_array_u32("distance-matrix", distance_matrix.as_ref())?;
|
||||
@@ -1160,3 +1206,118 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
||||
print_node(child, n_spaces + 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::*;
|
||||
use crate::NumaNode;
|
||||
|
||||
// Helper function to create a simple NumaNode for testing
|
||||
fn create_test_numa_node(cpus: Vec<u32>, device_id: Option<String>) -> NumaNode {
|
||||
NumaNode {
|
||||
memory_regions: Vec::new(),
|
||||
hotplug_regions: Vec::new(),
|
||||
cpus,
|
||||
pci_segments: Vec::new(),
|
||||
distances: BTreeMap::new(),
|
||||
memory_zones: Vec::new(),
|
||||
device_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fdt_generic_initiator_detection_and_skip() {
|
||||
// No Generic Initiator - should not skip FDT NUMA
|
||||
let mut numa_nodes = BTreeMap::new();
|
||||
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
|
||||
numa_nodes.insert(1, create_test_numa_node(vec![2, 3], None));
|
||||
|
||||
let has_gi = numa_nodes.values().any(|node| node.device_id.is_some());
|
||||
assert!(
|
||||
!has_gi,
|
||||
"Should not detect Generic Initiator when none present"
|
||||
);
|
||||
|
||||
// One Generic Initiator - should skip FDT NUMA
|
||||
let mut numa_nodes = BTreeMap::new();
|
||||
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
|
||||
numa_nodes.insert(1, create_test_numa_node(vec![], Some("vfio0".to_string())));
|
||||
|
||||
let has_gi = numa_nodes.values().any(|node| node.device_id.is_some());
|
||||
assert!(has_gi, "Should detect Generic Initiator when present");
|
||||
|
||||
let mut fdt = FdtWriter::new().unwrap();
|
||||
let result = create_distance_map_node(&mut fdt, &numa_nodes);
|
||||
assert!(result.is_ok(), "Should skip distance map when GI present");
|
||||
|
||||
// Multiple Generic Initiators - should skip FDT NUMA
|
||||
let mut numa_nodes = BTreeMap::new();
|
||||
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
|
||||
numa_nodes.insert(1, create_test_numa_node(vec![], Some("vfio0".to_string())));
|
||||
numa_nodes.insert(2, create_test_numa_node(vec![], Some("vfio1".to_string())));
|
||||
|
||||
let has_gi = numa_nodes.values().any(|node| node.device_id.is_some());
|
||||
assert!(has_gi, "Should detect multiple Generic Initiators");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fdt_distance_map() {
|
||||
// Single NUMA node - should skip distance map
|
||||
let mut numa_nodes = BTreeMap::new();
|
||||
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
|
||||
|
||||
let mut fdt = FdtWriter::new().unwrap();
|
||||
let result = create_distance_map_node(&mut fdt, &numa_nodes);
|
||||
assert!(result.is_ok(), "Should skip distance map for single node");
|
||||
|
||||
// Empty NUMA nodes - should handle gracefully
|
||||
let numa_nodes = BTreeMap::new();
|
||||
let mut fdt = FdtWriter::new().unwrap();
|
||||
let result = create_distance_map_node(&mut fdt, &numa_nodes);
|
||||
assert!(result.is_ok(), "Should handle empty NUMA nodes");
|
||||
|
||||
// Non-contiguous NUMA IDs (0, 2, 5) with distance symmetry
|
||||
let mut numa_nodes = BTreeMap::new();
|
||||
|
||||
let mut node0 = create_test_numa_node(vec![0], None);
|
||||
node0.distances.insert(2, 20);
|
||||
// node0 has no explicit distance to node5
|
||||
|
||||
let mut node2 = create_test_numa_node(vec![1], None);
|
||||
node2.distances.insert(0, 20);
|
||||
node2.distances.insert(5, 25);
|
||||
|
||||
let mut node5 = create_test_numa_node(vec![2], None);
|
||||
node5.distances.insert(0, 30);
|
||||
node5.distances.insert(2, 25);
|
||||
// node5->node0 (should be used for node0->node5)
|
||||
|
||||
numa_nodes.insert(0, node0);
|
||||
numa_nodes.insert(2, node2);
|
||||
numa_nodes.insert(5, node5);
|
||||
|
||||
// Verify IDs are sorted lexicographically
|
||||
let mut numa_ids: Vec<u32> = numa_nodes.keys().cloned().collect();
|
||||
numa_ids.sort_unstable();
|
||||
assert_eq!(numa_ids, vec![0, 2, 5]);
|
||||
|
||||
let mut fdt = FdtWriter::new().unwrap();
|
||||
let result = create_distance_map_node(&mut fdt, &numa_nodes);
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Should handle non-contiguous IDs and symmetry"
|
||||
);
|
||||
|
||||
// Default distance (20) when no distance specified in either direction
|
||||
let mut numa_nodes = BTreeMap::new();
|
||||
numa_nodes.insert(0, create_test_numa_node(vec![0], None));
|
||||
numa_nodes.insert(1, create_test_numa_node(vec![1], None));
|
||||
// Neither node has distance to the other
|
||||
|
||||
let mut fdt = FdtWriter::new().unwrap();
|
||||
let result = create_distance_map_node(&mut fdt, &numa_nodes);
|
||||
assert!(result.is_ok(), "Should default to 20 for missing distances");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ pub struct NumaNode {
|
||||
pub pci_segments: Vec<u16>,
|
||||
pub distances: BTreeMap<u32, u8>,
|
||||
pub memory_zones: Vec<String>,
|
||||
pub device_id: Option<String>,
|
||||
}
|
||||
|
||||
pub type NumaNodes = BTreeMap<u32, NumaNode>;
|
||||
|
||||
@@ -559,6 +559,7 @@ pub fn generate_common_cpuid(
|
||||
hypervisor: &dyn hypervisor::Hypervisor,
|
||||
config: &CpuidConfig,
|
||||
) -> super::Result<Vec<CpuIdEntry>> {
|
||||
#[allow(unused_unsafe)]
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
if unsafe { x86_64::__cpuid(1) }.ecx & (1 << HYPERVISOR_ECX_BIT) == 1 << HYPERVISOR_ECX_BIT {
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
@@ -694,6 +695,7 @@ pub fn generate_common_cpuid(
|
||||
// Copy host L1 cache details if not populated by KVM
|
||||
0x8000_0005 => {
|
||||
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
||||
#[allow(unused_unsafe)]
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0005 {
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
@@ -708,8 +710,10 @@ pub fn generate_common_cpuid(
|
||||
// Copy host L2 cache details if not populated by KVM
|
||||
0x8000_0006 => {
|
||||
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
||||
#[allow(unused_unsafe)]
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0006 {
|
||||
#[allow(unused_unsafe)]
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0006) };
|
||||
entry.eax = leaf.eax;
|
||||
@@ -747,6 +751,7 @@ pub fn generate_common_cpuid(
|
||||
for i in 0x8000_0002..=0x8000_0004 {
|
||||
cpuid.retain(|c| c.function != i);
|
||||
// SAFETY: call cpuid with valid leaves
|
||||
#[allow(unused_unsafe)]
|
||||
let leaf = unsafe { std::arch::x86_64::__cpuid(i) };
|
||||
cpuid.push(CpuIdEntry {
|
||||
function: i,
|
||||
@@ -859,6 +864,7 @@ pub fn configure_vcpu(
|
||||
// The TSC frequency CPUID leaf should not be included when running with HyperV emulation
|
||||
if !kvm_hyperv && let Some(tsc_khz) = vcpu.tsc_khz().map_err(Error::GetTscFrequency)? {
|
||||
// Need to check that the TSC doesn't vary with dynamic frequency
|
||||
#[allow(unused_unsafe)]
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
if unsafe { std::arch::x86_64::__cpuid(0x8000_0007) }.edx & (1u32 << INVARIANT_TSC_EDX_BIT)
|
||||
> 0
|
||||
@@ -1307,6 +1313,7 @@ pub fn initramfs_load_addr(
|
||||
|
||||
pub fn get_host_cpu_phys_bits(hypervisor: &dyn hypervisor::Hypervisor) -> u8 {
|
||||
// SAFETY: call cpuid with valid leaves
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
let leaf = x86_64::__cpuid(0x8000_0000);
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ pub enum Error {
|
||||
#[error("Failure to write additional data to memory")]
|
||||
WriteData,
|
||||
/// Failure to parse uuid, uuid format may be error
|
||||
#[error("Failure to parse uuid")]
|
||||
ParseUuid(#[source] uuid::Error),
|
||||
#[error("Failure to parse uuid: {1}")]
|
||||
ParseUuid(#[source] uuid::Error, String),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
@@ -198,7 +198,7 @@ pub fn setup_smbios(
|
||||
let uuid_number = uuid
|
||||
.map(Uuid::parse_str)
|
||||
.transpose()
|
||||
.map_err(Error::ParseUuid)?
|
||||
.map_err(|e| Error::ParseUuid(e, uuid.unwrap().to_string()))?
|
||||
.unwrap_or(Uuid::nil());
|
||||
let smbios_sysinfo = SmbiosSysInfo {
|
||||
r#type: SYSTEM_INFORMATION,
|
||||
|
||||
@@ -9,9 +9,10 @@ default = []
|
||||
io_uring = ["dep:io-uring"]
|
||||
|
||||
[dependencies]
|
||||
bitflags = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
crc-any = "2.5.0"
|
||||
flate2 = "1.0"
|
||||
flate2 = "1.1"
|
||||
io-uring = { version = "0.7.11", optional = true }
|
||||
libc = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
@@ -78,6 +78,18 @@ pub trait DiskFile: Send {
|
||||
Err(DiskFileError::Unsupported)
|
||||
}
|
||||
|
||||
/// Indicates support for sparse operations (punch hole, write zeroes, discard).
|
||||
/// Override to return true when supported.
|
||||
fn supports_sparse_operations(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Indicates support for zero flag optimization in WRITE_ZEROES. Override
|
||||
/// to return true when supported.
|
||||
fn supports_zero_flag(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns the file descriptor of the underlying disk image file.
|
||||
///
|
||||
/// The file descriptor is supposed to be used for `fcntl()` calls but no
|
||||
@@ -96,6 +108,12 @@ pub enum AsyncIoError {
|
||||
/// Failed synchronizing file.
|
||||
#[error("Failed synchronizing file")]
|
||||
Fsync(#[source] std::io::Error),
|
||||
/// Failed punching hole.
|
||||
#[error("Failed punching hole")]
|
||||
PunchHole(#[source] std::io::Error),
|
||||
/// Failed writing zeroes.
|
||||
#[error("Failed writing zeroes")]
|
||||
WriteZeroes(#[source] std::io::Error),
|
||||
/// Failed submitting batch requests.
|
||||
#[error("Failed submitting batch requests")]
|
||||
SubmitBatchRequests(#[source] std::io::Error),
|
||||
@@ -118,6 +136,8 @@ pub trait AsyncIo: Send {
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()>;
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
|
||||
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()>;
|
||||
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()>;
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
|
||||
fn batch_requests_enabled(&self) -> bool {
|
||||
false
|
||||
|
||||
@@ -115,6 +115,18 @@ impl AsyncIo for FixedVhdAsync {
|
||||
self.raw_file_async.next_completed_request()
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::PunchHole(std::io::Error::other(
|
||||
"punch_hole not supported for fixed VHD",
|
||||
)))
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::WriteZeroes(std::io::Error::other(
|
||||
"write_zeroes not supported for fixed VHD",
|
||||
)))
|
||||
}
|
||||
|
||||
fn batch_requests_enabled(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -113,4 +113,16 @@ impl AsyncIo for FixedVhdSync {
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||
self.raw_file_sync.next_completed_request()
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::PunchHole(std::io::Error::other(
|
||||
"punch_hole not supported for fixed VHD",
|
||||
)))
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::WriteZeroes(std::io::Error::other(
|
||||
"write_zeroes not supported for fixed VHD",
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
267
block/src/lib.rs
267
block/src/lib.rs
@@ -30,19 +30,20 @@ pub mod vhdx_sync;
|
||||
|
||||
use std::alloc::{Layout, alloc_zeroed, dealloc};
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::fs::File;
|
||||
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
||||
use std::os::linux::fs::MetadataExt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::time::Instant;
|
||||
use std::{cmp, result};
|
||||
|
||||
#[cfg(feature = "io_uring")]
|
||||
use io_uring::{IoUring, Probe, opcode};
|
||||
use libc::{S_IFBLK, S_IFMT, ioctl};
|
||||
use log::{error, info, warn};
|
||||
use log::{debug, error, info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
use thiserror::Error;
|
||||
@@ -50,7 +51,7 @@ use virtio_bindings::virtio_blk::*;
|
||||
use virtio_queue::DescriptorChain;
|
||||
use vm_memory::bitmap::Bitmap;
|
||||
use vm_memory::{
|
||||
ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryLoadGuard,
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryLoadGuard,
|
||||
};
|
||||
use vm_virtio::{AccessPlatform, Translatable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
@@ -156,6 +157,10 @@ pub enum ExecuteError {
|
||||
AsyncWrite(#[source] AsyncIoError),
|
||||
#[error("failed to async flush")]
|
||||
AsyncFlush(#[source] AsyncIoError),
|
||||
#[error("Failed to async punch hole")]
|
||||
AsyncPunchHole(#[source] AsyncIoError),
|
||||
#[error("Failed to async write zeroes")]
|
||||
AsyncWriteZeroes(#[source] AsyncIoError),
|
||||
#[error("Failed allocating a temporary buffer")]
|
||||
TemporaryBufferAllocation(#[source] io::Error),
|
||||
}
|
||||
@@ -177,6 +182,8 @@ impl ExecuteError {
|
||||
ExecuteError::AsyncRead(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncPunchHole(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncWriteZeroes(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::TemporaryBufferAllocation(_) => VIRTIO_BLK_S_IOERR,
|
||||
};
|
||||
status as u8
|
||||
@@ -189,6 +196,8 @@ pub enum RequestType {
|
||||
Out,
|
||||
Flush,
|
||||
GetDeviceId,
|
||||
Discard,
|
||||
WriteZeroes,
|
||||
Unsupported(u32),
|
||||
}
|
||||
|
||||
@@ -202,6 +211,8 @@ pub fn request_type<B: Bitmap + 'static>(
|
||||
VIRTIO_BLK_T_OUT => Ok(RequestType::Out),
|
||||
VIRTIO_BLK_T_FLUSH => Ok(RequestType::Flush),
|
||||
VIRTIO_BLK_T_GET_ID => Ok(RequestType::GetDeviceId),
|
||||
VIRTIO_BLK_T_DISCARD => Ok(RequestType::Discard),
|
||||
VIRTIO_BLK_T_WRITE_ZEROES => Ok(RequestType::WriteZeroes),
|
||||
t => Ok(RequestType::Unsupported(t)),
|
||||
}
|
||||
}
|
||||
@@ -299,6 +310,12 @@ impl Request {
|
||||
if desc.is_write_only() && req.request_type == RequestType::Out {
|
||||
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
||||
}
|
||||
if desc.is_write_only() && req.request_type == RequestType::Discard {
|
||||
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
||||
}
|
||||
if desc.is_write_only() && req.request_type == RequestType::WriteZeroes {
|
||||
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
||||
}
|
||||
if !desc.is_write_only() && req.request_type == RequestType::In {
|
||||
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||
}
|
||||
@@ -395,6 +412,12 @@ impl Request {
|
||||
mem.write_slice(serial, *data_addr)
|
||||
.map_err(ExecuteError::Write)?;
|
||||
}
|
||||
RequestType::Discard => {
|
||||
return Err(ExecuteError::Unsupported(VIRTIO_BLK_T_DISCARD));
|
||||
}
|
||||
RequestType::WriteZeroes => {
|
||||
return Err(ExecuteError::Unsupported(VIRTIO_BLK_T_WRITE_ZEROES));
|
||||
}
|
||||
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
||||
}
|
||||
}
|
||||
@@ -407,6 +430,7 @@ impl Request {
|
||||
disk_nsectors: u64,
|
||||
disk_image: &mut dyn AsyncIo,
|
||||
serial: &[u8],
|
||||
disable_sector0_writes: bool,
|
||||
user_data: u64,
|
||||
) -> result::Result<ExecuteAsync, ExecuteError> {
|
||||
let sector = self.sector;
|
||||
@@ -544,6 +568,71 @@ impl Request {
|
||||
ret.async_complete = false;
|
||||
return Ok(ret);
|
||||
}
|
||||
RequestType::Discard => {
|
||||
let (data_addr, data_len) = if self.data_descriptors.len() == 1 {
|
||||
(self.data_descriptors[0].0, self.data_descriptors[0].1)
|
||||
} else {
|
||||
return Err(ExecuteError::BadRequest(Error::TooManyDescriptors));
|
||||
};
|
||||
|
||||
if data_len < 16 {
|
||||
return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall));
|
||||
}
|
||||
|
||||
let mut discard_sector = [0u8; 8];
|
||||
let mut discard_num_sectors = [0u8; 4];
|
||||
mem.read_slice(&mut discard_sector, data_addr)
|
||||
.map_err(ExecuteError::Read)?;
|
||||
mem.read_slice(&mut discard_num_sectors, data_addr.checked_add(8).unwrap())
|
||||
.map_err(ExecuteError::Read)?;
|
||||
|
||||
let discard_sector = u64::from_le_bytes(discard_sector);
|
||||
|
||||
if discard_sector == 0 && disable_sector0_writes {
|
||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||
}
|
||||
|
||||
let discard_num_sectors = u32::from_le_bytes(discard_num_sectors);
|
||||
|
||||
let discard_offset = discard_sector * SECTOR_SIZE;
|
||||
let discard_length = (discard_num_sectors as u64) * SECTOR_SIZE;
|
||||
|
||||
disk_image
|
||||
.punch_hole(discard_offset, discard_length, user_data)
|
||||
.map_err(ExecuteError::AsyncPunchHole)?;
|
||||
}
|
||||
RequestType::WriteZeroes => {
|
||||
let (data_addr, data_len) = if self.data_descriptors.len() == 1 {
|
||||
(self.data_descriptors[0].0, self.data_descriptors[0].1)
|
||||
} else {
|
||||
return Err(ExecuteError::BadRequest(Error::TooManyDescriptors));
|
||||
};
|
||||
|
||||
if data_len < 16 {
|
||||
return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall));
|
||||
}
|
||||
|
||||
let mut wz_sector = [0u8; 8];
|
||||
|
||||
let mut wz_num_sectors = [0u8; 4];
|
||||
mem.read_slice(&mut wz_sector, data_addr)
|
||||
.map_err(ExecuteError::Read)?;
|
||||
mem.read_slice(&mut wz_num_sectors, data_addr.checked_add(8).unwrap())
|
||||
.map_err(ExecuteError::Read)?;
|
||||
|
||||
let wz_sector = u64::from_le_bytes(wz_sector);
|
||||
let wz_num_sectors = u32::from_le_bytes(wz_num_sectors);
|
||||
|
||||
let wz_offset = wz_sector * SECTOR_SIZE;
|
||||
if wz_offset == 0 && disable_sector0_writes {
|
||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||
}
|
||||
let wz_length = (wz_num_sectors as u64) * SECTOR_SIZE;
|
||||
|
||||
disk_image
|
||||
.write_zeroes(wz_offset, wz_length, user_data)
|
||||
.map_err(ExecuteError::AsyncWriteZeroes)?;
|
||||
}
|
||||
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
||||
}
|
||||
|
||||
@@ -684,6 +773,145 @@ pub fn block_io_uring_is_supported() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Probe whether the file/device supports punch hole and zero range
|
||||
pub fn probe_sparse_support(file: &File) -> bool {
|
||||
let fd = file.as_raw_fd();
|
||||
|
||||
let is_block_device = {
|
||||
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
|
||||
// SAFETY: FFI call with valid fd and buffer
|
||||
let ret = unsafe { libc::fstat(fd, stat.as_mut_ptr()) };
|
||||
if ret != 0 {
|
||||
warn!(
|
||||
"Failed to stat file descriptor for sparse probe: {}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// SAFETY: stat result is valid at this point
|
||||
unsafe { (*stat.as_ptr()).st_mode & S_IFMT == S_IFBLK }
|
||||
};
|
||||
|
||||
if is_block_device {
|
||||
probe_block_device_sparse_support(fd)
|
||||
} else {
|
||||
probe_file_sparse_support(fd)
|
||||
}
|
||||
}
|
||||
|
||||
/// Probe sparse support for a regular file using fallocate().
|
||||
fn probe_file_sparse_support(fd: libc::c_int) -> bool {
|
||||
const FALLOC_FL_KEEP_SIZE: libc::c_int = 0x01;
|
||||
const FALLOC_FL_PUNCH_HOLE: libc::c_int = 0x02;
|
||||
const FALLOC_FL_ZERO_RANGE: libc::c_int = 0x10;
|
||||
|
||||
// SAFETY: FFI call with valid fd
|
||||
let file_size = unsafe { libc::lseek(fd, 0, libc::SEEK_END) };
|
||||
if file_size < 0 {
|
||||
let err = io::Error::last_os_error();
|
||||
warn!("Failed to get file size for sparse probe: {err}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// SAFETY: FFI call with valid fd, probing past EOF is safe with KEEP_SIZE
|
||||
let punch_hole =
|
||||
unsafe { libc::fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, file_size, 1) }
|
||||
== 0;
|
||||
|
||||
if !punch_hole {
|
||||
let err = io::Error::last_os_error();
|
||||
if err.raw_os_error() == Some(libc::EOPNOTSUPP) {
|
||||
debug!("File does not support FALLOC_FL_PUNCH_HOLE: {err}");
|
||||
} else {
|
||||
debug!("PUNCH_HOLE probe returned unexpected error: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: FFI call with valid fd, probing past EOF is safe with KEEP_SIZE
|
||||
let zero_range =
|
||||
unsafe { libc::fallocate(fd, FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE, file_size, 1) }
|
||||
== 0;
|
||||
|
||||
if !zero_range {
|
||||
let err = io::Error::last_os_error();
|
||||
if err.raw_os_error() == Some(libc::EOPNOTSUPP) {
|
||||
debug!("File does not support FALLOC_FL_ZERO_RANGE: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
let supported = punch_hole || zero_range;
|
||||
info!(
|
||||
"Probed file sparse support: punch_hole={punch_hole}, zero_range={zero_range} => {supported}"
|
||||
);
|
||||
supported
|
||||
}
|
||||
|
||||
/// Probe sparse support for a block device using ioctls.
|
||||
fn probe_block_device_sparse_support(fd: libc::c_int) -> bool {
|
||||
ioctl_io_nr!(BLKDISCARD, 0x12, 119);
|
||||
ioctl_io_nr!(BLKZEROOUT, 0x12, 127);
|
||||
|
||||
let range: [u64; 2] = [0, 0];
|
||||
|
||||
// SAFETY: FFI call with valid fd and valid range buffer
|
||||
let punch_hole = unsafe { ioctl(fd, BLKDISCARD() as _, &range) } == 0;
|
||||
|
||||
if !punch_hole {
|
||||
let err = io::Error::last_os_error();
|
||||
debug!("Block device BLKDISCARD probe returned: {err}");
|
||||
}
|
||||
|
||||
// SAFETY: FFI call with valid fd and valid range buffer
|
||||
let zero_range = unsafe { ioctl(fd, BLKZEROOUT() as _, &range) } == 0;
|
||||
|
||||
if !zero_range {
|
||||
let err = io::Error::last_os_error();
|
||||
debug!("Block device BLKZEROOUT probe returned: {err}");
|
||||
}
|
||||
|
||||
let supported = punch_hole || zero_range;
|
||||
info!(
|
||||
"Probed block device sparse support: punch_hole={punch_hole}, zero_range={zero_range} => {supported}"
|
||||
);
|
||||
supported
|
||||
}
|
||||
|
||||
/// Preallocate disk space for a disk image file.
|
||||
///
|
||||
/// Uses `fallocate()` to allocate all disk space upfront, ensuring storage
|
||||
/// availability and reducing fragmentation. Allocating all blocks upfront is
|
||||
/// more likely to place them contiguously than allocating on demand during
|
||||
/// random writes.
|
||||
pub fn preallocate_disk<P: AsRef<Path>>(file: &File, path: P) {
|
||||
let size = match file.metadata() {
|
||||
Ok(m) => m.len(),
|
||||
Err(e) => {
|
||||
warn!("Failed to get metadata for {:?}: {}", path.as_ref(), e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if size == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// SAFETY: FFI call with valid file descriptor and size
|
||||
let ret = unsafe { libc::fallocate(file.as_raw_fd(), 0, 0, size as libc::off_t) };
|
||||
|
||||
if ret != 0 {
|
||||
warn!(
|
||||
"Failed to preallocate disk space for {:?}: {}",
|
||||
path.as_ref(),
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
"Preallocated {size} bytes for disk image {:?}",
|
||||
path.as_ref()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AsyncAdaptor {
|
||||
fn read_vectored_sync(
|
||||
&mut self,
|
||||
@@ -788,11 +1016,44 @@ pub trait AsyncAdaptor {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
pub enum ImageType {
|
||||
FixedVhd,
|
||||
Qcow2,
|
||||
Raw,
|
||||
Vhdx,
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl fmt::Display for ImageType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ImageType::FixedVhd => write!(f, "vhd"),
|
||||
ImageType::Qcow2 => write!(f, "qcow2"),
|
||||
ImageType::Raw => write!(f, "raw"),
|
||||
ImageType::Vhdx => write!(f, "vhdx"),
|
||||
ImageType::Unknown => write!(f, "unknown"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ImageTypeParseError {
|
||||
InvalidValue(String),
|
||||
}
|
||||
|
||||
impl FromStr for ImageType {
|
||||
type Err = ImageTypeParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"vhd" => Ok(ImageType::FixedVhd),
|
||||
"qcow2" => Ok(ImageType::Qcow2),
|
||||
"raw" => Ok(ImageType::Raw),
|
||||
"vhdx" => Ok(ImageType::Vhdx),
|
||||
_ => Err(ImageTypeParseError::InvalidValue(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,8 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::io::{self, BufWriter, Seek, SeekFrom, Write};
|
||||
use std::fmt::Debug;
|
||||
use std::io::{self, BufWriter, Read, Seek, SeekFrom, Write};
|
||||
use std::mem::size_of;
|
||||
use std::os::fd::{AsRawFd, RawFd};
|
||||
|
||||
@@ -13,25 +14,184 @@ use vmm_sys_util::write_zeroes::WriteZeroes;
|
||||
|
||||
use super::RawFile;
|
||||
|
||||
// Type aliases for the refcount read/write function pointers
|
||||
type RefcountReader = fn(&mut RawFile, usize) -> io::Result<Vec<u64>>;
|
||||
type RefcountWriter = fn(&mut RawFile, &[u64]) -> io::Result<()>;
|
||||
|
||||
/// Big-endian file access trait.
|
||||
pub(super) trait BeUint: Sized + Copy {
|
||||
fn from_be_slice(bytes: &[u8]) -> u64;
|
||||
fn read_be<R: Read>(r: &mut R) -> io::Result<Self>;
|
||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl BeUint for u8 {
|
||||
#[inline(always)]
|
||||
fn from_be_slice(bytes: &[u8]) -> u64 {
|
||||
bytes[0] as u64
|
||||
}
|
||||
#[inline(always)]
|
||||
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
|
||||
r.read_u8()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
||||
w.write_u8(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl BeUint for u16 {
|
||||
#[inline(always)]
|
||||
fn from_be_slice(bytes: &[u8]) -> u64 {
|
||||
u16::from_be_bytes([bytes[0], bytes[1]]) as u64
|
||||
}
|
||||
#[inline(always)]
|
||||
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
|
||||
r.read_u16::<BigEndian>()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
||||
w.write_u16::<BigEndian>(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl BeUint for u32 {
|
||||
#[inline(always)]
|
||||
fn from_be_slice(bytes: &[u8]) -> u64 {
|
||||
u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as u64
|
||||
}
|
||||
#[inline(always)]
|
||||
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
|
||||
r.read_u32::<BigEndian>()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
||||
w.write_u32::<BigEndian>(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl BeUint for u64 {
|
||||
#[inline(always)]
|
||||
fn from_be_slice(bytes: &[u8]) -> u64 {
|
||||
u64::from_be_bytes([
|
||||
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
|
||||
])
|
||||
}
|
||||
#[inline(always)]
|
||||
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
|
||||
r.read_u64::<BigEndian>()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
||||
w.write_u64::<BigEndian>(val)
|
||||
}
|
||||
}
|
||||
|
||||
/// Read byte-aligned refcounts.
|
||||
fn read_refcount<T: BeUint>(file: &mut RawFile, count: usize) -> io::Result<Vec<u64>> {
|
||||
let bytes_per_entry = size_of::<T>();
|
||||
let mut data = vec![0u8; count * bytes_per_entry];
|
||||
file.read_exact(&mut data)?;
|
||||
Ok(data
|
||||
.chunks_exact(bytes_per_entry)
|
||||
.map(T::from_be_slice)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Write byte-aligned refcounts.
|
||||
fn write_refcount<T: BeUint + TryFrom<u64>>(file: &mut RawFile, table: &[u64]) -> io::Result<()>
|
||||
where
|
||||
<T as TryFrom<u64>>::Error: Debug,
|
||||
{
|
||||
let bytes_per_entry = size_of::<T>();
|
||||
let mut buffer = BufWriter::with_capacity(table.len() * bytes_per_entry, file);
|
||||
for &val in table {
|
||||
let converted = T::try_from(val).expect("refcount values are validated on increment");
|
||||
T::write_be(&mut buffer, converted)?;
|
||||
}
|
||||
buffer.flush()
|
||||
}
|
||||
|
||||
/// Read sub-byte refcounts. Bit 0 is the least significant bit.
|
||||
fn read_refcount_subbyte<const BITS: usize>(
|
||||
file: &mut RawFile,
|
||||
count: usize,
|
||||
) -> io::Result<Vec<u64>> {
|
||||
const { assert!(BITS == 1 || BITS == 2 || BITS == 4) };
|
||||
let entries_per_byte = 8 / BITS;
|
||||
let mask = (1u64 << BITS) - 1;
|
||||
let bytes_needed = count.div_ceil(entries_per_byte);
|
||||
let mut bytes = vec![0u8; bytes_needed];
|
||||
file.read_exact(&mut bytes)?;
|
||||
|
||||
let mut table = vec![0u64; count];
|
||||
for (i, val) in table.iter_mut().enumerate() {
|
||||
let byte_idx = i / entries_per_byte;
|
||||
let bit_offset = (i % entries_per_byte) * BITS;
|
||||
*val = (bytes[byte_idx] as u64 >> bit_offset) & mask;
|
||||
}
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
/// Write sub-byte refcounts. Bit 0 is the least significant bit.
|
||||
fn write_refcount_subbyte<const BITS: usize>(file: &mut RawFile, table: &[u64]) -> io::Result<()> {
|
||||
const { assert!(BITS == 1 || BITS == 2 || BITS == 4) };
|
||||
let entries_per_byte = 8 / BITS;
|
||||
let mask = (1u64 << BITS) - 1;
|
||||
let mut buffer = BufWriter::with_capacity(table.len().div_ceil(entries_per_byte), file);
|
||||
|
||||
for chunk in table.chunks(entries_per_byte) {
|
||||
let mut byte = 0u8;
|
||||
for (i, &val) in chunk.iter().enumerate() {
|
||||
let bit_offset = i * BITS;
|
||||
byte |= ((val & mask) << bit_offset) as u8;
|
||||
}
|
||||
buffer.write_u8(byte)?;
|
||||
}
|
||||
buffer.flush()
|
||||
}
|
||||
|
||||
/// A qcow file. Allows reading/writing clusters and appending clusters.
|
||||
#[derive(Debug)]
|
||||
pub struct QcowRawFile {
|
||||
file: RawFile,
|
||||
cluster_size: u64,
|
||||
cluster_mask: u64,
|
||||
refcount_block_entries: u64,
|
||||
read_refcount_fn: RefcountReader,
|
||||
write_refcount_fn: RefcountWriter,
|
||||
}
|
||||
|
||||
impl QcowRawFile {
|
||||
/// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not
|
||||
/// a power of two.
|
||||
pub fn from(file: RawFile, cluster_size: u64) -> Option<Self> {
|
||||
/// a power of two or refcount_bits is invalid.
|
||||
pub fn from(file: RawFile, cluster_size: u64, refcount_bits: u64) -> Option<Self> {
|
||||
if !cluster_size.is_power_of_two() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (read_refcount_fn, write_refcount_fn): (RefcountReader, RefcountWriter) =
|
||||
match refcount_bits {
|
||||
1 => (read_refcount_subbyte::<1>, write_refcount_subbyte::<1>),
|
||||
2 => (read_refcount_subbyte::<2>, write_refcount_subbyte::<2>),
|
||||
4 => (read_refcount_subbyte::<4>, write_refcount_subbyte::<4>),
|
||||
8 => (read_refcount::<u8>, write_refcount::<u8>),
|
||||
16 => (read_refcount::<u16>, write_refcount::<u16>),
|
||||
32 => (read_refcount::<u32>, write_refcount::<u32>),
|
||||
64 => (read_refcount::<u64>, write_refcount::<u64>),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// For sub-byte refcounts (1,2,4 bits), entries pack multiple per byte
|
||||
let refcount_block_entries = cluster_size * 8 / refcount_bits;
|
||||
|
||||
Some(QcowRawFile {
|
||||
file,
|
||||
cluster_size,
|
||||
cluster_mask: cluster_size - 1,
|
||||
refcount_block_entries,
|
||||
read_refcount_fn,
|
||||
write_refcount_fn,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -86,7 +246,7 @@ impl QcowRawFile {
|
||||
|
||||
for addr in entries {
|
||||
let entry = f(self, *addr)?;
|
||||
buffer.write_u64::<BigEndian>(entry)?;
|
||||
u64::write_be(&mut buffer, entry)?;
|
||||
}
|
||||
buffer.flush()?;
|
||||
Ok(())
|
||||
@@ -101,7 +261,7 @@ impl QcowRawFile {
|
||||
let mut buffer = self.setup_pointer_table_writer(offset, &entries)?;
|
||||
|
||||
for &entry in entries {
|
||||
buffer.write_u64::<BigEndian>(entry)?;
|
||||
u64::write_be(&mut buffer, entry)?;
|
||||
}
|
||||
buffer.flush()?;
|
||||
Ok(())
|
||||
@@ -109,24 +269,17 @@ impl QcowRawFile {
|
||||
|
||||
/// Read a refcount block from the file and returns a Vec containing the block.
|
||||
/// Always returns a cluster's worth of data.
|
||||
pub fn read_refcount_block(&mut self, offset: u64) -> io::Result<Vec<u16>> {
|
||||
let count = self.cluster_size / size_of::<u16>() as u64;
|
||||
let mut table = vec![0; count as usize];
|
||||
#[inline]
|
||||
pub fn read_refcount_block(&mut self, offset: u64) -> io::Result<Vec<u64>> {
|
||||
self.file.seek(SeekFrom::Start(offset))?;
|
||||
self.file.read_u16_into::<BigEndian>(&mut table)?;
|
||||
Ok(table)
|
||||
(self.read_refcount_fn)(&mut self.file, self.refcount_block_entries as usize)
|
||||
}
|
||||
|
||||
/// Writes a refcount block to the file.
|
||||
pub fn write_refcount_block(&mut self, offset: u64, table: &[u16]) -> io::Result<()> {
|
||||
#[inline]
|
||||
pub fn write_refcount_block(&mut self, offset: u64, table: &[u64]) -> io::Result<()> {
|
||||
self.file.seek(SeekFrom::Start(offset))?;
|
||||
let mut buffer = BufWriter::with_capacity(std::mem::size_of_val(table), &mut self.file);
|
||||
|
||||
for count in table {
|
||||
buffer.write_u16::<BigEndian>(*count)?;
|
||||
}
|
||||
buffer.flush()?;
|
||||
Ok(())
|
||||
(self.write_refcount_fn)(&mut self.file, table)
|
||||
}
|
||||
|
||||
/// Allocates a new cluster at the end of the current file, return the address.
|
||||
@@ -145,6 +298,11 @@ impl QcowRawFile {
|
||||
Ok(Some(new_cluster_address))
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying file.
|
||||
pub fn file(&self) -> &RawFile {
|
||||
&self.file
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying file.
|
||||
pub fn file_mut(&mut self) -> &mut RawFile {
|
||||
&mut self.file
|
||||
@@ -191,6 +349,9 @@ impl Clone for QcowRawFile {
|
||||
file: self.file.try_clone().expect("QcowRawFile cloning failed"),
|
||||
cluster_size: self.cluster_size,
|
||||
cluster_mask: self.cluster_mask,
|
||||
refcount_block_entries: self.refcount_block_entries,
|
||||
read_refcount_fn: self.read_refcount_fn,
|
||||
write_refcount_fn: self.write_refcount_fn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::slice;
|
||||
|
||||
use libc::c_void;
|
||||
use vmm_sys_util::file_traits::FileSync;
|
||||
use vmm_sys_util::seek_hole::SeekHole;
|
||||
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
||||
|
||||
@@ -122,6 +123,17 @@ impl RawFile {
|
||||
pub fn is_direct(&self) -> bool {
|
||||
self.direct_io
|
||||
}
|
||||
|
||||
/// Returns true if the file was opened with write access.
|
||||
pub fn is_writable(&self) -> bool {
|
||||
// SAFETY: fcntl with F_GETFL is safe and doesn't modify the file descriptor
|
||||
let flags = unsafe { libc::fcntl(self.file.as_raw_fd(), libc::F_GETFL) };
|
||||
if flags < 0 {
|
||||
return false;
|
||||
}
|
||||
let access_mode = flags & libc::O_ACCMODE;
|
||||
access_mode == libc::O_WRONLY || access_mode == libc::O_RDWR
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for RawFile {
|
||||
@@ -327,6 +339,12 @@ impl PunchHole for RawFile {
|
||||
}
|
||||
}
|
||||
|
||||
impl FileSync for RawFile {
|
||||
fn fsync(&mut self) -> std::io::Result<()> {
|
||||
self.file.fsync()
|
||||
}
|
||||
}
|
||||
|
||||
impl SeekHole for RawFile {
|
||||
fn seek_hole(&mut self, offset: u64) -> std::io::Result<Option<u64>> {
|
||||
match self.file.seek_hole(offset) {
|
||||
|
||||
@@ -20,6 +20,9 @@ pub enum Error {
|
||||
/// `InvalidIndex` - Address requested isn't within the range of the disk.
|
||||
#[error("Address requested is not within the range of the disk")]
|
||||
InvalidIndex,
|
||||
/// `RefblockUnaligned` - Refcount block offset is not cluster aligned.
|
||||
#[error("Refcount block offset {0:#x} is not cluster aligned")]
|
||||
RefblockUnaligned(u64),
|
||||
/// `NeedCluster` - Handle this error by reading the cluster and calling the function again.
|
||||
#[error("Cluster with addr={0} needs to be read")]
|
||||
NeedCluster(u64),
|
||||
@@ -29,6 +32,13 @@ pub enum Error {
|
||||
/// `ReadingRefCounts` - Error reading the file into the refcount cache.
|
||||
#[error("Failed to read the file into the refcount cache")]
|
||||
ReadingRefCounts(#[source] io::Error),
|
||||
/// `RefcountOverflow` - Refcount value exceeds maximum for the refcount width.
|
||||
#[error("Refcount value {value} exceeds {refcount_bits}-bit max ({max})")]
|
||||
RefcountOverflow {
|
||||
value: u64,
|
||||
max: u64,
|
||||
refcount_bits: u64,
|
||||
},
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -38,16 +48,19 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub struct RefCount {
|
||||
ref_table: VecCache<u64>,
|
||||
refcount_table_offset: u64,
|
||||
refblock_cache: CacheMap<VecCache<u16>>,
|
||||
refblock_cache: CacheMap<VecCache<u64>>,
|
||||
refcount_block_entries: u64, // number of refcounts in a cluster.
|
||||
cluster_size: u64,
|
||||
max_valid_cluster_offset: u64,
|
||||
max_refcount: u64, // maximum refcount value for this image's refcount_order
|
||||
refcount_bits: u64, // number of bits per refcount entry
|
||||
}
|
||||
|
||||
impl RefCount {
|
||||
/// Creates a `RefCount` from `file`, reading the refcount table from `refcount_table_offset`.
|
||||
/// `refcount_table_entries` specifies the number of refcount blocks used by this image.
|
||||
/// `refcount_block_entries` indicates the number of refcounts in each refcount block.
|
||||
/// `refcount_bits` is the number of bits per refcount (1, 2, 4, 8, 16, 32, or 64).
|
||||
/// Each refcount table entry points to a refcount block.
|
||||
pub fn new(
|
||||
raw_file: &mut QcowRawFile,
|
||||
@@ -55,6 +68,7 @@ impl RefCount {
|
||||
refcount_table_entries: u64,
|
||||
refcount_block_entries: u64,
|
||||
cluster_size: u64,
|
||||
refcount_bits: u64,
|
||||
) -> io::Result<RefCount> {
|
||||
let ref_table = VecCache::from_vec(raw_file.read_pointer_table(
|
||||
refcount_table_offset,
|
||||
@@ -63,6 +77,11 @@ impl RefCount {
|
||||
)?);
|
||||
let max_valid_cluster_index = (ref_table.len() as u64) * refcount_block_entries - 1;
|
||||
let max_valid_cluster_offset = max_valid_cluster_index * cluster_size;
|
||||
let max_refcount = if refcount_bits >= 64 {
|
||||
u64::MAX
|
||||
} else {
|
||||
(1u64 << refcount_bits) - 1
|
||||
};
|
||||
Ok(RefCount {
|
||||
ref_table,
|
||||
refcount_table_offset,
|
||||
@@ -70,6 +89,8 @@ impl RefCount {
|
||||
refcount_block_entries,
|
||||
cluster_size,
|
||||
max_valid_cluster_offset,
|
||||
max_refcount,
|
||||
refcount_bits,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -92,9 +113,17 @@ impl RefCount {
|
||||
&mut self,
|
||||
raw_file: &mut QcowRawFile,
|
||||
cluster_address: u64,
|
||||
refcount: u16,
|
||||
mut new_cluster: Option<(u64, VecCache<u16>)>,
|
||||
refcount: u64,
|
||||
mut new_cluster: Option<(u64, VecCache<u64>)>,
|
||||
) -> Result<Option<u64>> {
|
||||
if refcount > self.max_refcount {
|
||||
return Err(Error::RefcountOverflow {
|
||||
value: refcount,
|
||||
max: self.max_refcount,
|
||||
refcount_bits: self.refcount_bits,
|
||||
});
|
||||
}
|
||||
|
||||
let (table_index, block_index) = self.get_refcount_index(cluster_address);
|
||||
|
||||
let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
|
||||
@@ -170,12 +199,15 @@ impl RefCount {
|
||||
&mut self,
|
||||
raw_file: &mut QcowRawFile,
|
||||
address: u64,
|
||||
) -> Result<u16> {
|
||||
) -> Result<u64> {
|
||||
let (table_index, block_index) = self.get_refcount_index(address);
|
||||
let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
|
||||
if block_addr_disk == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
if block_addr_disk & (self.cluster_size - 1) != 0 {
|
||||
return Err(Error::RefblockUnaligned(block_addr_disk));
|
||||
}
|
||||
if !self.refblock_cache.contains_key(table_index) {
|
||||
let table = VecCache::from_vec(
|
||||
raw_file
|
||||
@@ -202,7 +234,7 @@ impl RefCount {
|
||||
&mut self,
|
||||
raw_file: &mut QcowRawFile,
|
||||
table_index: usize,
|
||||
) -> Result<Option<&[u16]>> {
|
||||
) -> Result<Option<&[u64]>> {
|
||||
let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
|
||||
if block_addr_disk == 0 {
|
||||
return Ok(None);
|
||||
|
||||
@@ -62,6 +62,21 @@ impl<T: 'static + Copy + Default> VecCache<T> {
|
||||
pub fn len(&self) -> usize {
|
||||
self.vec.len()
|
||||
}
|
||||
|
||||
/// Extends the cache capacity to `new_len` elements.
|
||||
///
|
||||
/// No-op if `new_len <= self.len()`. Allocates a new buffer, copies
|
||||
/// existing data, and fills new elements with default values.
|
||||
/// Marks the cache as dirty.
|
||||
pub fn extend(&mut self, new_len: usize) {
|
||||
if new_len <= self.vec.len() {
|
||||
return;
|
||||
}
|
||||
let mut new_vec = vec![Default::default(); new_len];
|
||||
new_vec[..self.vec.len()].copy_from_slice(&self.vec);
|
||||
self.vec = new_vec.into_boxed_slice();
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + Copy + Default> Cacheable for VecCache<T> {
|
||||
|
||||
@@ -4,25 +4,45 @@
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::io::{self, Seek, SeekFrom};
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::write_zeroes::PunchHole;
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
|
||||
use crate::qcow::{Error as QcowError, MAX_NESTING_DEPTH, QcowFile, RawFile, Result as QcowResult};
|
||||
use crate::{AsyncAdaptor, BlockBackend};
|
||||
|
||||
pub struct QcowDiskSync {
|
||||
qcow_file: QcowFile,
|
||||
// FIXME: The Mutex serializes all QCOW2 I/O operations across queues, which
|
||||
// is necessary for correctness but eliminates any parallelism benefit from
|
||||
// multiqueue. QcowFile has internal mutable state (L2 cache, refcounts, file
|
||||
// position) that is not safe to share across threads via Clone.
|
||||
//
|
||||
// A proper fix would require restructuring QcowFile to separate metadata
|
||||
// operations (which need synchronization) from data I/O (which could be
|
||||
// parallelized with per queue file descriptors). See #7560 for details.
|
||||
qcow_file: Arc<Mutex<QcowFile>>,
|
||||
}
|
||||
|
||||
impl QcowDiskSync {
|
||||
pub fn new(file: File, direct_io: bool) -> QcowResult<Self> {
|
||||
pub fn new(file: File, direct_io: bool, backing_files: bool, sparse: bool) -> QcowResult<Self> {
|
||||
let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 };
|
||||
let qcow_file = QcowFile::from_with_nesting_depth(
|
||||
RawFile::new(file, direct_io),
|
||||
max_nesting_depth,
|
||||
sparse,
|
||||
)
|
||||
.map_err(|e| match e {
|
||||
QcowError::MaxNestingDepthExceeded if !backing_files => QcowError::BackingFilesDisabled,
|
||||
other => other,
|
||||
})?;
|
||||
Ok(QcowDiskSync {
|
||||
qcow_file: QcowFile::from(RawFile::new(file, direct_io))?,
|
||||
qcow_file: Arc::new(Mutex::new(qcow_file)),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -30,12 +50,14 @@ impl QcowDiskSync {
|
||||
impl DiskFile for QcowDiskSync {
|
||||
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
||||
self.qcow_file
|
||||
.lock()
|
||||
.unwrap()
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(DiskFileError::Size)
|
||||
}
|
||||
|
||||
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
||||
self.qcow_file.physical_size().map_err(|e| {
|
||||
self.qcow_file.lock().unwrap().physical_size().map_err(|e| {
|
||||
let io_inner = match e {
|
||||
crate::Error::GetFileMetadata(e) => e,
|
||||
_ => unreachable!(),
|
||||
@@ -45,22 +67,38 @@ impl DiskFile for QcowDiskSync {
|
||||
}
|
||||
|
||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(QcowSync::new(self.qcow_file.clone())) as Box<dyn AsyncIo>)
|
||||
Ok(Box::new(QcowSync::new(Arc::clone(&self.qcow_file))) as Box<dyn AsyncIo>)
|
||||
}
|
||||
|
||||
fn resize(&mut self, size: u64) -> DiskFileResult<()> {
|
||||
self.qcow_file
|
||||
.lock()
|
||||
.unwrap()
|
||||
.resize(size)
|
||||
.map_err(|e| DiskFileError::ResizeError(io::Error::other(e)))
|
||||
}
|
||||
|
||||
fn supports_sparse_operations(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_zero_flag(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||
BorrowedDiskFd::new(self.qcow_file.as_raw_fd())
|
||||
BorrowedDiskFd::new(self.qcow_file.lock().unwrap().as_raw_fd())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QcowSync {
|
||||
qcow_file: QcowFile,
|
||||
qcow_file: Arc<Mutex<QcowFile>>,
|
||||
eventfd: EventFd,
|
||||
completion_list: VecDeque<(u64, i32)>,
|
||||
}
|
||||
|
||||
impl QcowSync {
|
||||
pub fn new(qcow_file: QcowFile) -> Self {
|
||||
pub fn new(qcow_file: Arc<Mutex<QcowFile>>) -> Self {
|
||||
QcowSync {
|
||||
qcow_file,
|
||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
||||
@@ -83,7 +121,7 @@ impl AsyncIo for QcowSync {
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
self.qcow_file.read_vectored_sync(
|
||||
self.qcow_file.lock().unwrap().read_vectored_sync(
|
||||
offset,
|
||||
iovecs,
|
||||
user_data,
|
||||
@@ -98,7 +136,7 @@ impl AsyncIo for QcowSync {
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
self.qcow_file.write_vectored_sync(
|
||||
self.qcow_file.lock().unwrap().write_vectored_sync(
|
||||
offset,
|
||||
iovecs,
|
||||
user_data,
|
||||
@@ -108,11 +146,359 @@ impl AsyncIo for QcowSync {
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
self.qcow_file
|
||||
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
|
||||
self.qcow_file.lock().unwrap().fsync_sync(
|
||||
user_data,
|
||||
&self.eventfd,
|
||||
&mut self.completion_list,
|
||||
)
|
||||
}
|
||||
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||
self.completion_list.pop_front()
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||
// For QCOW2, punch_hole calls deallocate_cluster
|
||||
let result = self
|
||||
.qcow_file
|
||||
.lock()
|
||||
.unwrap()
|
||||
.punch_hole(offset, length)
|
||||
.map(|_| 0i32)
|
||||
.map_err(AsyncIoError::PunchHole);
|
||||
|
||||
match result {
|
||||
Ok(res) => {
|
||||
self.completion_list.push_back((user_data, res));
|
||||
self.eventfd.write(1).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
// CRITICAL: Always signal completion even on error to avoid hangs
|
||||
let errno = if let AsyncIoError::PunchHole(io_err) = &e {
|
||||
let err = io_err.raw_os_error().unwrap_or(libc::EIO);
|
||||
-err
|
||||
} else {
|
||||
-libc::EIO
|
||||
};
|
||||
self.completion_list.push_back((user_data, errno));
|
||||
self.eventfd.write(1).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||
// For QCOW2, write_zeroes is implemented by deallocating clusters via punch_hole.
|
||||
// This is more efficient than writing actual zeros and reduces disk usage.
|
||||
// Unallocated clusters inherently read as zero in the QCOW2 format.
|
||||
let result = self
|
||||
.qcow_file
|
||||
.lock()
|
||||
.unwrap()
|
||||
.punch_hole(offset, length)
|
||||
.map(|_| 0i32)
|
||||
.map_err(AsyncIoError::WriteZeroes);
|
||||
|
||||
match result {
|
||||
Ok(res) => {
|
||||
self.completion_list.push_back((user_data, res));
|
||||
self.eventfd.write(1).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
// Always signal completion even on error to avoid hangs
|
||||
let errno = if let AsyncIoError::WriteZeroes(io_err) = &e {
|
||||
let err = io_err.raw_os_error().unwrap_or(libc::EIO);
|
||||
-err
|
||||
} else {
|
||||
-libc::EIO
|
||||
};
|
||||
self.completion_list.push_back((user_data, errno));
|
||||
self.eventfd.write(1).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests {
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use vmm_sys_util::tempfile::TempFile;
|
||||
|
||||
use super::*;
|
||||
use crate::qcow::{QcowFile, QcowHeader, RawFile};
|
||||
|
||||
#[test]
|
||||
fn test_qcow_async_punch_hole_completion() {
|
||||
// Create a QCOW2 image with valid header
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let raw_file = RawFile::new(temp_file.into_file(), false);
|
||||
let file_size = 1024 * 1024 * 100; // 100MB
|
||||
let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap();
|
||||
|
||||
// Write some data
|
||||
let data = vec![0xDD; 128 * 1024]; // 128KB
|
||||
let offset = 0;
|
||||
qcow_file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
qcow_file.write_all(&data).unwrap();
|
||||
qcow_file.flush().unwrap();
|
||||
|
||||
// Create async wrapper
|
||||
let qcow_file = Arc::new(Mutex::new(qcow_file));
|
||||
let mut async_qcow = QcowSync::new(qcow_file.clone());
|
||||
|
||||
// Punch hole
|
||||
async_qcow
|
||||
.punch_hole(offset, data.len() as u64, 100)
|
||||
.unwrap();
|
||||
|
||||
// Verify completion event was generated
|
||||
let (user_data, result) = async_qcow.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 100);
|
||||
assert_eq!(result, 0, "punch_hole should succeed");
|
||||
|
||||
// Verify data reads as zeros
|
||||
let mut read_buf = vec![0; data.len()];
|
||||
qcow_file
|
||||
.lock()
|
||||
.unwrap()
|
||||
.seek(SeekFrom::Start(offset))
|
||||
.unwrap();
|
||||
qcow_file.lock().unwrap().read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0),
|
||||
"Punched hole should read as zeros"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qcow_async_write_zeroes_completion() {
|
||||
// Create a QCOW2 image with valid header
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let raw_file = RawFile::new(temp_file.into_file(), false);
|
||||
let file_size = 1024 * 1024 * 100; // 100MB
|
||||
let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap();
|
||||
|
||||
// Write some data
|
||||
let data = vec![0xEE; 256 * 1024]; // 256KB
|
||||
let offset = 64 * 1024; // Start at 64KB offset
|
||||
qcow_file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
qcow_file.write_all(&data).unwrap();
|
||||
qcow_file.flush().unwrap();
|
||||
|
||||
// Create async wrapper
|
||||
let qcow_file = Arc::new(Mutex::new(qcow_file));
|
||||
let mut async_qcow = QcowSync::new(qcow_file.clone());
|
||||
|
||||
// Write zeros
|
||||
async_qcow
|
||||
.write_zeroes(offset, data.len() as u64, 200)
|
||||
.unwrap();
|
||||
|
||||
// Verify completion event was generated
|
||||
let (user_data, result) = async_qcow.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 200);
|
||||
assert_eq!(result, 0, "write_zeroes should succeed");
|
||||
|
||||
// Verify data reads as zeros
|
||||
let mut read_buf = vec![0; data.len()];
|
||||
qcow_file
|
||||
.lock()
|
||||
.unwrap()
|
||||
.seek(SeekFrom::Start(offset))
|
||||
.unwrap();
|
||||
qcow_file.lock().unwrap().read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0),
|
||||
"Zeroed region should read as zeros"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qcow_async_multiple_operations() {
|
||||
// Create a QCOW2 image with valid header
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let raw_file = RawFile::new(temp_file.into_file(), false);
|
||||
let file_size = 1024 * 1024 * 100; // 100MB
|
||||
let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap();
|
||||
|
||||
// Write data at multiple offsets
|
||||
let data = vec![0xFF; 64 * 1024]; // 64KB chunks
|
||||
for i in 0..4 {
|
||||
let offset = i * 128 * 1024; // 128KB spacing
|
||||
qcow_file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
qcow_file.write_all(&data).unwrap();
|
||||
}
|
||||
qcow_file.flush().unwrap();
|
||||
|
||||
// Create async wrapper
|
||||
let qcow_file = Arc::new(Mutex::new(qcow_file));
|
||||
let mut async_qcow = QcowSync::new(qcow_file.clone());
|
||||
|
||||
// Queue multiple punch_hole operations
|
||||
async_qcow.punch_hole(0, 64 * 1024, 1).unwrap();
|
||||
async_qcow.punch_hole(128 * 1024, 64 * 1024, 2).unwrap();
|
||||
async_qcow.punch_hole(256 * 1024, 64 * 1024, 3).unwrap();
|
||||
|
||||
// Verify all completions
|
||||
let (user_data, result) = async_qcow.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 1);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
let (user_data, result) = async_qcow.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 2);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
let (user_data, result) = async_qcow.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 3);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
// Verify no more completions
|
||||
assert!(async_qcow.next_completed_request().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qcow_punch_hole_with_shared_instance() {
|
||||
// This test verifies that with Arc<Mutex<>>, multiple async I/O operations
|
||||
// share the same QcowFile instance and see each other's changes.
|
||||
|
||||
// Create a QCOW2 image
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let raw_file = RawFile::new(temp_file.into_file(), false);
|
||||
let file_size = 1024 * 1024 * 100; // 100MB
|
||||
let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap();
|
||||
|
||||
// Write some data at offset 0
|
||||
let data = vec![0xAB; 128 * 1024]; // 128KB of 0xAB pattern
|
||||
let offset = 0;
|
||||
qcow_file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
qcow_file.write_all(&data).unwrap();
|
||||
qcow_file.flush().unwrap();
|
||||
|
||||
let qcow_shared = Arc::new(Mutex::new(qcow_file));
|
||||
|
||||
// First async I/O: punch hole
|
||||
let mut async_qcow1 = QcowSync::new(qcow_shared.clone());
|
||||
async_qcow1
|
||||
.punch_hole(offset, data.len() as u64, 100)
|
||||
.unwrap();
|
||||
|
||||
// Verify punch_hole completed
|
||||
let (user_data, result) = async_qcow1.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 100);
|
||||
assert_eq!(result, 0, "punch_hole should succeed");
|
||||
|
||||
// Second async I/O: read from same shared instance
|
||||
// This should see the deallocated cluster because they share the same QcowFile
|
||||
let mut read_buf = vec![0xFF; data.len()];
|
||||
qcow_shared
|
||||
.lock()
|
||||
.unwrap()
|
||||
.seek(SeekFrom::Start(offset))
|
||||
.unwrap();
|
||||
qcow_shared
|
||||
.lock()
|
||||
.unwrap()
|
||||
.read_exact(&mut read_buf)
|
||||
.unwrap();
|
||||
|
||||
// The read should return zeros because the cluster was deallocated
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0),
|
||||
"After punch_hole, shared QcowFile instance should read zeros from deallocated cluster"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qcow_disk_sync_punch_hole_with_new_async_io() {
|
||||
// This test simulates the EXACT real usage pattern: QcowDiskSync.new_async_io()
|
||||
// creates a new QcowSync with a cloned QcowFile for each I/O operation.
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use crate::async_io::DiskFile;
|
||||
|
||||
// Create a QCOW2 image
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let file_size = 1024 * 1024 * 100; // 100MB
|
||||
|
||||
{
|
||||
let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false);
|
||||
let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap();
|
||||
|
||||
// Write data at offset 1MB - use single cluster (64KB) to simplify test
|
||||
let data = vec![0xCD; 64 * 1024]; // 64KB (one cluster)
|
||||
let offset = 1024 * 1024u64;
|
||||
qcow_file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
qcow_file.write_all(&data).unwrap();
|
||||
qcow_file.flush().unwrap();
|
||||
}
|
||||
|
||||
// Open with QcowDiskSync (like real code does)
|
||||
let disk =
|
||||
QcowDiskSync::new(temp_file.as_file().try_clone().unwrap(), false, true, true).unwrap();
|
||||
|
||||
// First async I/O: punch hole (simulates DISCARD command)
|
||||
let mut async_io1 = disk.new_async_io(1).unwrap();
|
||||
let offset = 1024 * 1024u64;
|
||||
let length = 64 * 1024u64; // Single cluster
|
||||
async_io1.punch_hole(offset, length, 1).unwrap();
|
||||
let (user_data, result) = async_io1.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 1);
|
||||
assert_eq!(result, 0, "punch_hole should succeed");
|
||||
drop(async_io1);
|
||||
|
||||
// Second async I/O: read from the same location (simulates READ command)
|
||||
let mut async_io2 = disk.new_async_io(1).unwrap();
|
||||
let mut read_buf = vec![0xFF; length as usize];
|
||||
let iovec = libc::iovec {
|
||||
iov_base: read_buf.as_mut_ptr() as *mut libc::c_void,
|
||||
iov_len: read_buf.len(),
|
||||
};
|
||||
|
||||
// These assertions are critical to prevent compiler optimization bugs
|
||||
// that can reorder operations. Without them, the test can fail even
|
||||
// though the QCOW2 implementation is correct.
|
||||
assert_eq!(iovec.iov_base as *const u8, read_buf.as_ptr());
|
||||
assert_eq!(iovec.iov_len, read_buf.len());
|
||||
|
||||
async_io2
|
||||
.read_vectored(offset as libc::off_t, &[iovec], 2)
|
||||
.unwrap();
|
||||
|
||||
let (user_data, result) = async_io2.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 2);
|
||||
assert_eq!(
|
||||
result as usize, length as usize,
|
||||
"read should complete successfully"
|
||||
);
|
||||
|
||||
// Verify the data is all zeros
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0),
|
||||
"After punch_hole via new_async_io, read should return zeros"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backing_files_disabled_error() {
|
||||
let header =
|
||||
QcowHeader::create_for_size_and_path(3, 0x10_0000, Some("/path/to/backing/file"))
|
||||
.expect("Failed to create header.");
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let mut raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false);
|
||||
header
|
||||
.write_to(&mut raw_file)
|
||||
.expect("Failed to write header.");
|
||||
|
||||
let file = temp_file.into_file();
|
||||
match QcowDiskSync::new(file, false, false, true) {
|
||||
Err(QcowError::BackingFilesDisabled) => {}
|
||||
Err(other) => panic!("Expected BackingFilesDisabled, got: {other:?}"),
|
||||
Ok(_) => panic!("Expected BackingFilesDisabled error, but succeeded"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use vmm_sys_util::eventfd::EventFd;
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::{BatchRequest, DiskTopology, RequestType};
|
||||
use crate::{BatchRequest, DiskTopology, RequestType, probe_sparse_support};
|
||||
|
||||
pub struct RawFileDisk {
|
||||
file: File,
|
||||
@@ -59,6 +59,10 @@ impl DiskFile for RawFileDisk {
|
||||
self.file.set_len(size).map_err(DiskFileError::ResizeError)
|
||||
}
|
||||
|
||||
fn supports_sparse_operations(&self) -> bool {
|
||||
probe_sparse_support(&self.file)
|
||||
}
|
||||
|
||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||
BorrowedDiskFd::new(self.file.as_raw_fd())
|
||||
}
|
||||
@@ -253,4 +257,58 @@ impl AsyncIo for RawFileAsync {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
const FALLOC_FL_PUNCH_HOLE: i32 = 0x02;
|
||||
const FALLOC_FL_KEEP_SIZE: i32 = 0x01;
|
||||
let mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
|
||||
|
||||
// SAFETY: The file descriptor is known to be valid.
|
||||
unsafe {
|
||||
sq.push(
|
||||
&opcode::Fallocate::new(types::Fd(self.fd), length)
|
||||
.offset(offset)
|
||||
.mode(mode)
|
||||
.build()
|
||||
.user_data(user_data),
|
||||
)
|
||||
.map_err(|e| {
|
||||
AsyncIoError::PunchHole(Error::other(format!("Submission queue is full: {e:?}")))
|
||||
})?;
|
||||
};
|
||||
|
||||
sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::PunchHole)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
const FALLOC_FL_ZERO_RANGE: i32 = 0x10;
|
||||
const FALLOC_FL_KEEP_SIZE: i32 = 0x01;
|
||||
let mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE;
|
||||
|
||||
// SAFETY: The file descriptor is known to be valid.
|
||||
unsafe {
|
||||
sq.push(
|
||||
&opcode::Fallocate::new(types::Fd(self.fd), length)
|
||||
.offset(offset)
|
||||
.mode(mode)
|
||||
.build()
|
||||
.user_data(user_data),
|
||||
)
|
||||
.map_err(|e| {
|
||||
AsyncIoError::WriteZeroes(Error::other(format!("Submission queue is full: {e:?}")))
|
||||
})?;
|
||||
};
|
||||
|
||||
sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::WriteZeroes)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ use log::warn;
|
||||
use vmm_sys_util::aio;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::DiskTopology;
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::{DiskTopology, probe_sparse_support};
|
||||
|
||||
pub struct RawFileDiskAio {
|
||||
file: File,
|
||||
@@ -58,6 +58,10 @@ impl DiskFile for RawFileDiskAio {
|
||||
}
|
||||
}
|
||||
|
||||
fn supports_sparse_operations(&self) -> bool {
|
||||
probe_sparse_support(&self.file)
|
||||
}
|
||||
|
||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||
BorrowedDiskFd::new(self.file.as_raw_fd())
|
||||
}
|
||||
@@ -161,4 +165,16 @@ impl AsyncIo for RawFileAsyncAio {
|
||||
Some((events[0].data, events[0].res as i32))
|
||||
}
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::PunchHole(std::io::Error::other(
|
||||
"punch_hole not supported with AIO backend",
|
||||
)))
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::WriteZeroes(std::io::Error::other(
|
||||
"write_zeroes not supported with AIO backend",
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use log::warn;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::DiskTopology;
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::{DiskTopology, probe_sparse_support};
|
||||
|
||||
pub struct RawFileDiskSync {
|
||||
file: File,
|
||||
@@ -52,6 +52,10 @@ impl DiskFile for RawFileDiskSync {
|
||||
}
|
||||
}
|
||||
|
||||
fn supports_sparse_operations(&self) -> bool {
|
||||
probe_sparse_support(&self.file)
|
||||
}
|
||||
|
||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||
BorrowedDiskFd::new(self.file.as_raw_fd())
|
||||
}
|
||||
@@ -146,4 +150,226 @@ impl AsyncIo for RawFileSync {
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||
self.completion_list.pop_front()
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||
const FALLOC_FL_PUNCH_HOLE: i32 = 0x02;
|
||||
const FALLOC_FL_KEEP_SIZE: i32 = 0x01;
|
||||
let mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
|
||||
|
||||
// SAFETY: FFI call with valid arguments
|
||||
let result = unsafe {
|
||||
libc::fallocate(
|
||||
self.fd as libc::c_int,
|
||||
mode,
|
||||
offset as libc::off_t,
|
||||
length as libc::off_t,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::PunchHole(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
self.completion_list.push_back((user_data, result));
|
||||
self.eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||
const FALLOC_FL_ZERO_RANGE: i32 = 0x10;
|
||||
const FALLOC_FL_KEEP_SIZE: i32 = 0x01;
|
||||
let mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE;
|
||||
|
||||
// SAFETY: FFI call with valid arguments
|
||||
let result = unsafe {
|
||||
libc::fallocate(
|
||||
self.fd as libc::c_int,
|
||||
mode,
|
||||
offset as libc::off_t,
|
||||
length as libc::off_t,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::WriteZeroes(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
self.completion_list.push_back((user_data, result));
|
||||
self.eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests {
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use vmm_sys_util::tempfile::TempFile;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_punch_hole() {
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let mut file = temp_file.into_file();
|
||||
|
||||
// Write 4MB of data
|
||||
let data = vec![0xAA; 4 * 1024 * 1024];
|
||||
file.write_all(&data).unwrap();
|
||||
file.sync_all().unwrap();
|
||||
|
||||
// Create async IO instance
|
||||
let mut async_io = RawFileSync::new(file.as_raw_fd());
|
||||
|
||||
// Punch hole in the middle (1MB at offset 1MB)
|
||||
let offset = 1024 * 1024;
|
||||
let length = 1024 * 1024;
|
||||
async_io.punch_hole(offset, length, 1).unwrap();
|
||||
|
||||
// Check completion
|
||||
let (user_data, result) = async_io.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 1);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
// Verify the hole reads as zeros
|
||||
file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
let mut read_buf = vec![0; length as usize];
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0),
|
||||
"Punched hole should read as zeros"
|
||||
);
|
||||
|
||||
// Verify data before hole is intact
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
let mut read_buf = vec![0; 1024];
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0xAA),
|
||||
"Data before hole should be intact"
|
||||
);
|
||||
|
||||
// Verify data after hole is intact
|
||||
file.seek(SeekFrom::Start(offset + length)).unwrap();
|
||||
let mut read_buf = vec![0; 1024];
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0xAA),
|
||||
"Data after hole should be intact"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_zeroes() {
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let mut file = temp_file.into_file();
|
||||
|
||||
// Write 4MB of data
|
||||
let data = vec![0xBB; 4 * 1024 * 1024];
|
||||
file.write_all(&data).unwrap();
|
||||
file.sync_all().unwrap();
|
||||
|
||||
// Create async IO instance
|
||||
let mut async_io = RawFileSync::new(file.as_raw_fd());
|
||||
|
||||
// Write zeros in the middle (512KB at offset 2MB)
|
||||
let offset = 2 * 1024 * 1024;
|
||||
let length = 512 * 1024;
|
||||
let write_zeroes_result = async_io.write_zeroes(offset, length, 2);
|
||||
|
||||
// FALLOC_FL_ZERO_RANGE might not be supported on all filesystems (e.g., tmpfs)
|
||||
// If it fails with ENOTSUP, skip the test
|
||||
if let Err(AsyncIoError::WriteZeroes(ref e)) = write_zeroes_result
|
||||
&& (e.raw_os_error() == Some(libc::EOPNOTSUPP)
|
||||
|| e.raw_os_error() == Some(libc::ENOTSUP))
|
||||
{
|
||||
eprintln!(
|
||||
"Skipping test_write_zeroes: filesystem doesn't support FALLOC_FL_ZERO_RANGE"
|
||||
);
|
||||
return;
|
||||
}
|
||||
write_zeroes_result.unwrap();
|
||||
|
||||
// Check completion
|
||||
let (user_data, result) = async_io.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 2);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
// Verify the zeroed region reads as zeros
|
||||
file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
let mut read_buf = vec![0; length as usize];
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0),
|
||||
"Zeroed region should read as zeros"
|
||||
);
|
||||
|
||||
// Verify data before zeroed region is intact
|
||||
file.seek(SeekFrom::Start(offset - 1024)).unwrap();
|
||||
let mut read_buf = vec![0; 1024];
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0xBB),
|
||||
"Data before zeroed region should be intact"
|
||||
);
|
||||
|
||||
// Verify data after zeroed region is intact
|
||||
file.seek(SeekFrom::Start(offset + length)).unwrap();
|
||||
let mut read_buf = vec![0; 1024];
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0xBB),
|
||||
"Data after zeroed region should be intact"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_punch_hole_multiple_operations() {
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let mut file = temp_file.into_file();
|
||||
|
||||
// Write 8MB of data
|
||||
let data = vec![0xCC; 8 * 1024 * 1024];
|
||||
file.write_all(&data).unwrap();
|
||||
file.sync_all().unwrap();
|
||||
|
||||
// Create async IO instance
|
||||
let mut async_io = RawFileSync::new(file.as_raw_fd());
|
||||
|
||||
// Punch multiple holes
|
||||
async_io.punch_hole(1024 * 1024, 512 * 1024, 10).unwrap();
|
||||
async_io
|
||||
.punch_hole(3 * 1024 * 1024, 512 * 1024, 11)
|
||||
.unwrap();
|
||||
async_io
|
||||
.punch_hole(5 * 1024 * 1024, 512 * 1024, 12)
|
||||
.unwrap();
|
||||
|
||||
// Check all completions
|
||||
let (user_data, result) = async_io.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 10);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
let (user_data, result) = async_io.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 11);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
let (user_data, result) = async_io.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 12);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
// Verify all holes read as zeros
|
||||
file.seek(SeekFrom::Start(1024 * 1024)).unwrap();
|
||||
let mut read_buf = vec![0; 512 * 1024];
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(read_buf.iter().all(|&b| b == 0));
|
||||
|
||||
file.seek(SeekFrom::Start(3 * 1024 * 1024)).unwrap();
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(read_buf.iter().all(|&b| b == 0));
|
||||
|
||||
file.seek(SeekFrom::Start(5 * 1024 * 1024)).unwrap();
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(read_buf.iter().all(|&b| b == 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::os::fd::AsRawFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::vhdx::{Result as VhdxResult, Vhdx};
|
||||
use crate::{AsyncAdaptor, BlockBackend, Error};
|
||||
@@ -114,4 +114,16 @@ impl AsyncIo for VhdxSync {
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||
self.completion_list.pop_front()
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::PunchHole(std::io::Error::other(
|
||||
"punch_hole not supported for VHDX",
|
||||
)))
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::WriteZeroes(std::io::Error::other(
|
||||
"write_zeroes not supported for VHDX",
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ edition = "2024"
|
||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||
license = "Apache-2.0 AND BSD-3-Clause"
|
||||
name = "cloud-hypervisor"
|
||||
version = "50.0.0"
|
||||
version = "51.0.0"
|
||||
# Minimum buildable version:
|
||||
# Keep in sync with version in .github/workflows/build.yaml
|
||||
# Policy on MSRV (see #4318):
|
||||
@@ -38,9 +38,10 @@ tracer = { path = "../tracer" }
|
||||
vm-memory = { workspace = true }
|
||||
vmm = { path = "../vmm" }
|
||||
vmm-sys-util = { workspace = true }
|
||||
zbus = { version = "5.7.1", optional = true }
|
||||
zbus = { version = "5.13.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
block = { path = "../block" }
|
||||
dirs = { workspace = true }
|
||||
net_util = { path = "../net_util" }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -218,18 +218,6 @@ fn get_cli_options_sorted(
|
||||
)
|
||||
.default_value(default_vcpus)
|
||||
.group("vm-config"),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
Arg::new("debug-console")
|
||||
.long("debug-console")
|
||||
.help("Debug console: off|pty|tty|file=</path/to/a/file>,iobase=<port in hex>")
|
||||
.default_value("off,iobase=0xe9")
|
||||
.group("vm-config"),
|
||||
#[cfg(feature = "dbus_api")]
|
||||
Arg::new("dbus-service-name")
|
||||
.long("dbus-service-name")
|
||||
.help("Well known name of the device")
|
||||
.num_args(1)
|
||||
.group("vmm-config"),
|
||||
#[cfg(feature = "dbus_api")]
|
||||
Arg::new("dbus-object-path")
|
||||
.long("dbus-object-path")
|
||||
@@ -237,12 +225,24 @@ fn get_cli_options_sorted(
|
||||
.num_args(1)
|
||||
.group("vmm-config"),
|
||||
#[cfg(feature = "dbus_api")]
|
||||
Arg::new("dbus-service-name")
|
||||
.long("dbus-service-name")
|
||||
.help("Well known name of the device")
|
||||
.num_args(1)
|
||||
.group("vmm-config"),
|
||||
#[cfg(feature = "dbus_api")]
|
||||
Arg::new("dbus-system-bus")
|
||||
.long("dbus-system-bus")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Use the system bus instead of a session bus")
|
||||
.num_args(0)
|
||||
.group("vmm-config"),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
Arg::new("debug-console")
|
||||
.long("debug-console")
|
||||
.help("Debug console: off|pty|tty|file=</path/to/a/file>,iobase=<port in hex>")
|
||||
.default_value("off,iobase=0xe9")
|
||||
.group("vm-config"),
|
||||
Arg::new("device")
|
||||
.long("device")
|
||||
.help(DeviceConfig::SYNTAX)
|
||||
@@ -1199,14 +1199,14 @@ mod unit_tests {
|
||||
"--kernel",
|
||||
"/path/to/kernel",
|
||||
"--disk",
|
||||
"path=/path/to/disk/1",
|
||||
"path=/path/to/disk/1,image_type=raw",
|
||||
"path=/path/to/disk/2",
|
||||
],
|
||||
r#"{
|
||||
"payload": {"kernel": "/path/to/kernel"},
|
||||
"disks": [
|
||||
{"path": "/path/to/disk/1"},
|
||||
{"path": "/path/to/disk/2"}
|
||||
{"path": "/path/to/disk/1", "image_type": "Raw"},
|
||||
{"path": "/path/to/disk/2", "image_type": "Unknown"}
|
||||
]
|
||||
}"#,
|
||||
true,
|
||||
@@ -1217,8 +1217,8 @@ mod unit_tests {
|
||||
"--kernel",
|
||||
"/path/to/kernel",
|
||||
"--disk",
|
||||
"path=/path/to/disk/1",
|
||||
"path=/path/to/disk/2",
|
||||
"path=/path/to/disk/1,image_type=raw",
|
||||
"path=/path/to/disk/2,image_type=qcow2",
|
||||
],
|
||||
r#"{
|
||||
"payload": {"kernel": "/path/to/kernel"},
|
||||
@@ -1280,8 +1280,8 @@ mod unit_tests {
|
||||
r#"{
|
||||
"payload": {"kernel": "/path/to/kernel"},
|
||||
"disks": [
|
||||
{"path": "/path/to/disk/1", "rate_limit_group": "group0"},
|
||||
{"path": "/path/to/disk/2", "rate_limit_group": "group0"}
|
||||
{"path": "/path/to/disk/1", "rate_limit_group": "group0", "image_type": "Unknown"},
|
||||
{"path": "/path/to/disk/2", "rate_limit_group": "group0", "image_type": "Unknown"}
|
||||
],
|
||||
"rate_limit_groups": [
|
||||
{"id": "group0", "rate_limiter_config": {"bandwidth": {"size": 1000, "one_time_burst": 0, "refill_time": 100}}}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,7 +34,7 @@ vm-memory = { workspace = true, features = [
|
||||
] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = { workspace = true }
|
||||
zerocopy = { version = "0.8.31", features = [
|
||||
zerocopy = { version = "0.8.39", features = [
|
||||
"alloc",
|
||||
"derive",
|
||||
], optional = true }
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
### WARNING
|
||||
|
||||
This feature is only currently supported on MSHV.
|
||||
This feature is currently only supported on MSHV.
|
||||
|
||||
AMD Secure Encrypted Virtualization & Secure Nested Paging (SEV-SNP) is an AMD
|
||||
technology designed to add strong memory integrity protection to help prevent
|
||||
@@ -10,13 +10,12 @@ malicious hypervisor-based attacks like data replay, memory-remapping and more
|
||||
in order to create an isolated execution environment. Here are some useful
|
||||
links:
|
||||
|
||||
- [SNP Homepage](https://www.amd.com/content/dam/amd/en/documents/epyc-business-docs/solution-briefs/amd-secure-encrypted-virtualization-solution-brief.pdf):
|
||||
- [SNP Homepage](https://docs.amd.com/v/u/en-US/amd-secure-encrypted-virtualization-solution-brief):
|
||||
more information about SEV-SNP technical aspects, design and specification.
|
||||
|
||||
## Cloud Hypervisor support
|
||||
|
||||
It is required to use a machine which has enabled support for AMD SEV-SNP in
|
||||
the BIOS.
|
||||
A machine with AMD SEV-SNP support which is enabled in the BIOS is required.
|
||||
|
||||
On the Cloud Hypervisor side, all you need is to build the project with the
|
||||
`sev_snp` feature enabled:
|
||||
@@ -26,7 +25,7 @@ cargo build --no-default-features --features "sev_snp"
|
||||
```
|
||||
|
||||
**Note**
|
||||
Please note that `sev_snp` cannot be enabled in conjunction with `tdx` feature flag.
|
||||
Please note that `sev_snp` cannot be enabled in conjunction with the `tdx` feature flag.
|
||||
|
||||
You can run a SEV-SNP VM using the following command:
|
||||
|
||||
@@ -38,4 +37,4 @@ You can run a SEV-SNP VM using the following command:
|
||||
--disk path=ubuntu.img
|
||||
```
|
||||
|
||||
For more information related to Microsoft Hypervisor please see [mshv.md](mshv.md)
|
||||
For more information related to Microsoft Hypervisor, please see [mshv.md](mshv.md)
|
||||
|
||||
27
docs/api.md
27
docs/api.md
@@ -8,14 +8,14 @@
|
||||
- [REST API Examples](#rest-api-examples)
|
||||
- [Create a Virtual Machine](#create-a-virtual-machine)
|
||||
- [Boot a Virtual Machine](#boot-a-virtual-machine)
|
||||
- [Dump a Virtual Machine Information](#dump-a-virtual-machine-information)
|
||||
- [Dump Virtual Machine Information](#dump-virtual-machine-information)
|
||||
- [Reboot a Virtual Machine](#reboot-a-virtual-machine)
|
||||
- [Shut a Virtual Machine Down](#shut-a-virtual-machine-down)
|
||||
- [D-Bus API](#d-bus-api)
|
||||
- [D-Bus API Location and availability](#d-bus-api-location-and-availability)
|
||||
- [D-Bus API Interface](#d-bus-api-interface)
|
||||
- [Command Line Interface](#command-line-interface)
|
||||
- [REST API, D-Bus API and CLI Architectural Relationship](#rest-api-and-cli-architectural-relationship)
|
||||
- [REST API, D-Bus API and CLI Architectural Relationship](#rest-api-d-bus-api-and-cli-architectural-relationship)
|
||||
- [Internal API](#internal-api)
|
||||
- [Goals and Design](#goals-and-design)
|
||||
- [End to End Example](#end-to-end-example)
|
||||
@@ -31,7 +31,7 @@ The Cloud Hypervisor API is made of 2 distinct interfaces:
|
||||
|
||||
1. **The internal API**, based on [rust's Multi-Producer, Single-Consumer (MPSC)](https://doc.rust-lang.org/std/sync/mpsc/)
|
||||
module. This API is used internally by the Cloud Hypervisor threads to
|
||||
communicate between each others.
|
||||
communicate with each other.
|
||||
|
||||
The goal of this document is to describe the Cloud Hypervisor API as a whole,
|
||||
and to outline how the internal and external APIs are architecturally related.
|
||||
@@ -81,7 +81,7 @@ The Cloud Hypervisor API exposes the following actions through its endpoints:
|
||||
| Trigger power button of the VM | `/vm.power-button` | N/A | N/A | The VM is booted |
|
||||
| Pause the VM | `/vm.pause` | N/A | N/A | The VM is booted |
|
||||
| Resume the VM | `/vm.resume` | N/A | N/A | The VM is paused |
|
||||
| Task a snapshot of the VM | `/vm.snapshot` | `/schemas/VmSnapshotConfig` | N/A | The VM is paused |
|
||||
| Take a snapshot of the VM | `/vm.snapshot` | `/schemas/VmSnapshotConfig` | N/A | The VM is paused |
|
||||
| Perform a coredump of the VM* | `/vm.coredump` | `/schemas/VmCoredumpData` | N/A | The VM is paused |
|
||||
| Restore the VM from a snapshot | `/vm.restore` | `/schemas/RestoreConfig` | N/A | The VM is created but not booted |
|
||||
| Add/remove CPUs to/from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted |
|
||||
@@ -155,9 +155,9 @@ Once the VM is created, we can boot it:
|
||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.boot'
|
||||
```
|
||||
|
||||
##### Dump a Virtual Machine Information
|
||||
##### Dump Virtual Machine Information
|
||||
|
||||
We can fetch information about any VM, as soon as it's created:
|
||||
We can fetch information about any VM as soon as it's created:
|
||||
|
||||
```shell
|
||||
#!/usr/bin/env bash
|
||||
@@ -201,7 +201,7 @@ see [D-Bus API Interface](#d-bus-api-interface).
|
||||
#### D-Bus API Location and availability
|
||||
|
||||
This feature is not compiled into Cloud Hypervisor by default. Users who
|
||||
wish to use the D-Bus API, must explicitly enable it with the `dbus_api`
|
||||
wish to use the D-Bus API must explicitly enable it with the `dbus_api`
|
||||
feature flag when compiling Cloud Hypervisor.
|
||||
|
||||
```sh
|
||||
@@ -278,7 +278,7 @@ From the CLI, one can:
|
||||
The REST API, D-Bus API and the CLI all rely on a common, [internal API](#internal-api).
|
||||
|
||||
The CLI options are parsed by the
|
||||
[clap crate](https://docs.rs/clap/4.3.11/clap/) and then translated into
|
||||
[clap crate](https://docs.rs/clap/4.5.53/clap/) and then translated into
|
||||
[internal API](#internal-api) commands.
|
||||
|
||||
The REST API is processed by an HTTP thread using the
|
||||
@@ -288,7 +288,7 @@ crate. As with the CLI, the HTTP requests eventually get translated into
|
||||
|
||||
The D-Bus API is implemented using the [zbus](https://github.com/dbus2/zbus)
|
||||
crate and runs in its own thread. Whenever it needs to call the [internal API](#internal-api),
|
||||
the [blocking](https://github.com/smol-rs/blocking) crate is used perform the call in zbus' async context.
|
||||
the [blocking](https://github.com/smol-rs/blocking) crate is used to perform the call in zbus' async context.
|
||||
|
||||
As a summary, the REST API, the D-Bus API and the CLI are essentially frontends for the
|
||||
[internal API](#internal-api):
|
||||
@@ -321,7 +321,7 @@ As a summary, the REST API, the D-Bus API and the CLI are essentially frontends
|
||||
|
||||
The Cloud Hypervisor internal API, as its name suggests, is used internally
|
||||
by the different Cloud Hypervisor threads (VMM, HTTP, D-Bus, control loop,
|
||||
etc) to send commands and responses to each others.
|
||||
etc) to send commands and responses to each other.
|
||||
|
||||
It is based on [rust's Multi-Producer, Single-Consumer (MPSC)](https://doc.rust-lang.org/std/sync/mpsc/),
|
||||
and the single consumer (a.k.a. the API receiver) is the Cloud Hypervisor
|
||||
@@ -365,9 +365,8 @@ APIs work together, let's look at a complete VM creation flow, from the
|
||||
[REST API](#rest-api) call, to the reply the external user will receive:
|
||||
|
||||
1. A user or operator sends an HTTP request to the Cloud Hypervisor
|
||||
[REST API](#rest-api) in order to creates a virtual machine:
|
||||
```
|
||||
shell
|
||||
[REST API](#rest-api) in order to create a virtual machine:
|
||||
```shell
|
||||
#!/usr/bin/env bash
|
||||
|
||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
||||
@@ -414,7 +413,7 @@ APIs work together, let's look at a complete VM creation flow, from the
|
||||
the `VmCreate` payload, and extracts both the `VmConfig` structure and the
|
||||
[Sender](https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html) from the
|
||||
command payload. It stores the `VmConfig` structure and replies back to the
|
||||
sender ((The HTTP thread):
|
||||
sender (The HTTP thread):
|
||||
```Rust
|
||||
match api_request {
|
||||
ApiRequest::VmCreate(config, sender) => {
|
||||
|
||||
@@ -26,8 +26,8 @@ struct BalloonConfig {
|
||||
|
||||
Size of the balloon device. It is subtracted from the VM's total size. For
|
||||
instance, if creating a VM with 4GiB of RAM, along with a balloon of 1GiB, the
|
||||
guest will be able to use 3GiB of accessible memory. The guest sees all the RAM
|
||||
and unless it is balloon enlightened is entitled to all of it.
|
||||
guest will be able to use 3GiB of accessible memory. The guest sees all the RAM,
|
||||
and unless it is balloon enlightened, it is entitled to all of it.
|
||||
|
||||
This parameter is mandatory.
|
||||
|
||||
@@ -42,7 +42,7 @@ _Example_
|
||||
|
||||
### `deflate_on_oom`
|
||||
|
||||
Allow the guest to deflate the balloon if running Out Of Memory (OOM). Assuming
|
||||
Allow the guest to deflate the balloon when running Out Of Memory (OOM). Assuming
|
||||
the balloon size is greater than 0, this means the guest is allowed to reduce
|
||||
the balloon size all the way down to 0 if this can help recover from the OOM
|
||||
event.
|
||||
|
||||
@@ -24,8 +24,8 @@ Hypervisor. Here, all the steps are based on Ubuntu, for other Linux
|
||||
distributions please replace the package manager and package name.
|
||||
|
||||
```shell
|
||||
# Install basic packages needed. For a package list targeting for more
|
||||
# functionalities for example the test, please see resources/Dockerfile.
|
||||
# Install basic dependencies. For a list of packages required for additional
|
||||
# features (e.g., testing), please refer to resources/Dockerfile.
|
||||
$ sudo apt-get update
|
||||
$ sudo apt install git build-essential m4 bison flex uuid-dev qemu-utils musl-tools
|
||||
# Install rust tool chain
|
||||
|
||||
42
docs/cpu.md
42
docs/cpu.md
@@ -11,8 +11,8 @@ to set vCPUs options for Cloud Hypervisor.
|
||||
|
||||
```rust
|
||||
struct CpusConfig {
|
||||
boot_vcpus: u8,
|
||||
max_vcpus: u8,
|
||||
boot_vcpus: u32,
|
||||
max_vcpus: u32,
|
||||
topology: Option<CpuTopology>,
|
||||
kvm_hyperv: bool,
|
||||
max_phys_bits: u8,
|
||||
@@ -30,12 +30,12 @@ struct CpusConfig {
|
||||
|
||||
Number of vCPUs present at boot time.
|
||||
|
||||
This option allows to define a specific number of vCPUs to be present at the
|
||||
This option allows defining a specific number of vCPUs to be present at the
|
||||
time the VM is started. This option is mandatory when using the `--cpus`
|
||||
parameter. If `--cpus` is not specified, this option takes the default value
|
||||
of `1`, starting the VM with a single vCPU.
|
||||
|
||||
Value is an unsigned integer of 8 bits.
|
||||
Value is an unsigned integer of 32 bits.
|
||||
|
||||
_Example_
|
||||
|
||||
@@ -48,14 +48,14 @@ _Example_
|
||||
Maximum number of vCPUs.
|
||||
|
||||
This option defines the maximum number of vCPUs that can be assigned to the VM.
|
||||
In particular, this option is used when looking for CPU hotplug as it lets the
|
||||
provide an indication about how many vCPUs might be needed later during the
|
||||
runtime of the VM.
|
||||
In particular, this option is used when looking for CPU hotplug as it provides
|
||||
an indication about how many vCPUs might be needed later during the runtime of
|
||||
the VM.
|
||||
For instance, if booting the VM with 2 vCPUs and a maximum of 6 vCPUs, it means
|
||||
up to 4 vCPUs can be added later at runtime by resizing the VM.
|
||||
|
||||
The value must be greater than or equal to the number of boot vCPUs.
|
||||
The value is an unsigned integer of 8 bits.
|
||||
The value is an unsigned integer of 32 bits.
|
||||
|
||||
By default this option takes the value of `boot`, meaning vCPU hotplug is not
|
||||
expected and can't be performed.
|
||||
@@ -73,16 +73,16 @@ Topology of the guest platform.
|
||||
This option gives the user a way to describe the exact topology that should be
|
||||
exposed to the guest. It can be useful to describe to the guest the same
|
||||
topology found on the host as it allows for proper usage of the resources and
|
||||
is a way to achieve better performances.
|
||||
is a way to achieve better performance.
|
||||
|
||||
The topology is described through the following structure:
|
||||
|
||||
```rust
|
||||
struct CpuTopology {
|
||||
threads_per_core: u8,
|
||||
cores_per_die: u8,
|
||||
dies_per_package: u8,
|
||||
packages: u8,
|
||||
threads_per_core: u16,
|
||||
cores_per_die: u16,
|
||||
dies_per_package: u16,
|
||||
packages: u16,
|
||||
}
|
||||
```
|
||||
|
||||
@@ -124,7 +124,7 @@ Maximum size for guest's addressable space.
|
||||
|
||||
This option defines the maximum number of physical bits for all vCPUs, which
|
||||
sets a limit for the size of the guest's addressable space. This is mainly
|
||||
useful for debug purpose.
|
||||
useful for debugging purposes.
|
||||
|
||||
The value is an unsigned integer of 8 bits.
|
||||
|
||||
@@ -141,16 +141,16 @@ Affinity of each vCPU.
|
||||
This option gives the user a way to provide the host CPU set associated with
|
||||
each vCPU. It is useful for achieving CPU pinning, ensuring multiple VMs won't
|
||||
affect the performance of each other. It might also be used in the context of
|
||||
NUMA as it is way of making sure the VM can run on a specific host NUMA node.
|
||||
In general, this option is used to increase the performances of a VM depending
|
||||
NUMA as it is a way of making sure the VM can run on a specific host NUMA node.
|
||||
In general, this option is used to increase the performance of a VM depending
|
||||
on the host platform and the type of workload running in the guest.
|
||||
|
||||
The affinity is described through the following structure:
|
||||
|
||||
```rust
|
||||
struct CpuAffinity {
|
||||
vcpu: u8,
|
||||
host_cpus: Vec<u8>,
|
||||
vcpu: u32,
|
||||
host_cpus: Vec<usize>,
|
||||
}
|
||||
```
|
||||
|
||||
@@ -164,8 +164,8 @@ The outer brackets define the list of vCPUs. And for each vCPU, the inner
|
||||
brackets attached to `@` define the list of host CPUs the vCPU is allowed to
|
||||
run onto.
|
||||
|
||||
Multiple values can be provided to define each list. Each value is an unsigned
|
||||
integer of 8 bits.
|
||||
Multiple values can be provided to define each list. Each value is a
|
||||
platform-native unsigned integer (`usize`).
|
||||
|
||||
For instance, if one needs to run vCPU 0 on host CPUs from 0 to 4, the syntax
|
||||
using `-` will help define a contiguous range with `affinity=0@[0-4]`. The
|
||||
@@ -220,4 +220,4 @@ _Example_
|
||||
|
||||
```
|
||||
--cpus nested=on
|
||||
```
|
||||
```
|
||||
|
||||
@@ -13,7 +13,7 @@ be used simultaneously.
|
||||
|
||||
### `0x80` I/O port
|
||||
|
||||
Whenever the guest write one byte between `0x0` and `0xF` on this particular
|
||||
Whenever the guest writes one byte between `0x0` and `0xF` on this particular
|
||||
I/O port, `cloud-hypervisor` will log and timestamp that event at the `debug`
|
||||
log level.
|
||||
|
||||
@@ -52,7 +52,7 @@ to easily grep for the tracing logs (e.g.
|
||||
|
||||
```
|
||||
./target/debug/cloud-hypervisor \
|
||||
--kernel ~/rust-hypervisor-firmware/target/target/release/hypervisor-fw \
|
||||
--kernel ~/rust-hypervisor-firmware/target/release/hypervisor-fw \
|
||||
--disk path=~/hypervisor/images/focal-server-cloudimg-amd64.raw \
|
||||
--cpus 4 \
|
||||
--memory size=1024M \
|
||||
@@ -94,4 +94,4 @@ The `0x80` debug port and the port of the firmware debug device are always
|
||||
available. The debug console must be activated via the command line, but
|
||||
provides more configuration options.
|
||||
|
||||
You can use different ports for different aspect of your logging messages.
|
||||
You can use different ports for different aspects of your logging messages.
|
||||
|
||||
@@ -31,7 +31,7 @@ Simple emulation of a serial port by reading and writing to specific port I/O
|
||||
addresses. The serial port can be very useful to gather early logs from the
|
||||
operating system booted inside the VM.
|
||||
|
||||
For x86_64, The default serial port is from an emulated 16550A device. It can
|
||||
For x86_64, the default serial port is from an emulated 16550A device. It can
|
||||
be used as the default console for Linux when booting with the option
|
||||
`console=ttyS0`. For AArch64, the default serial port is from an emulated
|
||||
PL011 UART device. The related command line for AArch64 is `console=ttyAMA0`.
|
||||
@@ -48,7 +48,7 @@ This device is built-in by default, but it can be compiled out with Rust
|
||||
features. When compiled in, it is always enabled, and cannot be disabled
|
||||
from the command line.
|
||||
|
||||
For AArch64 machines, an ARM PrimeCell Real Time Clock(PL031) is implemented.
|
||||
For AArch64 machines, an ARM PrimeCell Real Time Clock (PL031) is implemented.
|
||||
This device is built-in by default for the AArch64 platform, and it is always
|
||||
enabled, and cannot be disabled from the command line.
|
||||
|
||||
@@ -136,7 +136,7 @@ flag `--net`.
|
||||
|
||||
The `virtio-pmem` implementation emulates a virtual persistent memory device
|
||||
that `cloud-hypervisor` can e.g. boot from. Booting from a `virtio-pmem` device
|
||||
allows to bypass the guest page cache and improve the guest memory footprint.
|
||||
allows bypassing the guest page cache and improve the guest memory footprint.
|
||||
|
||||
This device is always built-in, and it is enabled based on the presence of the
|
||||
flag `--pmem`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GDB Support
|
||||
|
||||
This feature allows remote guest debugging using GDB. Note that this feature is only supported on x86_64/KVM.
|
||||
This feature allows remote guest debugging using GDB. Note that this feature is supported on x86_64 and aarch64 with KVM.
|
||||
|
||||
To enable debugging with GDB, build with the `guest_debug` feature enabled:
|
||||
|
||||
@@ -8,7 +8,7 @@ To enable debugging with GDB, build with the `guest_debug` feature enabled:
|
||||
cargo build --features guest_debug
|
||||
```
|
||||
|
||||
To use the `--gdb` option, specify the Unix Domain Socket with `--path` that Cloud Hypervisor will use to communicate with the host's GDB:
|
||||
To use the `--gdb` option, specify the Unix Domain Socket with `path` that Cloud Hypervisor will use to communicate with the host's GDB:
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
|
||||
@@ -110,7 +110,7 @@ Mem: 3.0Gi 71Mi 2.8Gi 0.0Ki 47Mi 2.8Gi
|
||||
Swap: 32Mi 0B 32Mi
|
||||
```
|
||||
|
||||
Due to guest OS limitations is is necessary to ensure that amount of memory added (between currently assigned RAM and that which is desired) is a multiple of 128MiB.
|
||||
Due to guest OS limitations it is necessary to ensure that amount of memory added (between currently assigned RAM and that which is desired) is a multiple of 128MiB.
|
||||
|
||||
The same API can also be used to reduce the desired RAM for a VM but the change will not be applied until the VM is rebooted.
|
||||
|
||||
@@ -179,7 +179,7 @@ Notice the addition of `--api-socket=/tmp/ch-socket`.
|
||||
|
||||
### Add VFIO Device
|
||||
|
||||
To ask the VMM to add additional VFIO device then use the `add-device` API.
|
||||
To ask the VMM to add additional VFIO device, use the `add-device` API.
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket add-device path=/sys/bus/pci/devices/0000:01:00.0/
|
||||
@@ -187,7 +187,7 @@ To ask the VMM to add additional VFIO device then use the `add-device` API.
|
||||
|
||||
### Add Disk Device
|
||||
|
||||
To ask the VMM to add additional disk device then use the `add-disk` API.
|
||||
To ask the VMM to add additional disk device, use the `add-disk` API.
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket add-disk path=/foo/bar/cloud.img
|
||||
@@ -195,7 +195,7 @@ To ask the VMM to add additional disk device then use the `add-disk` API.
|
||||
|
||||
### Add Fs Device
|
||||
|
||||
To ask the VMM to add additional fs device then use the `add-fs` API.
|
||||
To ask the VMM to add additional fs device, use the `add-fs` API.
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket add-fs tag=myfs,socket=/foo/bar/virtiofs.sock
|
||||
@@ -203,7 +203,7 @@ To ask the VMM to add additional fs device then use the `add-fs` API.
|
||||
|
||||
### Add Net Device
|
||||
|
||||
To ask the VMM to add additional network device then use the `add-net` API.
|
||||
To ask the VMM to add additional network device, use the `add-net` API.
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket add-net tap=chtap0
|
||||
@@ -211,7 +211,7 @@ To ask the VMM to add additional network device then use the `add-net` API.
|
||||
|
||||
### Add Pmem Device
|
||||
|
||||
To ask the VMM to add additional PMEM device then use the `add-pmem` API.
|
||||
To ask the VMM to add additional PMEM device, use the `add-pmem` API.
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket add-pmem file=/foo/bar.cloud.img
|
||||
@@ -219,7 +219,7 @@ To ask the VMM to add additional PMEM device then use the `add-pmem` API.
|
||||
|
||||
### Add Vsock Device
|
||||
|
||||
To ask the VMM to add additional vsock device then use the `add-vsock` API.
|
||||
To ask the VMM to add additional vsock device, use the `add-vsock` API.
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket add-vsock cid=3,socket=/foo/bar/vsock.sock
|
||||
@@ -241,7 +241,7 @@ After a reboot the added PCI device will remain.
|
||||
|
||||
### Remove PCI device
|
||||
|
||||
Removing a PCI device works the same way for all kind of PCI devices. The unique identifier related to the device must be provided. This identifier can be provided by the user when adding the new device, or by default Cloud Hypervisor will assign one.
|
||||
Removing a PCI device works the same way for all kinds of PCI devices. The unique identifier related to the device must be provided. This identifier can be provided by the user when adding the new device, or by default Cloud Hypervisor will assign one.
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket remove-device _disk0
|
||||
|
||||
@@ -75,7 +75,7 @@ meaning it will be printing guest kernel logs to the `virtio-console` device.
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--platform tdx=on
|
||||
--platform tdx=on \
|
||||
--firmware edk2/Build/IntelTdx/RELEASE_GCC5/FV/OVMF.fd \
|
||||
--cpus boot=1 \
|
||||
--memory size=1G \
|
||||
@@ -87,7 +87,7 @@ firmware:
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--platform tdx=on
|
||||
--platform tdx=on \
|
||||
--firmware edk2/Build/IntelTdx/DEBUG_GCC5/FV/OVMF.fd \
|
||||
--cpus boot=1 \
|
||||
--memory size=1G \
|
||||
@@ -105,7 +105,7 @@ This is a lightweight version of the TDVF, written in Rust and designed for
|
||||
direct kernel boot, which is useful for containers use cases.
|
||||
|
||||
To build TDShim from source, it is required to install `Rust`, `NASM`,
|
||||
and `LLVM` first. The TDshim can be build as follows:
|
||||
and `LLVM` first. The TDshim can be built as follows:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/confidential-containers/td-shim
|
||||
@@ -136,10 +136,10 @@ option as well.
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--platform tdx=on
|
||||
--platform tdx=on \
|
||||
--firmware td-shim/target/release/final.bin \
|
||||
--kernel bzImage \
|
||||
--cmdline "root=/dev/vda3 console=hvc0 rw"
|
||||
--cmdline "root=/dev/vda3 console=hvc0 rw" \
|
||||
--cpus boot=1 \
|
||||
--memory size=1G \
|
||||
--disk path=tdx_guest_img
|
||||
@@ -150,10 +150,10 @@ TDShim:
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--platform tdx=on
|
||||
--platform tdx=on \
|
||||
--firmware td-shim/target/debug/final.bin \
|
||||
--kernel bzImage \
|
||||
--cmdline "root=/dev/vda3 console=hvc0 rw"
|
||||
--cmdline "root=/dev/vda3 console=hvc0 rw" \
|
||||
--cpus boot=1 \
|
||||
--memory size=1G \
|
||||
--disk path=tdx_guest_img
|
||||
|
||||
@@ -27,11 +27,11 @@ Hypervisor provides another three options for limiting I/O operations,
|
||||
i.e., `ops_size` (I/O operations), `ops_one_time_burst` (I/O operations),
|
||||
and `ops_refill_time` (ms).
|
||||
|
||||
One caveat in the I/O throttling is that every-time the bucket gets
|
||||
One caveat in the I/O throttling is that every time the bucket gets
|
||||
empty, it will stop I/O operations for a fixed amount of time
|
||||
(`cool_down_time`). The `cool_down_time` now is fixed at `100 ms`, it
|
||||
can have big implications to the actual rate limit (which can be a lot
|
||||
different the expected "refill-rate" derived from user inputs). For
|
||||
can have big implications for the actual rate limit (which can be quite
|
||||
different from the expected "refill-rate" derived from user inputs). For
|
||||
example, to have a 1000 IOPS limit on a virtio-blk device, users should
|
||||
be able to provide either of the following two options:
|
||||
`ops_size=1000,ops_refill_time=1000` or
|
||||
@@ -53,5 +53,5 @@ demonstrates how to throttle the aggregate bandwidth of two disks to 10 MiB/s.
|
||||
```
|
||||
--disk path=disk0.raw,rate_limit_group=group0 \
|
||||
path=disk1.raw,rate_limit_group=group0 \
|
||||
--rate-limit-group bw_size=1048576,bw_refill_time,bw_refill_time=100
|
||||
--rate-limit-group bw_size=1048576,bw_refill_time=100
|
||||
```
|
||||
|
||||
@@ -15,7 +15,7 @@ to increase the security regarding the memory accesses performed by the virtual
|
||||
devices (VIRTIO devices), on behalf of the guest drivers.
|
||||
|
||||
With a virtual IOMMU, the VMM stands between the guest driver and its device
|
||||
counterpart, validating and translating every address before to try accessing
|
||||
counterpart, validating and translating every address before trying accessing
|
||||
the guest memory. This is standard interposition that is performed here by the
|
||||
VMM.
|
||||
|
||||
@@ -75,8 +75,8 @@ Not all devices support this extra option, and the default value will always
|
||||
be `off` since we want to avoid the performance impact for most users who don't
|
||||
need this.
|
||||
|
||||
Refer to the command line `--help` to find out which device support to be
|
||||
attached to the virtual IOMMU.
|
||||
Refer to the command line `--help` to find out which devices can be supported
|
||||
to be attached to the virtual IOMMU.
|
||||
|
||||
Below is a simple example exposing the `virtio-blk` device as attached to the
|
||||
virtual IOMMU:
|
||||
@@ -128,7 +128,7 @@ When ACPI is disabled, virtual IOMMU is supported through Flattened Device Tree
|
||||
IOMMU-attached and which should not. No matter how many devices you attached to
|
||||
the virtual IOMMU by setting `iommu=on` option, all the devices on the PCI bus
|
||||
will be attached to the virtual IOMMU (except the IOMMU itself). Each of the
|
||||
devices will be added into a IOMMU group.
|
||||
devices will be added into an IOMMU group.
|
||||
|
||||
As a result, the directory content of `/sys/kernel/iommu_groups` would be:
|
||||
|
||||
@@ -151,7 +151,7 @@ of requests need to be issued in order to create large mappings.
|
||||
One use case is even more impacted by the slowdown, the nested VFIO case. When
|
||||
passing a device through a L2 guest, the VFIO driver running in L1 will update
|
||||
the DMAR entries for the specific device. Because VFIO pins the entire guest
|
||||
memory, this means the entire mapping of the L2 guest need to be stored into
|
||||
memory, this means the entire mapping of the L2 guest needs to be stored into
|
||||
multiple 4k mappings. Obviously, the bigger the L2 guest RAM is, the longer the
|
||||
update of the mappings will last. There is an additional problem happening in
|
||||
this case, if the L2 guest RAM is quite large, it will require a large number
|
||||
@@ -194,7 +194,7 @@ be consumed.
|
||||
### Nested usage
|
||||
|
||||
Let's now look at the specific example of nested virtualization. In order to
|
||||
reach optimized performances, the L2 guest also need to be mapped based on
|
||||
reach optimized performances, the L2 guest also needs to be mapped based on
|
||||
huge pages. Here is how to achieve this, assuming the physical device you are
|
||||
passing through is `0000:00:01.0`.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ region between a guest and the host. In order for all guests to be able to
|
||||
pick up the shared memory area, it is modeled as a PCI device exposing said
|
||||
memory to the guest as a PCI BAR.
|
||||
|
||||
Device Specification is
|
||||
Device Specification is available
|
||||
at https://www.qemu.org/docs/master/specs/ivshmem-spec.html.
|
||||
|
||||
Now we support setting a backend file to share data between host and guest.
|
||||
@@ -16,9 +16,10 @@ supported yet.
|
||||
|
||||
`--ivshmem`, an optional argument, can be passed to enable ivshmem device.
|
||||
This argument takes a file as a `path` value and a file size as a `size` value.
|
||||
The `size` value must be 2^n.
|
||||
|
||||
```
|
||||
--ivshmem <ivshmem> device backend file "path=</path/to/a/file>,size=<file_size/must=2^n>";
|
||||
--ivshmem <ivshmem> device backend file "path=</path/to/a/file>,size=<file_size>"
|
||||
```
|
||||
|
||||
## Example
|
||||
@@ -41,11 +42,11 @@ Start application to mmap the file data to a memory region:
|
||||
--ivshmem path=/tmp/ivshmem.data,size=1M
|
||||
```
|
||||
|
||||
Insmod a ivshmem device driver to enable the device. The file data will be
|
||||
Insmod an ivshmem device driver to enable the device. The file data will be
|
||||
mmapped to the PCI `bar2` of ivshmem device,
|
||||
guest can r/w data by accessing this memory.
|
||||
|
||||
A simple example of ivshmem driver can get from:
|
||||
A simple example of ivshmem driver can be obtained from:
|
||||
https://github.com/lisongqian/clh-linux/commits/ch-6.12.8-ivshmem
|
||||
|
||||
The host process can r/w this data by remmaping the `/tmp/ivshmem.data`.
|
||||
The host process can r/w this data by remapping the `/tmp/ivshmem.data`.
|
||||
|
||||
@@ -16,11 +16,11 @@ permissions.
|
||||
|
||||
## Host Setup
|
||||
|
||||
Landlock should be enabled in Host kernel to use it with cloud-hypervisor.
|
||||
Please following [Kernel-Support](https://docs.kernel.org/userspace-api/landlock.html#kernel-support) link to enable Landlock on Host kernel.
|
||||
Landlock should be enabled in host kernel to use it with cloud-hypervisor.
|
||||
Please follow [Kernel-Support](https://docs.kernel.org/userspace-api/landlock.html#kernel-support) link to enable Landlock on Host kernel.
|
||||
|
||||
|
||||
Landlock support can be checked with following command:
|
||||
Landlock support can be checked with the following command:
|
||||
```
|
||||
$ sudo dmesg | grep -w landlock
|
||||
[ 0.000000] landlock: Up and running.
|
||||
@@ -30,8 +30,8 @@ Linux kernel confirms Landlock support with above message in dmesg.
|
||||
## Enable Landlock
|
||||
|
||||
At the time of enabling Landlock, Cloud-Hypervisor process needs the complete
|
||||
list of files it accesses over its lifetime. So, Landlock is enabled `vm_create`
|
||||
stage of guest boot.
|
||||
list of files it accesses over its lifetime. So, Landlock is enabled at the
|
||||
`vm_create` stage of guest boot.
|
||||
|
||||
### Command Line
|
||||
Append `--landlock` to Cloud-Hypervisor's command line to enable Landlock
|
||||
|
||||
@@ -6,7 +6,7 @@ in Cloud Hypervisor:
|
||||
1. local migration - migrating a VM from one Cloud Hypervisor instance to another on the same machine;
|
||||
1. remote migration - migrating a VM between two machines;
|
||||
|
||||
> :warning: These examples place sockets /tmp. This is done for
|
||||
> :warning: These examples place sockets in /tmp. This is done for
|
||||
> simplicity and should not be done in production.
|
||||
|
||||
## Local Migration (Suitable for Live Upgrade of VMM)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Using MACVTAP to Bridge onto Host Network
|
||||
|
||||
Cloud Hypervisor supports using a MACVTAP device which is derived from a MACVLAN. Full details of configuring MACVLAN or MACVTAP is out of scope of this document. However the example below indicates how to bridge the guest directly onto the network the host is on. Due to the lack of hairpin mode it not usually possible to reach the guest directly from the host.
|
||||
Cloud Hypervisor supports using a MACVTAP device which is derived from a MACVLAN. Full details of configuring MACVLAN or MACVTAP are out of scope of this document. However the example below indicates how to bridge the guest directly onto the network the host is on. Due to the lack of hairpin mode it is not usually possible to reach the guest directly from the host.
|
||||
|
||||
```bash
|
||||
# The MAC address must be attached to the macvtap and be used inside the guest
|
||||
@@ -26,7 +26,7 @@ target/debug/cloud-hypervisor \
|
||||
--disk path=~/workloads/focal.raw \
|
||||
--cpus boot=1 --memory size=512M \
|
||||
--cmdline "root=/dev/vda1 console=hvc0" \
|
||||
--net fd=3,mac=$mac 3<>$"$tapdevice"
|
||||
--net fd=3,mac=$mac 3<>"$tapdevice"
|
||||
```
|
||||
|
||||
As the guest is now connected to the same L2 network as the host you can obtain an IP address based on your host network (potentially including via DHCP)
|
||||
As the guest is now connected to the same L2 network as the host, you can obtain an IP address based on your host network (potentially including via DHCP)
|
||||
|
||||
@@ -20,7 +20,7 @@ struct MemoryConfig {
|
||||
hugepages: bool,
|
||||
hugepage_size: Option<u64>,
|
||||
prefault: bool,
|
||||
thp: bool
|
||||
thp: bool,
|
||||
zones: Option<Vec<MemoryZoneConfig>>,
|
||||
}
|
||||
```
|
||||
@@ -119,7 +119,7 @@ By default this option is turned off, which results in performing `mmap(2)`
|
||||
with `MAP_PRIVATE` flag.
|
||||
|
||||
If `hugepages=on` then the value of this field is ignored as huge pages always
|
||||
requires `MAP_SHARED`.
|
||||
require `MAP_SHARED`.
|
||||
|
||||
_Example_
|
||||
|
||||
@@ -135,8 +135,7 @@ If no huge page size is supplied the system's default huge page size is used.
|
||||
|
||||
By using hugepages, one can improve the overall performance of the VM, assuming
|
||||
the guest will allocate hugepages as well. Another interesting use case is VFIO
|
||||
as it speeds up the VM's boot time since the amount of IOMMU mappings are
|
||||
reduced.
|
||||
as it speeds up the VM's boot time since the amount of IOMMU mappings is reduced.
|
||||
|
||||
The user is responsible for ensuring there are sufficient huge pages of the
|
||||
specified size for the VMM to use. Failure to do so may result in strange VMM
|
||||
@@ -185,7 +184,7 @@ backing file) should be labelled `MADV_HUGEPAGE` with `madvise(2)` indicating
|
||||
to the kernel that this memory may be backed with huge pages transparently.
|
||||
|
||||
The use of transparent huge pages can improve the performance of the guest as
|
||||
there will fewer virtualisation related page faults. Unlike using
|
||||
there will be fewer virtualisation related page faults. Unlike using
|
||||
`hugepages=on` a specific number of huge pages do not need to be allocated by
|
||||
the kernel.
|
||||
|
||||
@@ -295,9 +294,9 @@ vhost-user devices as part of the VM device model, as they will be driven
|
||||
by standalone daemons needing access to the guest RAM content.
|
||||
|
||||
If `hugepages=on` then the value of this field is ignored as huge pages always
|
||||
requires `MAP_SHARED`.
|
||||
require `MAP_SHARED`.
|
||||
|
||||
By default this option is turned off, which result in performing `mmap(2)`
|
||||
By default this option is turned off, which results in performing `mmap(2)`
|
||||
with `MAP_PRIVATE` flag.
|
||||
|
||||
_Example_
|
||||
@@ -315,8 +314,7 @@ If no huge page size is supplied the system's default huge page size is used.
|
||||
|
||||
By using hugepages, one can improve the overall performance of the VM, assuming
|
||||
the guest will allocate hugepages as well. Another interesting use case is VFIO
|
||||
as it speeds up the VM's boot time since the amount of IOMMU mappings are
|
||||
reduced.
|
||||
as it speeds up the VM's boot time since the amount of IOMMU mappings is reduced.
|
||||
|
||||
The user is responsible for ensuring there are sufficient huge pages of the
|
||||
specified size for the VMM to use. Failure to do so may result in strange VMM
|
||||
@@ -325,7 +323,7 @@ error with `hugepages` enabled, just disable it or check whether there are enoug
|
||||
huge pages.
|
||||
|
||||
If `hugepages=on` then the value of `shared` is ignored as huge pages always
|
||||
requires `MAP_SHARED`.
|
||||
require `MAP_SHARED`.
|
||||
|
||||
By default this option is turned off.
|
||||
|
||||
@@ -431,17 +429,20 @@ introduced to define a guest NUMA topology. It allows for a fine description
|
||||
about the CPUs and memory ranges associated with each NUMA node. Additionally
|
||||
it allows for specifying the distance between each NUMA node.
|
||||
|
||||
Furthermore, it supports ACPI Generic Initiator Affinity (SRAT Type 5), which allows VFIO-PCI devices (such as GPUs) to be associated with NUMA nodes that are {memory,cpu}-less. Detailed configuration for this feature can be found under the device_id parameter.
|
||||
|
||||
```rust
|
||||
struct NumaConfig {
|
||||
guest_numa_id: u32,
|
||||
cpus: Option<Vec<u8>>,
|
||||
cpus: Option<Vec<u32>>,
|
||||
distances: Option<Vec<NumaDistance>>,
|
||||
memory_zones: Option<Vec<String>>,
|
||||
device_id: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
--numa <numa> Settings related to a given NUMA node "guest_numa_id=<node_id>,cpus=<cpus_id>,distances=<list_of_distances_to_destination_nodes>,memory_zones=<list_of_memory_zones>
|
||||
--numa <numa> Settings related to a given NUMA node "guest_numa_id=<node_id>,cpus=<cpus_id>,distances=<list_of_distances_to_destination_nodes>,memory_zones=<list_of_memory_zones>,device_id=<device_identifier>"
|
||||
```
|
||||
|
||||
### `guest_numa_id`
|
||||
@@ -456,7 +457,7 @@ Value is an unsigned integer of 32 bits.
|
||||
_Example_
|
||||
|
||||
```
|
||||
--numa guest_numa_id=0
|
||||
--numa guest_numa_id=0,cpus=[0-1],memory_zones=mem0
|
||||
```
|
||||
|
||||
### `cpus`
|
||||
@@ -470,7 +471,7 @@ regarding the CPUs associated with it, which might help the guest run more
|
||||
efficiently.
|
||||
|
||||
Multiple values can be provided to define the list. Each value is an unsigned
|
||||
integer of 8 bits.
|
||||
integer of 32 bits.
|
||||
|
||||
For instance, if one needs to attach all CPUs from 0 to 4 to a specific node,
|
||||
the syntax using `-` will help define a contiguous range with `cpus=0-4`. The
|
||||
@@ -483,6 +484,9 @@ simply be described with `cpus=[0-99,255]`.
|
||||
As soon as one tries to describe a list of values, `[` and `]` must be used to
|
||||
demarcate the list.
|
||||
|
||||
**Note:** When creating a Generic Initiator node via the `device_id` parameter,
|
||||
the `cpus` option must not be specified.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
@@ -493,7 +497,7 @@ _Example_
|
||||
### `distances`
|
||||
|
||||
List of distances between the current NUMA node referred by `guest_numa_id`
|
||||
and the destination NUMA nodes listed along with distances. This option let
|
||||
and the destination NUMA nodes listed along with distances. This option lets
|
||||
the user choose the distances between guest NUMA nodes. This is important to
|
||||
provide an accurate description of the way non uniform memory accesses will
|
||||
perform in the guest.
|
||||
@@ -509,13 +513,34 @@ from the others with `,` separator.
|
||||
As soon as one tries to describe a list of values, `[` and `]` must be used to
|
||||
demarcate the list.
|
||||
|
||||
**Default distances:**
|
||||
- If distances are not specified for a NUMA node, default values are applied:
|
||||
- Distance to self: 10
|
||||
- Distance to all other nodes: 20
|
||||
- Partial distance specifications are allowed; unspecified distances use the defaults above
|
||||
|
||||
**Distance symmetry:**
|
||||
- Cloud Hypervisor automatically ensures distance symmetry in ACPI SLIT (System Locality Information Table) and FDT
|
||||
- If node A specifies distance to node B, the reverse distance (B to A) is automatically set to the same value
|
||||
|
||||
For instance, if one wants to define 3 NUMA nodes, with each node located at
|
||||
different distances, it can be described with the following example.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
# Explicit bidirectional distances
|
||||
--numa guest_numa_id=0,distances=[1@15,2@25] guest_numa_id=1,distances=[0@15,2@20] guest_numa_id=2,distances=[0@25,1@20]
|
||||
|
||||
# Simplified with symmetry - only specify in one direction
|
||||
--numa guest_numa_id=0,distances=[1@15,2@25] guest_numa_id=1,distances=[2@20]
|
||||
# Results in the same topology: 0↔1=15, 0↔2=25, 1↔2=20
|
||||
|
||||
# Using defaults - only specify non-default distances
|
||||
--numa guest_numa_id=0,cpus=[0-1],memory_zones=mem0,distances=[1@15]
|
||||
--numa guest_numa_id=1,cpus=[2-3],memory_zones=mem1
|
||||
# Node 0: self=10, to node 1=15
|
||||
# Node 1: self=10, to node 0=15 (symmetric)
|
||||
```
|
||||
|
||||
### `memory_zones`
|
||||
@@ -541,6 +566,9 @@ Note that a memory zone must belong to a single NUMA node. The following
|
||||
configuration is incorrect, therefore not allowed:
|
||||
`--numa guest_numa_id=0,memory_zones=mem0 guest_numa_id=1,memory_zones=mem0`
|
||||
|
||||
**Note:** When creating a Generic Initiator node via the `device_id` parameter,
|
||||
the `memory_zones` option must not be specified.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
@@ -549,10 +577,48 @@ _Example_
|
||||
--numa guest_numa_id=0,memory_zones=[mem0,mem2] guest_numa_id=1,memory_zones=mem1
|
||||
```
|
||||
|
||||
### `device_id` (Generic Initiator)
|
||||
|
||||
Device identifier for creating a Generic Initiator NUMA node that is
|
||||
{CPU,memory}-less and associated with a specific VFIO-PCI device.
|
||||
|
||||
Generic Initiator nodes are defined by ACPI SRAT (System Resource Affinity
|
||||
Table) Type 5 entries and allow the guest OS to understand device-to-memory
|
||||
proximity relationships. Without Generic Initiator support, the guest OS has
|
||||
no way to know which NUMA node a passthrough device is closest to.
|
||||
|
||||
By exposing these proximity relationships, the guest OS can perform
|
||||
NUMA-aware scheduling and optimize memory placement for workloads
|
||||
utilizing those specific devices.
|
||||
|
||||
When `device_id` is specified, `cpus` and `memory_zones` must NOT be provided.
|
||||
|
||||
Value is a string referring to an existing device identifier defined via
|
||||
`--device id=<device_identifier>`.
|
||||
|
||||
_Example_
|
||||
|
||||
```bash
|
||||
# Create two standard NUMA nodes with CPUs and memory, plus one Generic
|
||||
# Initiator node for a VFIO GPU
|
||||
--cpus boot=4
|
||||
--memory size=0
|
||||
--memory-zone id=mem0,size=2G id=mem1,size=2G
|
||||
--numa guest_numa_id=0,cpus=[0-1],memory_zones=mem0,distances=[1@20,2@25]
|
||||
--numa guest_numa_id=1,cpus=[2-3],memory_zones=mem1,distances=[0@20,2@30]
|
||||
--numa guest_numa_id=2,device_id=gpu0,distances=[0@25,1@30]
|
||||
--device id=gpu0,path=/sys/bus/pci/devices/0000:01:00.0,iommu=on
|
||||
```
|
||||
|
||||
In this configuration:
|
||||
- Node 0: CPUs 0-1, 2GB memory
|
||||
- Node 1: CPUs 2-3, 2GB memory
|
||||
- Node 2 (auto-assigned): GPU device, closer to node 0 (distance=25) than node 1 (distance=30)
|
||||
|
||||
### PCI bus
|
||||
|
||||
Cloud Hypervisor supports guests with one or more PCI segments. The default PCI segment always
|
||||
has affinity to NUMA node 0. Be default, all other PCI segments have affinity to NUMA node 0.
|
||||
has affinity to NUMA node 0. By default, all other PCI segments have affinity to NUMA node 0.
|
||||
The user may configure the NUMA affinity for any additional PCI segments.
|
||||
|
||||
_Example_
|
||||
|
||||
43
docs/uefi.md
43
docs/uefi.md
@@ -35,13 +35,31 @@ After the successful build, the resulting firmware binaries are available under
|
||||
# On an AArch64 machine:
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install uuid-dev nasm iasl build-essential python3-distutils git
|
||||
$ git clone --depth 1 https://github.com/tianocore/edk2.git -b master
|
||||
$ cd edk2
|
||||
$ git submodule update --init
|
||||
$ cd ..
|
||||
$ git clone --depth 1 https://github.com/tianocore/edk2-platforms.git -b master
|
||||
$ git clone --depth 1 https://github.com/acpica/acpica.git -b master
|
||||
|
||||
# Master branches for these repos can be unstable, and newer GCC versions
|
||||
# enforce strict warning-as-error policies that break builds
|
||||
# These specific commit # are verified to compile cleanly with GCC 13.3.0
|
||||
# Shallow clone edk2 repo
|
||||
$ mkdir -p edk2 && cd edk2 && \
|
||||
git init -q && \
|
||||
git remote add origin https://github.com/tianocore/edk2.git && \
|
||||
git fetch -q --depth 1 origin 22130dcd98b4d4b76ac8d922adb4a2dbc86fa52c && \
|
||||
git checkout -q FETCH_HEAD && \
|
||||
git submodule update --init --recursive --depth 1 && \
|
||||
cd ..
|
||||
# Shallow clone edk2-platforms repo
|
||||
$ mkdir -p edk2-platforms && cd edk2-platforms && \
|
||||
git init -q && \
|
||||
git remote add origin https://github.com/tianocore/edk2-platforms.git && \
|
||||
git fetch -q --depth 1 origin 8227e9e9f6a8aefbd772b40138f835121ccb2307 && \
|
||||
git checkout -q FETCH_HEAD && \
|
||||
cd ..
|
||||
# Shallow clone acpica repo
|
||||
$ mkdir -p acpica && cd acpica && \
|
||||
git init -q && \
|
||||
git remote add origin https://github.com/acpica/acpica.git && \
|
||||
git fetch -q --depth 1 origin e80cbd7b52de20aa8c75bfba9845e9cb61f2e681 && \
|
||||
git checkout -q FETCH_HEAD && \
|
||||
cd ..
|
||||
# Build tools
|
||||
$ export PACKAGES_PATH="$PWD/edk2:$PWD/edk2-platforms"
|
||||
$ export IASL_PREFIX="$PWD/acpica/generate/unix/bin/"
|
||||
@@ -53,10 +71,19 @@ $ make -C edk2/BaseTools
|
||||
|
||||
# Build EDK2
|
||||
$ build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtCloudHv.dsc -b RELEASE
|
||||
|
||||
# Alternate method
|
||||
# Launch developer container from AArch64 machine
|
||||
$ ./scripts/dev_cli.sh shell
|
||||
# Inside the container
|
||||
$ source scripts/test-util.sh
|
||||
$ source scripts/common-aarch64.sh
|
||||
$ build_edk2
|
||||
```
|
||||
|
||||
If the build goes well, the EDK2 binary is available at
|
||||
`edk2/Build/ArmVirtCloudHv-AARCH64/RELEASE_GCC5/FV/CLOUDHV_EFI.fd`.
|
||||
`edk2/Build/ArmVirtCloudHv-AARCH64/RELEASE_GCC5/FV/CLOUDHV_EFI.fd` or `workloads/CLOUDHV_EFI.fd`
|
||||
when using developer container to produce firmware.
|
||||
|
||||
## Using OVMF Binaries
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ parameters available for the vDPA device.
|
||||
struct VdpaConfig {
|
||||
path: PathBuf,
|
||||
num_queues: usize,
|
||||
iommu: bool,
|
||||
id: Option<String>,
|
||||
pci_segment: u16,
|
||||
}
|
||||
@@ -83,7 +84,7 @@ _Example_
|
||||
|
||||
### `pci_segment`
|
||||
|
||||
PCI segment number to which the vDPA device should be attached to.
|
||||
PCI segment number to which the vDPA device should be attached.
|
||||
|
||||
This parameter is optional.
|
||||
|
||||
|
||||
@@ -37,20 +37,20 @@ sudo sed -i '/vt100/a \n# paravirt console\nhvc0::respawn:/sbin/getty -L hvc0 11
|
||||
# any sort of production setup
|
||||
sudo sed -i 's/root:!::0:::::/root:::0:::::/' etc/shadow
|
||||
# set up init scripts
|
||||
for i in acpid crond
|
||||
for i in acpid crond; do
|
||||
sudo ln -sf /etc/init.d/$i etc/runlevels/default/$i
|
||||
end
|
||||
for i in bootmisc hostname hwclock loadkmap modules networking swap sysctl syslog urandom
|
||||
done
|
||||
for i in bootmisc hostname hwclock loadkmap modules networking swap sysctl syslog urandom; do
|
||||
sudo ln -sf /etc/init.d/$i etc/runlevels/boot/$i
|
||||
end
|
||||
done
|
||||
|
||||
for i in killprocs mount-ro savecache
|
||||
for i in killprocs mount-ro savecache; do
|
||||
sudo ln -sf /etc/init.d/$i etc/runlevels/shutdown/$i
|
||||
end
|
||||
done
|
||||
|
||||
for i in devfs dmesg hwdrivers mdev
|
||||
for i in devfs dmesg hwdrivers mdev; do
|
||||
sudo ln -sf /etc/init.d/$i etc/runlevels/sysinit/$i
|
||||
end
|
||||
done
|
||||
# setup network config
|
||||
echo 'auto lo
|
||||
iface lo inet loopback
|
||||
@@ -89,4 +89,4 @@ virtiofs
|
||||
If you find any issues or have suggestions, feel free to reach out to @iggy on
|
||||
the cloud-hypervisor slack. Also if this works for you, I'd like to know as
|
||||
well. It would also be nice to get steps for preparing other distribution root
|
||||
filesystems.
|
||||
filesystems.
|
||||
|
||||
441
fuzz/Cargo.lock
generated
441
fuzz/Cargo.lock
generated
@@ -4,8 +4,9 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "acpi_tables"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/acpi_tables?branch=main#e08a3f0b0a59b98859dbf59f5aa7fd4d2eb4018a"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ad581b2b0fa02638f3df6ff3f852ebc30dc7cfe531e9745d1ca4c0f283a6dbe"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
@@ -68,9 +69,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.100"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
@@ -80,9 +81,12 @@ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.7.1"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||
checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arch"
|
||||
@@ -96,7 +100,7 @@ dependencies = [
|
||||
"linux-loader",
|
||||
"log",
|
||||
"serde",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"uuid",
|
||||
"vm-fdt",
|
||||
"vm-memory",
|
||||
@@ -128,14 +132,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"byteorder",
|
||||
"crc-any",
|
||||
"flate2",
|
||||
@@ -144,7 +149,7 @@ dependencies = [
|
||||
"remain",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"uuid",
|
||||
"virtio-bindings",
|
||||
"virtio-queue",
|
||||
@@ -156,9 +161,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
version = "3.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
@@ -168,9 +173,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.49"
|
||||
version = "1.2.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
|
||||
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -186,18 +191,18 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.53"
|
||||
version = "4.5.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
||||
checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.53"
|
||||
version = "4.5.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
||||
checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -207,9 +212,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.6"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
||||
|
||||
[[package]]
|
||||
name = "cloud-hypervisor-fuzz"
|
||||
@@ -319,7 +324,7 @@ dependencies = [
|
||||
"acpi_tables",
|
||||
"anyhow",
|
||||
"arch",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.0",
|
||||
"byteorder",
|
||||
"event_monitor",
|
||||
"hypervisor",
|
||||
@@ -328,7 +333,7 @@ dependencies = [
|
||||
"num_enum",
|
||||
"pci",
|
||||
"serde",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"tpm",
|
||||
"vm-allocator",
|
||||
"vm-device",
|
||||
@@ -369,7 +374,7 @@ version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e74d68fe2927dbf47aa976d14d93db9b23dced457c7bb2bdc6925a16d31b736e"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -379,6 +384,16 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event_monitor"
|
||||
version = "0.1.0"
|
||||
@@ -396,7 +411,7 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -407,15 +422,15 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.5"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.5"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
|
||||
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
@@ -440,24 +455,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
|
||||
|
||||
[[package]]
|
||||
name = "gdbstub"
|
||||
version = "0.7.8"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72742d2b395902caf8a5d520d0dd3334ba6d1138938429200e58d5174e275f3f"
|
||||
checksum = "6bf845b08f7c2ef3b5ad19f80779d43ae20d278652b91bb80adda65baf2d8ed6"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.0",
|
||||
"cfg-if",
|
||||
"log",
|
||||
"managed",
|
||||
@@ -477,9 +498,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -500,12 +521,40 @@ dependencies = [
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hypervisor"
|
||||
version = "0.1.0"
|
||||
@@ -526,7 +575,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"vfio-ioctls",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
@@ -542,6 +591,12 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
@@ -550,12 +605,14 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.12.1"
|
||||
version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
|
||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"hashbrown 0.16.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -575,9 +632,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
@@ -616,7 +673,7 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c8f7370330b4f57981e300fa39b02088f2f2a5c2d0f1f994e8090589619c56d"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.0",
|
||||
"kvm-bindings",
|
||||
"libc",
|
||||
"vmm-sys-util",
|
||||
@@ -630,7 +687,7 @@ checksum = "49fefd6652c57d68aaa32544a4c0e642929725bdc1fd929367cdeb673ab81088"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"libc",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -640,16 +697,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.178"
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.182"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.10"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
|
||||
checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
@@ -687,9 +750,9 @@ checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "micro_http"
|
||||
@@ -712,9 +775,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mshv-bindings"
|
||||
version = "0.6.5"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66f415da68542aca92b33f55ac3e93031dc30a2941952b99679258f7e0527353"
|
||||
checksum = "3cbfd4f32d185152003679339751839da77c17e18fa8882a11051a236f841426"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num_enum",
|
||||
@@ -736,13 +799,13 @@ name = "net_util"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"epoll",
|
||||
"getrandom 0.3.4",
|
||||
"getrandom 0.4.1",
|
||||
"libc",
|
||||
"log",
|
||||
"net_gen",
|
||||
"rate_limiter",
|
||||
"serde",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"virtio-bindings",
|
||||
"virtio-queue",
|
||||
"vm-memory",
|
||||
@@ -817,7 +880,7 @@ dependencies = [
|
||||
name = "option_parser"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -836,7 +899,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"serde",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"vfio-bindings",
|
||||
"vfio-ioctls",
|
||||
"vfio_user",
|
||||
@@ -862,6 +925,16 @@ dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.4.0"
|
||||
@@ -873,18 +946,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.42"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -917,9 +990,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
@@ -931,7 +1004,7 @@ dependencies = [
|
||||
"epoll",
|
||||
"libc",
|
||||
"log",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
@@ -952,12 +1025,6 @@ version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@@ -973,6 +1040,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
@@ -1005,15 +1078,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.145"
|
||||
version = "1.0.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1050,9 +1123,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.18"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||
checksum = "3b57709da74f9ff9f4a27dce9526eec25ca8407c45a7887243b031a58935fb8e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
@@ -1060,10 +1133,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.7"
|
||||
version = "1.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad"
|
||||
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||
dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -1096,9 +1170,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.111"
|
||||
version = "2.0.116"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1116,11 +1190,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.17"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.17",
|
||||
"thiserror-impl 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1136,9 +1210,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.17"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1147,18 +1221,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.3"
|
||||
version = "0.7.5+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
|
||||
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.23.9"
|
||||
version = "0.23.10+spec-1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832"
|
||||
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
@@ -1168,9 +1242,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.4"
|
||||
version = "1.0.9+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
|
||||
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
@@ -1183,7 +1257,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"net_gen",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
@@ -1199,9 +1273,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
@@ -1211,11 +1291,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.19.0"
|
||||
version = "1.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
|
||||
checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"getrandom 0.4.1",
|
||||
"js-sys",
|
||||
"rand",
|
||||
"wasm-bindgen",
|
||||
@@ -1241,7 +1321,7 @@ dependencies = [
|
||||
"kvm-ioctls",
|
||||
"libc",
|
||||
"log",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"vfio-bindings",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
@@ -1253,13 +1333,13 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8db5bc783aad75202ad4cbcdc5e893cff1dd8fa24a1bcdb4de8998d3c4d169a"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.0",
|
||||
"libc",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"vfio-bindings",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
@@ -1271,7 +1351,7 @@ version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a4dcad85a129d97d5d4b2f3c47a4affdeedd76bdcd02094bcb5d9b76cac2d05"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.0",
|
||||
"libc",
|
||||
"uuid",
|
||||
"vm-memory",
|
||||
@@ -1302,7 +1382,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_with",
|
||||
"serial_buffer",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"vhost",
|
||||
"virtio-bindings",
|
||||
"virtio-queue",
|
||||
@@ -1341,7 +1421,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"hypervisor",
|
||||
"serde",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"vfio-ioctls",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
@@ -1373,7 +1453,7 @@ dependencies = [
|
||||
"itertools",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"vm-memory",
|
||||
]
|
||||
|
||||
@@ -1392,7 +1472,7 @@ dependencies = [
|
||||
"acpi_tables",
|
||||
"anyhow",
|
||||
"arch",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags 2.11.0",
|
||||
"block",
|
||||
"cfg-if",
|
||||
"clap",
|
||||
@@ -1417,7 +1497,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serial_buffer",
|
||||
"signal-hook",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"tracer",
|
||||
"uuid",
|
||||
"vfio-ioctls",
|
||||
@@ -1454,9 +1534,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip3"
|
||||
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
@@ -1519,6 +1608,40 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@@ -1567,30 +1690,118 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
dependencies = [
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"indexmap",
|
||||
"prettyplease",
|
||||
"syn",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.11.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-xid",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.31"
|
||||
version = "0.8.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
|
||||
checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.31"
|
||||
version = "0.8.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
|
||||
checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
|
||||
@@ -21,11 +21,11 @@ block = { path = "../block" }
|
||||
devices = { path = "../devices" }
|
||||
epoll = "4.3.3"
|
||||
hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] }
|
||||
libc = "0.2.178"
|
||||
libfuzzer-sys = "0.4.10"
|
||||
libc = "0.2.182"
|
||||
libfuzzer-sys = "0.4.12"
|
||||
linux-loader = { version = "0.13.1", features = ["bzimage", "elf", "pe"] }
|
||||
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" }
|
||||
mshv-bindings = "0.6.5"
|
||||
mshv-bindings = "0.6.6"
|
||||
net_util = { path = "../net_util" }
|
||||
seccompiler = "0.5.0"
|
||||
virtio-devices = { path = "../virtio-devices" }
|
||||
|
||||
@@ -67,6 +67,8 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||
None,
|
||||
queue_affinity,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ tdx = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
arc-swap = "1.7.1"
|
||||
arc-swap = "1.8.2"
|
||||
bitfield-struct = "0.12.0"
|
||||
byteorder = { workspace = true }
|
||||
cfg-if = { workspace = true }
|
||||
|
||||
@@ -538,16 +538,13 @@ impl<T: CpuStateManager> Emulator<'_, T> {
|
||||
handler
|
||||
}
|
||||
|
||||
fn emulate_insn_stream(
|
||||
pub fn emulate_insn_stream(
|
||||
&mut self,
|
||||
cpu_id: usize,
|
||||
old_state: &T,
|
||||
insn_stream: &[u8],
|
||||
num_insn: Option<usize>,
|
||||
) -> EmulationResult<T, Exception> {
|
||||
let mut state = self
|
||||
.platform
|
||||
.cpu_state(cpu_id)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
let mut state = old_state.clone();
|
||||
let mut decoder = Decoder::new(64, insn_stream, DecoderOptions::NONE);
|
||||
let mut insn = Instruction::default();
|
||||
let mut num_insn_emulated: usize = 0;
|
||||
@@ -627,7 +624,11 @@ impl<T: CpuStateManager> Emulator<'_, T> {
|
||||
|
||||
/// Emulate all instructions from the instructions stream.
|
||||
pub fn emulate(&mut self, cpu_id: usize, insn_stream: &[u8]) -> EmulationResult<T, Exception> {
|
||||
self.emulate_insn_stream(cpu_id, insn_stream, None)
|
||||
let state = self
|
||||
.platform
|
||||
.cpu_state(cpu_id)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
self.emulate_insn_stream(&state, insn_stream, None)
|
||||
}
|
||||
|
||||
/// Only emulate the first instruction from the stream.
|
||||
@@ -640,7 +641,11 @@ impl<T: CpuStateManager> Emulator<'_, T> {
|
||||
cpu_id: usize,
|
||||
insn_stream: &[u8],
|
||||
) -> EmulationResult<T, Exception> {
|
||||
self.emulate_insn_stream(cpu_id, insn_stream, Some(1))
|
||||
let state = self
|
||||
.platform
|
||||
.cpu_state(cpu_id)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
self.emulate_insn_stream(&state, insn_stream, Some(1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,10 +711,13 @@ mod mock_vmm {
|
||||
insn: &[u8],
|
||||
num_insn: Option<usize>,
|
||||
) -> MockResult {
|
||||
let ip = self.cpu_state(cpu_id).unwrap().ip();
|
||||
let state = self
|
||||
.cpu_state(cpu_id)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
let ip = state.ip();
|
||||
let mut emulator = Emulator::new(self);
|
||||
|
||||
let new_state = emulator.emulate_insn_stream(cpu_id, insn, num_insn)?;
|
||||
let new_state = emulator.emulate_insn_stream(&state, insn, num_insn)?;
|
||||
if num_insn.is_none() {
|
||||
assert_eq!(ip + insn.len() as u64, new_state.ip());
|
||||
}
|
||||
|
||||
@@ -155,7 +155,8 @@ pub trait Hypervisor: Send + Sync {
|
||||
/// Determine CPU vendor
|
||||
///
|
||||
fn get_cpu_vendor(&self) -> CpuVendor {
|
||||
// SAFETY: call cpuid with valid leaves
|
||||
#[allow(unused_unsafe)]
|
||||
// SAFETY: not actually unsafe, but considered unsafe by current stable
|
||||
unsafe {
|
||||
let leaf = x86_64::__cpuid(0x0);
|
||||
|
||||
|
||||
@@ -171,6 +171,7 @@ pub struct HypervisorVmConfig {
|
||||
#[cfg(feature = "sev_snp")]
|
||||
pub mem_size: u64,
|
||||
pub nested: bool,
|
||||
pub smt_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
@@ -224,6 +225,8 @@ macro_rules! set_x86_64_reg {
|
||||
StandardRegisters::Kvm(s) => s.$reg_name = val,
|
||||
#[cfg(any(feature = "mshv", feature = "mshv_emulator"))]
|
||||
StandardRegisters::Mshv(s) => s.$reg_name = val,
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => { let _ = val; unreachable!("no x86_64 register backend available") },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,6 +245,8 @@ macro_rules! get_x86_64_reg {
|
||||
StandardRegisters::Kvm(s) => s.$reg_name,
|
||||
#[cfg(any(feature = "mshv", feature = "mshv_emulator"))]
|
||||
StandardRegisters::Mshv(s) => s.$reg_name,
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => unreachable!("no x86_64 register backend available"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,6 @@ impl Vgic for MshvGicV2M {
|
||||
}
|
||||
|
||||
fn save_data_tables(&self) -> Result<()> {
|
||||
unimplemented!()
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,7 @@ use mshv_bindings::*;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use mshv_ioctls::InterruptRequest;
|
||||
use mshv_ioctls::{
|
||||
Mshv, NoDatamatch, VcpuFd, VmFd, VmType, make_default_partition_create_arg,
|
||||
make_default_synthetic_features_mask, set_registers_64,
|
||||
Mshv, NoDatamatch, VcpuFd, VmFd, VmType, make_default_synthetic_features_mask, set_registers_64,
|
||||
};
|
||||
use vfio_ioctls::VfioDeviceFd;
|
||||
use vm::DataMatch;
|
||||
@@ -69,6 +68,7 @@ pub use x86_64::{VcpuMshvState, emulator};
|
||||
/// Export generically-named wrappers of mshv-bindings for Unix-based platforms
|
||||
///
|
||||
pub use {
|
||||
mshv_bindings::hv_partition_property_code_HV_PARTITION_PROPERTY_PROCESSORS_PER_SOCKET as HV_PARTITION_PROPERTY_PROCESSORS_PER_SOCKET,
|
||||
mshv_bindings::mshv_create_device as CreateDevice,
|
||||
mshv_bindings::mshv_device_attr as DeviceAttr, mshv_ioctls, mshv_ioctls::DeviceFd,
|
||||
};
|
||||
@@ -285,7 +285,8 @@ impl hypervisor::Hypervisor for MshvHypervisor {
|
||||
VmType::Normal
|
||||
};
|
||||
}
|
||||
let mut create_args = make_default_partition_create_arg(mshv_vm_type);
|
||||
|
||||
let mut create_args = self.mshv.make_default_partition_create_arg(mshv_vm_type);
|
||||
let mut disable_proc_features = hv_partition_processor_features::default();
|
||||
// SAFETY: Accessing a union element from bindgen generated bindings.
|
||||
unsafe {
|
||||
@@ -306,6 +307,10 @@ impl hypervisor::Hypervisor for MshvHypervisor {
|
||||
.__bindgen_anon_1
|
||||
.set_nested_virt_support(1u64);
|
||||
}
|
||||
|
||||
if _config.smt_enabled {
|
||||
create_args.pt_flags |= 1 << MSHV_PT_BIT_SMT_ENABLED_GUEST;
|
||||
}
|
||||
}
|
||||
// Modified feature bit fields are written back to create_args
|
||||
for i in 0..create_args.pt_num_cpu_fbanks {
|
||||
@@ -714,20 +719,25 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
map: (gva, gpa),
|
||||
};
|
||||
|
||||
let old_state = context
|
||||
.cpu_state(self.vp_index as usize)
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
|
||||
|
||||
// Create a new emulator.
|
||||
let mut emul = Emulator::new(&mut context);
|
||||
|
||||
// Emulate the trapped instruction, and only the first one.
|
||||
let new_state = emul
|
||||
.emulate_first_insn(
|
||||
self.vp_index as usize,
|
||||
.emulate_insn_stream(
|
||||
&old_state,
|
||||
&info.instruction_bytes[..insn_len],
|
||||
Some(1),
|
||||
)
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
|
||||
|
||||
// Set CPU state back.
|
||||
context
|
||||
.set_cpu_state(self.vp_index as usize, new_state)
|
||||
.update_cpu_state(self.vp_index as usize, old_state, new_state)
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
|
||||
|
||||
Ok(cpu::VmExit::Ignore)
|
||||
@@ -863,6 +873,19 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
assert!(info.header.intercept_access_type == HV_INTERCEPT_ACCESS_EXECUTE as u8);
|
||||
|
||||
match ghcb_op {
|
||||
GHCB_INFO_SPECIAL_DBGPRINT => {
|
||||
// Handle debug print from guest
|
||||
// The guest sends a character to print via GHCB data
|
||||
// Sample debug print implementation that only prints ASCII characters and ignores the rest
|
||||
// let char_to_print = (ghcb_data & 0xFF) as u8;
|
||||
// if char_to_print.is_ascii() {
|
||||
// debug!("'{}'", char_to_print as char);
|
||||
// }
|
||||
// Not printing the character to slow down the guest a bit,
|
||||
// as these debug prints can be very frequent.
|
||||
// In real implementation, we might want to buffer these characters and
|
||||
// print them out together or implement some rate limiting.]
|
||||
}
|
||||
GHCB_INFO_HYP_FEATURE_REQUEST => {
|
||||
// Pre-condition: GHCB data must be zero
|
||||
assert!(ghcb_data == 0);
|
||||
@@ -1712,6 +1735,16 @@ impl MshvVm {
|
||||
.map_err(|e| vm::HypervisorVmError::CreateDevice(e.into()))?;
|
||||
Ok(VfioDeviceFd::new_from_mshv(device_fd))
|
||||
}
|
||||
|
||||
///
|
||||
/// Sets a partition property.
|
||||
///
|
||||
/// This allows runtime configuration of partition properties.
|
||||
pub fn set_partition_property(&self, code: u32, value: u64) -> anyhow::Result<()> {
|
||||
self.fd
|
||||
.set_partition_property(code, value)
|
||||
.map_err(|e| anyhow!("Failed to set partition property: {e:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
@@ -106,6 +106,38 @@ impl MshvEmulatorContext<'_> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_cpu_state(
|
||||
&self,
|
||||
cpu_id: usize,
|
||||
old_state: <Self as PlatformEmulator>::CpuState,
|
||||
new_state: <Self as PlatformEmulator>::CpuState,
|
||||
) -> Result<(), PlatformError> {
|
||||
if cpu_id != self.vcpu.vp_index as usize {
|
||||
return Err(PlatformError::SetCpuStateFailure(anyhow!(
|
||||
"CPU id mismatch {:?} {:?}",
|
||||
cpu_id,
|
||||
self.vcpu.vp_index
|
||||
)));
|
||||
}
|
||||
|
||||
debug!("mshv emulator: Updating CPU state");
|
||||
debug!("mshv emulator: {:#x?}", new_state.regs);
|
||||
|
||||
self.vcpu
|
||||
.set_regs(&new_state.regs)
|
||||
.map_err(|e| PlatformError::SetCpuStateFailure(e.into()))?;
|
||||
|
||||
if old_state.sregs != new_state.sregs {
|
||||
debug!("mshv emulator: Updating CPU special registers");
|
||||
debug!("mshv emulator: {:#x?}", new_state.sregs);
|
||||
self.vcpu
|
||||
.set_sregs(&new_state.sregs)
|
||||
.map_err(|e| PlatformError::SetCpuStateFailure(e.into()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform emulation for Hyper-V
|
||||
|
||||
@@ -6,7 +6,7 @@ version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
epoll = { workspace = true }
|
||||
getrandom = "0.3.4"
|
||||
getrandom = "0.4.1"
|
||||
libc = { workspace = true }
|
||||
log = { workspace = true }
|
||||
net_gen = { path = "../net_gen" }
|
||||
|
||||
@@ -39,13 +39,10 @@ impl MacAddr {
|
||||
if v[i].len() != 2 {
|
||||
return common_err;
|
||||
}
|
||||
bytes[i] = u8::from_str_radix(v[i], 16).map_err(|e| {
|
||||
io::Error::other(format!(
|
||||
"parsing of {} into a MAC address failed: {}",
|
||||
s.as_ref(),
|
||||
e
|
||||
))
|
||||
})?;
|
||||
if !v[i].bytes().all(|a| a.is_ascii_hexdigit()) {
|
||||
return common_err;
|
||||
}
|
||||
bytes[i] = u8::from_str_radix(v[i], 16).unwrap();
|
||||
}
|
||||
|
||||
Ok(MacAddr { bytes })
|
||||
@@ -187,6 +184,8 @@ mod unit_tests {
|
||||
let bytes = mac.get_bytes();
|
||||
assert_eq!(bytes, [0x12u8, 0x34, 0x56, 0x78, 0x9a, 0xbc]);
|
||||
|
||||
MacAddr::parse_str("12:34:56:78:9a:+c").unwrap_err();
|
||||
|
||||
let s = serde_json::to_string(&mac).expect("MacAddr serialization failed.");
|
||||
assert_eq!(s, "\"12:34:56:78:9a:bc\"");
|
||||
}
|
||||
|
||||
@@ -276,6 +276,17 @@ pub struct MmioRegion {
|
||||
pub(crate) user_memory_regions: Vec<UserMemoryRegion>,
|
||||
}
|
||||
|
||||
impl MmioRegion {
|
||||
/// Returns true if this region has the exact same memory slots as the other region.
|
||||
pub fn has_matching_slots(&self, other: &MmioRegion) -> bool {
|
||||
self.user_memory_regions.len() == other.user_memory_regions.len()
|
||||
&& self
|
||||
.user_memory_regions
|
||||
.iter()
|
||||
.all(|u| other.user_memory_regions.iter().any(|o| o.slot == u.slot))
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// [`Self::find_user_address`] must always either return `Err`
|
||||
|
||||
@@ -173,6 +173,7 @@ pub struct BlockControl {
|
||||
pub struct PerformanceTestControl {
|
||||
test_timeout: u32,
|
||||
test_iterations: u32,
|
||||
warmup_iterations: u32,
|
||||
num_queues: Option<u32>,
|
||||
queue_size: Option<u32>,
|
||||
net_control: Option<(bool, bool)>, // First bool is for RX(true)/TX(false), second bool is for bandwidth or PPS
|
||||
@@ -183,8 +184,8 @@ pub struct PerformanceTestControl {
|
||||
impl fmt::Display for PerformanceTestControl {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut output = format!(
|
||||
"test_timeout = {}s, test_iterations = {}",
|
||||
self.test_timeout, self.test_iterations
|
||||
"test_timeout = {}s, test_iterations = {}, warmup_iterations = {}",
|
||||
self.test_timeout, self.test_iterations, self.warmup_iterations
|
||||
);
|
||||
if let Some(o) = self.num_queues {
|
||||
output = format!("{output}, num_queues = {o}");
|
||||
@@ -212,6 +213,7 @@ impl PerformanceTestControl {
|
||||
Self {
|
||||
test_timeout: 10,
|
||||
test_iterations: 5,
|
||||
warmup_iterations: 0,
|
||||
num_queues: None,
|
||||
queue_size: None,
|
||||
net_control: None,
|
||||
@@ -233,6 +235,17 @@ struct PerformanceTest {
|
||||
|
||||
impl PerformanceTest {
|
||||
pub fn run(&self, overrides: &PerformanceTestOverrides) -> PerformanceTestResult {
|
||||
// Run warmup iterations if configured (results discarded)
|
||||
for _ in 0..self.control.warmup_iterations {
|
||||
if let Some(test_timeout) = overrides.test_timeout {
|
||||
let mut control: PerformanceTestControl = self.control.clone();
|
||||
control.test_timeout = test_timeout;
|
||||
let _ = (self.func_ptr)(&control);
|
||||
} else {
|
||||
let _ = (self.func_ptr)(&self.control);
|
||||
}
|
||||
}
|
||||
|
||||
let mut metrics = Vec::new();
|
||||
for _ in 0..overrides
|
||||
.test_iterations
|
||||
@@ -265,8 +278,9 @@ impl PerformanceTest {
|
||||
// Calculate the timeout for each test
|
||||
// Note: To cover the setup/cleanup time, 20s is added for each iteration of the test
|
||||
pub fn calc_timeout(&self, test_iterations: &Option<u32>, test_timeout: &Option<u32>) -> u64 {
|
||||
((test_timeout.unwrap_or(self.control.test_timeout) + 20)
|
||||
* test_iterations.unwrap_or(self.control.test_iterations)) as u64
|
||||
let total_iterations = test_iterations.unwrap_or(self.control.test_iterations)
|
||||
+ self.control.warmup_iterations;
|
||||
((test_timeout.unwrap_or(self.control.test_timeout) + 20) * total_iterations) as u64
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +333,7 @@ mod adjuster {
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_LIST: [PerformanceTest; 32] = [
|
||||
const TEST_LIST: [PerformanceTest; 60] = [
|
||||
PerformanceTest {
|
||||
name: "boot_time_ms",
|
||||
func_ptr: performance_boot_time,
|
||||
@@ -710,6 +724,282 @@ const TEST_LIST: [PerformanceTest; 32] = [
|
||||
},
|
||||
unit_adjuster: adjuster::identity,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_UNCOMPRESSED_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_random_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::RandomRead,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_UNCOMPRESSED_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_read_warm_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
warmup_iterations: 2,
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_UNCOMPRESSED_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_multi_queue_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_UNCOMPRESSED_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_multi_queue_random_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::RandomRead,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_UNCOMPRESSED_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_multi_queue_read_warm_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(128),
|
||||
warmup_iterations: 2,
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_UNCOMPRESSED_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_zlib_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_ZLIB_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_zlib_random_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::RandomRead,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_ZLIB_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_zlib_read_warm_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
warmup_iterations: 2,
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_ZLIB_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_zlib_multi_queue_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_ZLIB_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_zlib_multi_queue_random_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::RandomRead,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_ZLIB_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_zlib_multi_queue_read_warm_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(128),
|
||||
warmup_iterations: 2,
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_ZLIB_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_zstd_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_ZSTD_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_zstd_random_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::RandomRead,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_ZSTD_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_zstd_read_warm_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
warmup_iterations: 2,
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_ZSTD_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_zstd_multi_queue_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_ZSTD_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_zstd_multi_queue_random_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::RandomRead,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_ZSTD_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_zstd_multi_queue_read_warm_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(128),
|
||||
warmup_iterations: 2,
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: QCOW2_ZSTD_IMG,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_backing_qcow2_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
@@ -740,6 +1030,160 @@ const TEST_LIST: [PerformanceTest; 32] = [
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_backing_raw_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: OVERLAY_WITH_RAW_BACKING,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_backing_raw_random_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::RandomRead,
|
||||
bandwidth: true,
|
||||
test_file: OVERLAY_WITH_RAW_BACKING,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_backing_qcow2_read_warm_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
warmup_iterations: 2,
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: OVERLAY_WITH_QCOW2_BACKING,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_backing_raw_read_warm_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
warmup_iterations: 2,
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: OVERLAY_WITH_RAW_BACKING,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_multi_queue_backing_qcow2_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: OVERLAY_WITH_QCOW2_BACKING,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_multi_queue_backing_qcow2_random_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::RandomRead,
|
||||
bandwidth: true,
|
||||
test_file: OVERLAY_WITH_QCOW2_BACKING,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_multi_queue_backing_raw_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: OVERLAY_WITH_RAW_BACKING,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_multi_queue_backing_raw_random_read_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(128),
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::RandomRead,
|
||||
bandwidth: true,
|
||||
test_file: OVERLAY_WITH_RAW_BACKING,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_multi_queue_backing_qcow2_read_warm_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(128),
|
||||
warmup_iterations: 2,
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: OVERLAY_WITH_QCOW2_BACKING,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_qcow2_multi_queue_backing_raw_read_warm_MiBps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(128),
|
||||
warmup_iterations: 2,
|
||||
block_control: Some(BlockControl {
|
||||
fio_ops: FioOps::Read,
|
||||
bandwidth: true,
|
||||
test_file: OVERLAY_WITH_RAW_BACKING,
|
||||
}),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::Bps_to_MiBps,
|
||||
},
|
||||
];
|
||||
|
||||
fn run_test_with_timeout(
|
||||
|
||||
@@ -35,6 +35,11 @@ enum Error {
|
||||
pub const BLK_IO_TEST_IMG: &str = "/var/tmp/ch-blk-io-test.img";
|
||||
const QCOW2_BACKING_FILE: &str = "/var/tmp/ch-blk-io-test-qcow2-backing.qcow2";
|
||||
pub const OVERLAY_WITH_QCOW2_BACKING: &str = "/var/tmp/ch-blk-io-test-overlay-qcow2.qcow2";
|
||||
const RAW_BACKING_FILE: &str = "/var/tmp/ch-blk-io-test-raw-backing.raw";
|
||||
pub const OVERLAY_WITH_RAW_BACKING: &str = "/var/tmp/ch-blk-io-test-overlay-raw.qcow2";
|
||||
pub const QCOW2_UNCOMPRESSED_IMG: &str = "/var/tmp/ch-blk-io-test-uncompressed.qcow2";
|
||||
pub const QCOW2_ZLIB_IMG: &str = "/var/tmp/ch-blk-io-test-zlib.qcow2";
|
||||
pub const QCOW2_ZSTD_IMG: &str = "/var/tmp/ch-blk-io-test-zstd.qcow2";
|
||||
|
||||
pub fn init_tests(overrides: &PerformanceTestOverrides) {
|
||||
let mut cmd = format!("dd if=/dev/zero of={BLK_IO_TEST_IMG} bs=1M count=4096");
|
||||
@@ -66,6 +71,32 @@ pub fn init_tests(overrides: &PerformanceTestOverrides) {
|
||||
"qemu-img create -f qcow2 -b {QCOW2_BACKING_FILE} -F qcow2 {OVERLAY_WITH_QCOW2_BACKING} 4G"
|
||||
);
|
||||
assert!(exec_host_command_output(&cmd).status.success());
|
||||
|
||||
// RAW backing file for backing file tests
|
||||
cmd = format!("dd if=/dev/zero of={RAW_BACKING_FILE} bs=1M count=4096");
|
||||
assert!(exec_host_command_output(&cmd).status.success());
|
||||
|
||||
// QCOW2 overlay with RAW backing
|
||||
cmd = format!(
|
||||
"qemu-img create -f qcow2 -b {RAW_BACKING_FILE} -F raw {OVERLAY_WITH_RAW_BACKING} 4G"
|
||||
);
|
||||
assert!(exec_host_command_output(&cmd).status.success());
|
||||
|
||||
// Standalone QCOW2 image with no backing file
|
||||
cmd = format!("qemu-img create -f qcow2 -o preallocation=full {QCOW2_UNCOMPRESSED_IMG} 4G");
|
||||
assert!(exec_host_command_output(&cmd).status.success());
|
||||
|
||||
// Zlib compressed QCOW2 image, convert populates actual compressed clusters
|
||||
cmd = format!(
|
||||
"qemu-img convert -f qcow2 -O qcow2 -c -o compression_type=zlib {QCOW2_UNCOMPRESSED_IMG} {QCOW2_ZLIB_IMG}"
|
||||
);
|
||||
assert!(exec_host_command_output(&cmd).status.success());
|
||||
|
||||
// Zstd compressed QCOW2 image, convert populates actual compressed clusters
|
||||
cmd = format!(
|
||||
"qemu-img convert -f qcow2 -O qcow2 -c -o compression_type=zstd {QCOW2_UNCOMPRESSED_IMG} {QCOW2_ZSTD_IMG}"
|
||||
);
|
||||
assert!(exec_host_command_output(&cmd).status.success());
|
||||
}
|
||||
|
||||
pub fn cleanup_tests() {
|
||||
@@ -75,6 +106,16 @@ pub fn cleanup_tests() {
|
||||
.unwrap_or_else(|_| panic!("Failed to remove file '{QCOW2_BACKING_FILE}'."));
|
||||
fs::remove_file(OVERLAY_WITH_QCOW2_BACKING)
|
||||
.unwrap_or_else(|_| panic!("Failed to remove file '{OVERLAY_WITH_QCOW2_BACKING}'."));
|
||||
fs::remove_file(RAW_BACKING_FILE)
|
||||
.unwrap_or_else(|_| panic!("Failed to remove file '{RAW_BACKING_FILE}'."));
|
||||
fs::remove_file(OVERLAY_WITH_RAW_BACKING)
|
||||
.unwrap_or_else(|_| panic!("Failed to remove file '{OVERLAY_WITH_RAW_BACKING}'."));
|
||||
fs::remove_file(QCOW2_UNCOMPRESSED_IMG)
|
||||
.unwrap_or_else(|_| panic!("Failed to remove file '{QCOW2_UNCOMPRESSED_IMG}'."));
|
||||
fs::remove_file(QCOW2_ZLIB_IMG)
|
||||
.unwrap_or_else(|_| panic!("Failed to remove file '{QCOW2_ZLIB_IMG}'."));
|
||||
fs::remove_file(QCOW2_ZSTD_IMG)
|
||||
.unwrap_or_else(|_| panic!("Failed to remove file '{QCOW2_ZSTD_IMG}'."));
|
||||
}
|
||||
|
||||
// Performance tests are expected to be executed sequentially, so we can
|
||||
@@ -153,7 +194,7 @@ pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 {
|
||||
.unwrap();
|
||||
|
||||
let r = std::panic::catch_unwind(|| {
|
||||
guest.wait_vm_boot(None).unwrap();
|
||||
guest.wait_vm_boot().unwrap();
|
||||
measure_virtio_net_throughput(test_timeout, num_queues / 2, &guest, rx, bandwidth).unwrap()
|
||||
});
|
||||
|
||||
@@ -194,7 +235,7 @@ pub fn performance_net_latency(control: &PerformanceTestControl) -> f64 {
|
||||
.unwrap();
|
||||
|
||||
let r = std::panic::catch_unwind(|| {
|
||||
guest.wait_vm_boot(None).unwrap();
|
||||
guest.wait_vm_boot().unwrap();
|
||||
|
||||
// 'ethr' tool will measure the latency multiple times with provided test time
|
||||
let latency = measure_virtio_net_latency(&guest, control.test_timeout).unwrap();
|
||||
@@ -397,6 +438,12 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 {
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let mut test_disk_arg =
|
||||
format!("path={test_file},queue_size={queue_size},num_queues={num_queues}");
|
||||
if test_file == OVERLAY_WITH_QCOW2_BACKING || test_file == OVERLAY_WITH_RAW_BACKING {
|
||||
test_disk_arg.push_str(",backing_files=on");
|
||||
}
|
||||
|
||||
let mut child = GuestCommand::new(&guest)
|
||||
.args(["--cpus", &format!("boot={num_queues}")])
|
||||
.args(["--memory", "size=4G"])
|
||||
@@ -414,7 +461,7 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 {
|
||||
guest.disk_config.disk(DiskType::CloudInit).unwrap()
|
||||
)
|
||||
.as_str(),
|
||||
format!("path={test_file},queue_size={queue_size},num_queues={num_queues}").as_str(),
|
||||
test_disk_arg.as_str(),
|
||||
])
|
||||
.default_net()
|
||||
.args(["--api-socket", &api_socket])
|
||||
@@ -425,7 +472,7 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 {
|
||||
.unwrap();
|
||||
|
||||
let r = std::panic::catch_unwind(|| {
|
||||
guest.wait_vm_boot(None).unwrap();
|
||||
guest.wait_vm_boot().unwrap();
|
||||
|
||||
let fio_command = format!(
|
||||
"sudo fio --filename=/dev/vdc --name=test --output-format=json \
|
||||
|
||||
320
release-notes.md
320
release-notes.md
@@ -1,19 +1,28 @@
|
||||
- [v51.0](#v510)
|
||||
- [Security Fixes](#security-fixes)
|
||||
- [Significant QCOW2 v3 Improvements](#significant-qcow2-v3-improvements)
|
||||
- [ACPI Generic Initiator Support](#acpi-generic-initiator-support)
|
||||
- [Block Device DISCARD and WRITE_ZEROES Support](#block-device-discard-and-write_zeroes-support)
|
||||
- [Notable Performance Improvements](#notable-performance-improvements)
|
||||
- [MSHV Support Improvements](#mshv-support-improvements)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes)
|
||||
- [Contributors](#contributors)
|
||||
- [v50.0](#v500)
|
||||
- [Configurable Nested Virtualization Option on x86_64](#configurable-nested-virtualization-option-on-x86_64)
|
||||
- [Compression Support for QCOW2](#compression-support-for-qcow2)
|
||||
- [Notable Performance Improvements](#notable-performance-improvements)
|
||||
- [Notable Performance Improvements](#notable-performance-improvements-1)
|
||||
- [Live Disk Resizing Support for Raw Images](#live-disk-resizing-support-for-raw-images)
|
||||
- [Developer Experience Improvements](#developer-experience-improvements)
|
||||
- [Improved File-level Locking Support](#improved-file-level-locking-support)
|
||||
- [Live Disk Resizing Support for Raw Images](#live-disk-resizing-support-for-raw-images)
|
||||
- [Logging Improvements](#logging-improvements)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes)
|
||||
- [Contributors](#contributors)
|
||||
- [v49.0](#v490)
|
||||
- [MSHV Support Improvements](#mshv-support-improvements)
|
||||
- [Logging Improvements](#logging-improvements-1)
|
||||
- [Removed Default IP and Mask for `virtio-net` Devices](#removed-default-ip-and-mask-for-virtio-net-devices)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-1)
|
||||
- [Contributors](#contributors-1)
|
||||
- [v49.0](#v490)
|
||||
- [MSHV Support Improvements](#mshv-support-improvements-1)
|
||||
- [Logging Improvements](#logging-improvements-1)
|
||||
- [Removed Default IP and Mask for `virtio-net` Devices](#removed-default-ip-and-mask-for-virtio-net-devices)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-2)
|
||||
- [Contributors](#contributors-2)
|
||||
- [v48.0](#v480)
|
||||
- [Experimental `fw_cfg` Device Support](#experimental-fw_cfg-device-support)
|
||||
- [Experimental `ivshmem` Device Support](#experimental-ivshmem-device-support)
|
||||
@@ -24,62 +33,62 @@
|
||||
- [Updated Documentation on Windows Guest Support](#updated-documentation-on-windows-guest-support)
|
||||
- [Policy on AI Generated Code](#policy-on-ai-generated-code)
|
||||
- [Removed SGX Support](#removed-sgx-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-2)
|
||||
- [Contributors](#contributors-2)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-3)
|
||||
- [Contributors](#contributors-3)
|
||||
- [v47.0](#v470)
|
||||
- [Block Device Error Reporting to the Guest](#block-device-error-reporting-to-the-guest)
|
||||
- [Nice Error Messages on Exit](#nice-error-messages-on-exit)
|
||||
- [Alphabetically Sorted CLI Options for ch-remote](#alphabetically-sorted-cli-options-for-ch-remote)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-3)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-4)
|
||||
- [Deprecations](#deprecations)
|
||||
- [Contributors](#contributors-3)
|
||||
- [Contributors](#contributors-4)
|
||||
- [v46.0](#v460)
|
||||
- [File-level Locking Support with `--disk`](#file-level-locking-support-with---disk)
|
||||
- [Improved Error Reporting with VM Resizing](#improved-error-reporting-with-vm-resizing)
|
||||
- [IPv6 Address Support with `--net`](#ipv6-address-support-with---net)
|
||||
- [Experimental AArch64 Support with the MSHV Hypervisor](#experimental-aarch64-support-with-the-mshv-hypervisor)
|
||||
- [Deprecated SGX Support](#deprecated-sgx-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-4)
|
||||
- [Contributors](#contributors-4)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-5)
|
||||
- [Contributors](#contributors-5)
|
||||
- [v45.0](#v450)
|
||||
- [Experimental `riscv64` Architecture Support](#experimental-riscv64-architecture-support)
|
||||
- [Alphabetically Sorted CLI Options](#alphabetically-sorted-cli-options)
|
||||
- [Improved Downtime of VM Live Migration](#improved-downtime-of-vm-live-migration)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-5)
|
||||
- [Contributors](#contributors-5)
|
||||
- [v44.0](#v440)
|
||||
- [Configurable `virtio-iommu` Address Width](#configurable-virtio-iommu-address-width)
|
||||
- [Notable Performance Improvements](#notable-performance-improvements-1)
|
||||
- [New Fuzzers](#new-fuzzers)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-6)
|
||||
- [Contributors](#contributors-6)
|
||||
- [v43.0](#v430)
|
||||
- [Live Migration over TCP Connections](#live-migration-over-tcp-connections)
|
||||
- [v44.0](#v440)
|
||||
- [Configurable `virtio-iommu` Address Width](#configurable-virtio-iommu-address-width)
|
||||
- [Notable Performance Improvements](#notable-performance-improvements-2)
|
||||
- [New Fuzzers](#new-fuzzers)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-7)
|
||||
- [Contributors](#contributors-7)
|
||||
- [v43.0](#v430)
|
||||
- [Live Migration over TCP Connections](#live-migration-over-tcp-connections)
|
||||
- [Notable Performance Improvements](#notable-performance-improvements-3)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-8)
|
||||
- [Contributors](#contributors-8)
|
||||
- [v42.0](#v420)
|
||||
- [SVE/SVE2 Support on AArch64](#svesve2-support-on-aarch64)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-8)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-9)
|
||||
- [Sponsorships](#sponsorships)
|
||||
- [Contributors](#contributors-8)
|
||||
- [Contributors](#contributors-9)
|
||||
- [v41.0](#v410)
|
||||
- [Experimental "Pvmemcontrol" Support](#experimental-pvmemcontrol-support)
|
||||
- [Sandboxing With Landlock Support](#sandboxing-with-landlock-support)
|
||||
- [Notable Performance Improvements](#notable-performance-improvements-3)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-9)
|
||||
- [Contributors](#contributors-9)
|
||||
- [v40.0](#v400)
|
||||
- [Support for Restoring File Descriptor Backed Network Devices](#support-for-restoring-file-descriptor-backed-network-devices)
|
||||
- [Notable Performance Improvements](#notable-performance-improvements-4)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-10)
|
||||
- [Contributors](#contributors-10)
|
||||
- [v40.0](#v400)
|
||||
- [Support for Restoring File Descriptor Backed Network Devices](#support-for-restoring-file-descriptor-backed-network-devices)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-11)
|
||||
- [Contributors](#contributors-11)
|
||||
- [v39.0](#v390)
|
||||
- [Variable Sizing of PCI Apertures for Segments](#variable-sizing-of-pci-apertures-for-segments)
|
||||
- [Direct Booting with bzImages](#direct-booting-with-bzimages)
|
||||
- [Support for NVIDIA GPUDirect P2P Support](#support-for-nvidia-gpudirect-p2p-support)
|
||||
- [Guest NMI Injection Support](#guest-nmi-injection-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-11)
|
||||
- [Contributors](#contributors-11)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-12)
|
||||
- [Contributors](#contributors-12)
|
||||
- [v38.0](#v380)
|
||||
- [Group Rate Limiter on Block Devices](#group-rate-limiter-on-block-devices)
|
||||
- [CPU Pinning Support for Block Device Worker Thread](#cpu-pinning-support-for-block-device-worker-thread)
|
||||
@@ -87,16 +96,16 @@
|
||||
- [New 'debug-console' Device](#new-debug-console-device)
|
||||
- [Improved VFIO Device Support](#improved-vfio-device-support)
|
||||
- [Extended CPU Affinity Support](#extended-cpu-affinity-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-12)
|
||||
- [Contributors](#contributors-12)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-13)
|
||||
- [Contributors](#contributors-13)
|
||||
- [v37.0](#v370)
|
||||
- [Long Term Support (LTS) Release](#long-term-support-lts-release)
|
||||
- [Multiple PCI segments Support for 32-bit VFIO devices](#multiple-pci-segments-support-for-32-bit-vfio-devices)
|
||||
- [Configurable Named TAP Devices](#configurable-named-tap-devices)
|
||||
- [TTY Output from Both Serial Device and Virtio Console](#tty-output-from-both-serial-device-and-virtio-console)
|
||||
- [Faster VM Restoration from Snapshots](#faster-vm-restoration-from-snapshots)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-13)
|
||||
- [Contributors](#contributors-13)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-14)
|
||||
- [Contributors](#contributors-14)
|
||||
- [v36.0](#v360)
|
||||
- [Command Line Changes](#command-line-changes)
|
||||
- [Enabled Features Reported via API Endpoint and CLI](#enabled-features-reported-via-api-endpoint-and-cli)
|
||||
@@ -105,31 +114,31 @@
|
||||
- [Unix Socket Backend for Serial Port](#unix-socket-backend-for-serial-port)
|
||||
- [AIO Backend for Block Devices](#aio-backend-for-block-devices)
|
||||
- [Documentation Improvements](#documentation-improvements)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-14)
|
||||
- [Contributors](#contributors-14)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-15)
|
||||
- [Contributors](#contributors-15)
|
||||
- [v35.0](#v350)
|
||||
- [`virtio-vsock` Support for Linux Guest Kernel v6.3+](#virtio-vsock-support-for-linux-guest-kernel-v63)
|
||||
- [User Specified Serial Number for `virtio-block`](#user-specified-serial-number-for-virtio-block)
|
||||
- [vCPU TSC Frequency Included in Migration State](#vcpu-tsc-frequency-included-in-migration-state)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-15)
|
||||
- [Contributors](#contributors-15)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-16)
|
||||
- [Contributors](#contributors-16)
|
||||
- [v34.0](#v340)
|
||||
- [Paravirtualised Panic Device Support](#paravirtualised-panic-device-support)
|
||||
- [Improvements to VM Core Dump](#improvements-to-vm-core-dump)
|
||||
- [QCOW2 Support for Backing Files](#qcow2-support-for-backing-files)
|
||||
- [Minimum Host Kernel Bump](#minimum-host-kernel-bump)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-16)
|
||||
- [Contributors](#contributors-16)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-17)
|
||||
- [Contributors](#contributors-17)
|
||||
- [v33.0](#v330)
|
||||
- [D-Bus based API](#d-bus-based-api)
|
||||
- [Expose Host CPU Cache Details for AArch64](#expose-host-cpu-cache-details-for-aarch64)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-17)
|
||||
- [Contributors](#contributors-17)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-18)
|
||||
- [Contributors](#contributors-18)
|
||||
- [v32.0](#v320)
|
||||
- [Increased PCI Segment Limit](#increased-pci-segment-limit)
|
||||
- [API Changes](#api-changes)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-18)
|
||||
- [Contributors](#contributors-18)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-19)
|
||||
- [Contributors](#contributors-19)
|
||||
- [v31.1](#v311)
|
||||
- [v31.0](#v310)
|
||||
- [Update to Latest `acpi_tables`](#update-to-latest-acpi_tables)
|
||||
@@ -137,15 +146,15 @@
|
||||
- [Improvements on Console `SIGWINCH` Handler](#improvements-on-console-sigwinch-handler)
|
||||
- [Remove Directory Support from `MemoryZoneConfig::file`](#remove-directory-support-from-memoryzoneconfigfile)
|
||||
- [Documentation Improvements](#documentation-improvements-1)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-19)
|
||||
- [Contributors](#contributors-19)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-20)
|
||||
- [Contributors](#contributors-20)
|
||||
- [v30.0](#v300)
|
||||
- [Command Line Changes for Reduced Binary Size](#command-line-changes-for-reduced-binary-size)
|
||||
- [Basic vfio-user Server Support](#basic-vfio-user-server-support)
|
||||
- [Heap Profiling Support](#heap-profiling-support)
|
||||
- [Documentation Improvements](#documentation-improvements-2)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-20)
|
||||
- [Contributors](#contributors-20)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-21)
|
||||
- [Contributors](#contributors-21)
|
||||
- [v28.2](#v282)
|
||||
- [v29.0](#v290)
|
||||
- [Release Binary Supports Both MSHV and KVM](#release-binary-supports-both-mshv-and-kvm)
|
||||
@@ -155,10 +164,10 @@
|
||||
- [`AArch64` Documentation Integration](#aarch64-documentation-integration)
|
||||
- [`virtio-block` Counters Enhancement](#virtio-block-counters-enhancement)
|
||||
- [TCP Offload Control](#tcp-offload-control)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-21)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-22)
|
||||
- [Removals](#removals)
|
||||
- [Deprecations](#deprecations-1)
|
||||
- [Contributors](#contributors-21)
|
||||
- [Contributors](#contributors-22)
|
||||
- [v28.1](#v281)
|
||||
- [v28.0](#v280)
|
||||
- [Community Engagement (Reminder)](#community-engagement-reminder)
|
||||
@@ -166,9 +175,9 @@
|
||||
- [Virtualised TPM Support](#virtualised-tpm-support)
|
||||
- [Transparent Huge Page Support](#transparent-huge-page-support)
|
||||
- [README Quick Start Improved](#readme-quick-start-improved)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-22)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-23)
|
||||
- [Removals](#removals-1)
|
||||
- [Contributors](#contributors-22)
|
||||
- [Contributors](#contributors-23)
|
||||
- [v27.0](#v270)
|
||||
- [Community Engagement](#community-engagement)
|
||||
- [Prebuilt Packages](#prebuilt-packages)
|
||||
@@ -177,41 +186,41 @@
|
||||
- [Simplified Build Feature Flags](#simplified-build-feature-flags)
|
||||
- [Asynchronous Kernel Loading](#asynchronous-kernel-loading)
|
||||
- [GDB Support for AArch64](#gdb-support-for-aarch64)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-23)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-24)
|
||||
- [Deprecations](#deprecations-2)
|
||||
- [Contributors](#contributors-23)
|
||||
- [Contributors](#contributors-24)
|
||||
- [v26.0](#v260)
|
||||
- [SMBIOS Improvements via `--platform`](#smbios-improvements-via---platform)
|
||||
- [Unified Binary MSHV and KVM Support](#unified-binary-mshv-and-kvm-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-24)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-25)
|
||||
- [Deprecations](#deprecations-3)
|
||||
- [Removals](#removals-2)
|
||||
- [Contributors](#contributors-24)
|
||||
- [Contributors](#contributors-25)
|
||||
- [v25.0](#v250)
|
||||
- [`ch-remote` Improvements](#ch-remote-improvements-1)
|
||||
- [VM "Coredump" Support](#vm-coredump-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-25)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-26)
|
||||
- [Removals](#removals-3)
|
||||
- [Contributors](#contributors-25)
|
||||
- [Contributors](#contributors-26)
|
||||
- [v24.0](#v240)
|
||||
- [Bypass Mode for `virtio-iommu`](#bypass-mode-for-virtio-iommu)
|
||||
- [Ensure Identifiers Uniqueness](#ensure-identifiers-uniqueness)
|
||||
- [Sparse Mmap support](#sparse-mmap-support)
|
||||
- [Expose Platform Serial Number](#expose-platform-serial-number)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-26)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-27)
|
||||
- [Notable Improvements](#notable-improvements)
|
||||
- [Deprecations](#deprecations-4)
|
||||
- [New on the Website](#new-on-the-website)
|
||||
- [Contributors](#contributors-26)
|
||||
- [Contributors](#contributors-27)
|
||||
- [v23.1](#v231)
|
||||
- [v23.0](#v230)
|
||||
- [vDPA Support](#vdpa-support)
|
||||
- [Updated OS Support list](#updated-os-support-list)
|
||||
- [`AArch64` Memory Map Improvements](#aarch64-memory-map-improvements)
|
||||
- [`AMX` Support](#amx-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-27)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-28)
|
||||
- [Deprecations](#deprecations-5)
|
||||
- [Contributors](#contributors-27)
|
||||
- [Contributors](#contributors-28)
|
||||
- [v22.1](#v221)
|
||||
- [v22.0](#v220)
|
||||
- [GDB Debug Stub Support](#gdb-debug-stub-support)
|
||||
@@ -222,13 +231,13 @@
|
||||
- [PMU Support for AArch64](#pmu-support-for-aarch64)
|
||||
- [Documentation Under CC-BY-4.0 License](#documentation-under-cc-by-40-license)
|
||||
- [Deprecation of "Classic" `virtiofsd`](#deprecation-of-classic-virtiofsd)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-28)
|
||||
- [Contributors](#contributors-28)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-29)
|
||||
- [Contributors](#contributors-29)
|
||||
- [v21.0](#v210)
|
||||
- [Efficient Local Live Migration (for Live Upgrade)](#efficient-local-live-migration-for-live-upgrade)
|
||||
- [Recommended Kernel is Now 5.15](#recommended-kernel-is-now-515)
|
||||
- [Notable Bug fixes](#notable-bug-fixes-29)
|
||||
- [Contributors](#contributors-29)
|
||||
- [Notable Bug fixes](#notable-bug-fixes-30)
|
||||
- [Contributors](#contributors-30)
|
||||
- [v20.2](#v202)
|
||||
- [v20.1](#v201)
|
||||
- [v20.0](#v200)
|
||||
@@ -237,8 +246,8 @@
|
||||
- [Improved VFIO support](#improved-vfio-support)
|
||||
- [Safer code](#safer-code)
|
||||
- [Extended documentation](#extended-documentation)
|
||||
- [Notable bug fixes](#notable-bug-fixes-30)
|
||||
- [Contributors](#contributors-30)
|
||||
- [Notable bug fixes](#notable-bug-fixes-31)
|
||||
- [Contributors](#contributors-31)
|
||||
- [v19.0](#v190)
|
||||
- [Improved PTY handling for serial and `virtio-console`](#improved-pty-handling-for-serial-and-virtio-console)
|
||||
- [PCI boot time optimisations](#pci-boot-time-optimisations)
|
||||
@@ -246,8 +255,8 @@
|
||||
- [Live migration enhancements](#live-migration-enhancements)
|
||||
- [`virtio-mem` support with `vfio-user`](#virtio-mem-support-with-vfio-user)
|
||||
- [AArch64 for `virtio-iommu`](#aarch64-for-virtio-iommu)
|
||||
- [Notable bug fixes](#notable-bug-fixes-31)
|
||||
- [Contributors](#contributors-31)
|
||||
- [Notable bug fixes](#notable-bug-fixes-32)
|
||||
- [Contributors](#contributors-32)
|
||||
- [v18.0](#v180)
|
||||
- [Experimental User Device (`vfio-user`) support](#experimental-user-device-vfio-user-support)
|
||||
- [Migration support for `vhost-user` devices](#migration-support-for-vhost-user-devices)
|
||||
@@ -257,23 +266,23 @@
|
||||
- [Live migration on MSHV hypervisor](#live-migration-on-mshv-hypervisor)
|
||||
- [AArch64 CPU topology support](#aarch64-cpu-topology-support)
|
||||
- [Power button support on AArch64](#power-button-support-on-aarch64)
|
||||
- [Notable bug fixes](#notable-bug-fixes-32)
|
||||
- [Contributors](#contributors-32)
|
||||
- [Notable bug fixes](#notable-bug-fixes-33)
|
||||
- [Contributors](#contributors-33)
|
||||
- [v17.0](#v170)
|
||||
- [ARM64 NUMA support using ACPI](#arm64-numa-support-using-acpi)
|
||||
- [`Seccomp` support for MSHV backend](#seccomp-support-for-mshv-backend)
|
||||
- [Hotplug of `macvtap` devices](#hotplug-of-macvtap-devices)
|
||||
- [Improved SGX support](#improved-sgx-support)
|
||||
- [Inflight tracking for `vhost-user` devices](#inflight-tracking-for-vhost-user-devices)
|
||||
- [Notable bug fixes](#notable-bug-fixes-33)
|
||||
- [Contributors](#contributors-33)
|
||||
- [Notable bug fixes](#notable-bug-fixes-34)
|
||||
- [Contributors](#contributors-34)
|
||||
- [v16.0](#v160)
|
||||
- [Improved live migration support](#improved-live-migration-support)
|
||||
- [Improved `vhost-user` support](#improved-vhost-user-support)
|
||||
- [ARM64 ACPI and UEFI support](#arm64-acpi-and-uefi-support)
|
||||
- [Notable bug fixes](#notable-bug-fixes-34)
|
||||
- [Notable bug fixes](#notable-bug-fixes-35)
|
||||
- [Removed functionality](#removed-functionality)
|
||||
- [Contributors](#contributors-34)
|
||||
- [Contributors](#contributors-35)
|
||||
- [v15.0](#v150)
|
||||
- [Version numbering and stability guarantees](#version-numbering-and-stability-guarantees)
|
||||
- [Network device rate limiting](#network-device-rate-limiting)
|
||||
@@ -281,7 +290,7 @@
|
||||
- [`--api-socket` supports file descriptor parameter](#--api-socket-supports-file-descriptor-parameter)
|
||||
- [Bug fixes](#bug-fixes)
|
||||
- [Deprecations](#deprecations-6)
|
||||
- [Contributors](#contributors-35)
|
||||
- [Contributors](#contributors-36)
|
||||
- [v0.14.1](#v0141)
|
||||
- [v0.14.0](#v0140)
|
||||
- [Structured event monitoring](#structured-event-monitoring)
|
||||
@@ -291,7 +300,7 @@
|
||||
- [PTY control for serial and `virtio-console`](#pty-control-for-serial-and-virtio-console)
|
||||
- [Block device rate limiting](#block-device-rate-limiting)
|
||||
- [Deprecations](#deprecations-7)
|
||||
- [Contributors](#contributors-36)
|
||||
- [Contributors](#contributors-37)
|
||||
- [v0.13.0](#v0130)
|
||||
- [Wider VFIO device support](#wider-vfio-device-support)
|
||||
- [Improved huge page support](#improved-huge-page-support)
|
||||
@@ -299,13 +308,13 @@
|
||||
- [VHD disk image support](#vhd-disk-image-support)
|
||||
- [Improved Virtio device threading](#improved-virtio-device-threading)
|
||||
- [Clean shutdown support via synthetic power button](#clean-shutdown-support-via-synthetic-power-button)
|
||||
- [Contributors](#contributors-37)
|
||||
- [Contributors](#contributors-38)
|
||||
- [v0.12.0](#v0120)
|
||||
- [ARM64 enhancements](#arm64-enhancements)
|
||||
- [Removal of `vhost-user-net` and `vhost-user-block` self spawning](#removal-of-vhost-user-net-and-vhost-user-block-self-spawning)
|
||||
- [Migration of `vhost-user-fs` backend](#migration-of-vhost-user-fs-backend)
|
||||
- [Enhanced "info" API](#enhanced-info-api)
|
||||
- [Contributors](#contributors-38)
|
||||
- [Contributors](#contributors-39)
|
||||
- [v0.11.0](#v0110)
|
||||
- [`io_uring` support by default for `virtio-block`](#io_uring-support-by-default-for-virtio-block)
|
||||
- [Windows Guest Support](#windows-guest-support)
|
||||
@@ -317,15 +326,15 @@
|
||||
- [Default Log Level Changed](#default-log-level-changed)
|
||||
- [New `--balloon` Parameter Added](#new---balloon-parameter-added)
|
||||
- [Experimental `virtio-watchdog` Support](#experimental-virtio-watchdog-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-35)
|
||||
- [Contributors](#contributors-39)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-36)
|
||||
- [Contributors](#contributors-40)
|
||||
- [v0.10.0](#v0100)
|
||||
- [`virtio-block` Support for Multiple Descriptors](#virtio-block-support-for-multiple-descriptors)
|
||||
- [Memory Zones](#memory-zones)
|
||||
- [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements)
|
||||
- [Preliminary KVM HyperV Emulation Control](#preliminary-kvm-hyperv-emulation-control)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-36)
|
||||
- [Contributors](#contributors-40)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-37)
|
||||
- [Contributors](#contributors-41)
|
||||
- [v0.9.0](#v090)
|
||||
- [`io_uring` Based Block Device Support](#io_uring-based-block-device-support)
|
||||
- [Block and Network Device Statistics](#block-and-network-device-statistics)
|
||||
@@ -338,17 +347,17 @@
|
||||
- [Enhancements to ARM64 Support](#enhancements-to-arm64-support)
|
||||
- [Intel SGX Support](#intel-sgx-support)
|
||||
- [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements-1)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-37)
|
||||
- [Contributors](#contributors-41)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-38)
|
||||
- [Contributors](#contributors-42)
|
||||
- [v0.8.0](#v080)
|
||||
- [Experimental Snapshot and Restore Support](#experimental-snapshot-and-restore-support)
|
||||
- [Experimental ARM64 Support](#experimental-arm64-support)
|
||||
- [Support for Using 5-level Paging in Guests](#support-for-using-5-level-paging-in-guests)
|
||||
- [Virtio Device Interrupt Suppression for Network Devices](#virtio-device-interrupt-suppression-for-network-devices)
|
||||
- [`vhost_user_fs` Improvements](#vhost_user_fs-improvements)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-38)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-39)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes)
|
||||
- [Contributors](#contributors-42)
|
||||
- [Contributors](#contributors-43)
|
||||
- [v0.7.0](#v070)
|
||||
- [Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug](#block-network-persistent-memory-pmem-virtiofs-and-vsock-hotplug)
|
||||
- [Alternative `libc` Support](#alternative-libc-support)
|
||||
@@ -358,14 +367,14 @@
|
||||
- [`Seccomp` Sandboxing](#seccomp-sandboxing)
|
||||
- [Updated Distribution Support](#updated-distribution-support)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes-1)
|
||||
- [Contributors](#contributors-43)
|
||||
- [Contributors](#contributors-44)
|
||||
- [v0.6.0](#v060)
|
||||
- [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug)
|
||||
- [Shared Filesystem Improvements](#shared-filesystem-improvements)
|
||||
- [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading)
|
||||
- [Command Line Interface](#command-line-interface)
|
||||
- [PVH Boot](#pvh-boot)
|
||||
- [Contributors](#contributors-44)
|
||||
- [Contributors](#contributors-45)
|
||||
- [v0.5.1](#v051)
|
||||
- [v0.5.0](#v050)
|
||||
- [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing)
|
||||
@@ -373,7 +382,7 @@
|
||||
- [New Interrupt Management Framework](#new-interrupt-management-framework)
|
||||
- [Development Tools](#development-tools)
|
||||
- [Kata Containers Integration](#kata-containers-integration)
|
||||
- [Contributors](#contributors-45)
|
||||
- [Contributors](#contributors-46)
|
||||
- [v0.4.0](#v040)
|
||||
- [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition)
|
||||
- [Programmatic firmware tables generation](#programmatic-firmware-tables-generation)
|
||||
@@ -382,7 +391,7 @@
|
||||
- [Userspace IOAPIC by default](#userspace-ioapic-by-default)
|
||||
- [PCI BAR reprogramming](#pci-bar-reprogramming)
|
||||
- [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization)
|
||||
- [Contributors](#contributors-46)
|
||||
- [Contributors](#contributors-47)
|
||||
- [v0.3.0](#v030)
|
||||
- [Block device offloading](#block-device-offloading)
|
||||
- [Network device backend](#network-device-backend)
|
||||
@@ -409,6 +418,131 @@
|
||||
- [Unit testing](#unit-testing)
|
||||
- [Integration tests parallelization](#integration-tests-parallelization)
|
||||
|
||||
# v51.0
|
||||
|
||||
This release has been tracked in [v51.0
|
||||
group](https://github.com/orgs/cloud-hypervisor/projects/6/views/6?filterQuery=release%3A%22Release+51%22)
|
||||
of our [roadmap project](https://github.com/orgs/cloud-hypervisor/projects/6/).
|
||||
|
||||
### Security Fixes
|
||||
|
||||
This release fixes a security vulnerability in disk image handling.
|
||||
Details can be found in
|
||||
[GHSA-jmr4-g2hv-mjj6](https://github.com/cloud-hypervisor/cloud-hypervisor/security/advisories/GHSA-jmr4-g2hv-mjj6).
|
||||
|
||||
* A new `backing_files=on|off` option has been added to `--disk` to
|
||||
explicitly control whether QCOW2 backing files are permitted. This
|
||||
defaults to `off` to prevent the loading of backing files entirely.
|
||||
(#7685)
|
||||
* Explicit image type specification via the user interface, removing
|
||||
reliance on format autodetection (#7728).
|
||||
* Prevent sector-zero writes for autodetected raw images (#7728).
|
||||
|
||||
### Significant QCOW2 v3 Improvements
|
||||
|
||||
A large number of QCOW2 v3 specification features have been implemented:
|
||||
|
||||
* RAW backing file support for QCOW2 overlays (#7570)
|
||||
* Zero bit in L2 entries (#7627)
|
||||
* Incompatible feature bit validation (#7612)
|
||||
* Dirty bit support (#7636)
|
||||
* Variable refcount widths (1 to 64-bit) (#7633)
|
||||
* Corrupt bit detection and marking (#7639)
|
||||
* Autoclear feature bits handling (#7648)
|
||||
* Thread safety fix for multiple virtio queues (`num_queues > 1`)
|
||||
(#7661)
|
||||
* Correct zero-fill for reads beyond backing file size (#7678)
|
||||
* Live disk resize support (#7687)
|
||||
|
||||
### ACPI Generic Initiator Support
|
||||
|
||||
ACPI Generic Initiator Affinity (SRAT Type 5) support has been added
|
||||
to associate VFIO-PCI devices with dedicated memory/CPU-less NUMA
|
||||
nodes. This enables the guest OS to make NUMA-aware memory allocation
|
||||
decisions for device workloads. A new `device_id` parameter has been
|
||||
added to `--numa` for specifying VFIO devices. (#7626)
|
||||
|
||||
### Block Device DISCARD and WRITE_ZEROES Support
|
||||
|
||||
The `virtio-blk` device now supports `DISCARD` and `WRITE_ZEROES`
|
||||
operations for QCOW2 and RAW image formats. This enables thin
|
||||
provisioning and efficient space reclamation when guests trim
|
||||
filesystems. A new `sparse=on|off` option has been added to `--disk` to
|
||||
control disk space management: `sparse=on` (default) enables thin
|
||||
provisioning with space reclamation, while `sparse=off` provides thick
|
||||
provisioning with consistent I/O latency. (#7666)
|
||||
|
||||
### Notable Performance Improvements
|
||||
|
||||
* Transparent Huge Pages (THP) support has been extended to cover
|
||||
anonymous shared memory (`shared=on`) via `madvise`. Previously, THP
|
||||
was only used for non-shared memory. (#7646)
|
||||
* The `vhost-user-net` device now uses the default set of vhost-user
|
||||
virtio features, including `VIRTIO_F_RING_INDIRECT_DESC`, which
|
||||
provides a performance improvement. (#7653)
|
||||
|
||||
### MSHV Support Improvements
|
||||
|
||||
* Optimize CPU state update after emulation by only updating special
|
||||
registers when changed (#7603)
|
||||
* Enable SMT for guests with `threads_per_core > 1` (#7668)
|
||||
* Stub `save_data_tables()` to unblock VM pause/resume (#7692)
|
||||
* Handle `GHCB_INFO_SPECIAL_DBGPRINT` VMG exit in SEV-SNP guest exit
|
||||
handler (#7703)
|
||||
* Fix CVM boot failure on MSHV (#7548)
|
||||
* Fix CPU topology detection for multithreaded configurations (#7576)
|
||||
|
||||
### Notable Bug Fixes
|
||||
|
||||
* Fix VFIO device hot-remove leaving group and container file
|
||||
descriptors open, preventing re-add (#7676)
|
||||
* Fix snapshot restore when backing file is on read-only storage with
|
||||
`shared=false` (#7674)
|
||||
* Enforce `VIRTIO_BLK_F_RO` even if guest does not negotiate it
|
||||
(#7705)
|
||||
* Fix read-only block device FLUSH requests from OVMF preventing VMs
|
||||
from booting (#7706)
|
||||
* Fix vhost-user device not properly dropping unowned file descriptors
|
||||
(#7679)
|
||||
* Fix `vhost-user-block` `get_config` interoperability (#7617)
|
||||
* Fix vsock TOCTOU race condition by copying packet header from guest
|
||||
memory before processing (#7530)
|
||||
* Fix vsock handling of large TX packets spanning multiple data
|
||||
descriptors (#7680)
|
||||
* Add `gettid()` to all seccomp filters (#7596)
|
||||
* Fix MAC address parsing that wrongly allowed `+` instead of hex
|
||||
characters (#7579)
|
||||
* Improve UUID parse error message and `--net` fd help text (#7702)
|
||||
* Fix various inconsistencies in our OpenAPI specification file
|
||||
(#7716, #7726)
|
||||
* Various documentation fixes (#7602, #7606)
|
||||
|
||||
### Contributors
|
||||
|
||||
Many thanks to everyone who has contributed to our release:
|
||||
|
||||
* Aastha Rawat <aastharawat@microsoft.com>
|
||||
* Alyssa Ross <hi@alyssa.is>
|
||||
* Anatol Belski <anbelski@linux.microsoft.com>
|
||||
* Anirudh Rayabharam <anrayabh@microsoft.com>
|
||||
* Bo Chen <bchen@crusoe.ai>
|
||||
* Champ-Goblem <cameron@northflank.com>
|
||||
* Changyuan Lyu <changyuanl@google.com>
|
||||
* Damian Barabonkov <dbctl@pm.me>
|
||||
* Demi Marie Obenour <demiobenour@gmail.com>
|
||||
* Leander Kohler <leander.kohler@cyberus-technology.de>
|
||||
* Muminul Islam <muislam@microsoft.com>
|
||||
* Philipp Schuster <philipp.schuster@cyberus-technology.de>
|
||||
* Rob Bradford <rbradford@meta.com>
|
||||
* Rowen-Ye <rowenye1@gmail.com>
|
||||
* Saravanan D <saravanand@crusoe.ai>
|
||||
* Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
|
||||
* Thomas Leroy <thomas.leroy.mp@gmail.com>
|
||||
* Wei Liu <liuwe@microsoft.com>
|
||||
* Yi Wang <foxywang@tencent.com>
|
||||
* Zhibin Li <banlu.lzb@antgroup.com>
|
||||
* stevenhorsman <steven@uk.ibm.com>
|
||||
|
||||
# v50.0
|
||||
|
||||
This release has been tracked in [v50.0
|
||||
|
||||
@@ -21,12 +21,12 @@ build_edk2() {
|
||||
fi
|
||||
|
||||
# Prepare source code
|
||||
checkout_repo "$EDK2_DIR" "$EDK2_REPO" master "46b4606ba23498d3d0e66b53e498eb3d5d592586"
|
||||
checkout_repo "$EDK2_DIR" "$EDK2_REPO" master "22130dcd98b4d4b76ac8d922adb4a2dbc86fa52c"
|
||||
pushd "$EDK2_DIR" || exit
|
||||
git submodule update --init
|
||||
popd || exit
|
||||
checkout_repo "$EDK2_PLAT_DIR" "$EDK2_PLAT_REPO" master "8227e9e9f6a8aefbd772b40138f835121ccb2307"
|
||||
checkout_repo "$ACPICA_DIR" "$ACPICA_REPO" master "b9c69f81a05c45611c91ea9cbce8756078d76233"
|
||||
checkout_repo "$ACPICA_DIR" "$ACPICA_REPO" master "e80cbd7b52de20aa8c75bfba9845e9cb61f2e681"
|
||||
|
||||
if [[ ! -f "$EDK2_DIR/.built" ||
|
||||
! -f "$EDK2_PLAT_DIR/.built" ||
|
||||
@@ -38,10 +38,14 @@ build_edk2() {
|
||||
source edk2/edksetup.sh
|
||||
make -C edk2/BaseTools -j "$(nproc)"
|
||||
build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtCloudHv.dsc -b RELEASE -n 0
|
||||
cp Build/ArmVirtCloudHv-AARCH64/RELEASE_GCC5/FV/CLOUDHV_EFI.fd "$WORKLOADS_DIR"
|
||||
touch "$EDK2_DIR"/.built
|
||||
touch "$EDK2_PLAT_DIR"/.built
|
||||
touch "$ACPICA_DIR"/.built
|
||||
if cp Build/ArmVirtCloudHv-AARCH64/RELEASE_GCC5/FV/CLOUDHV_EFI.fd "$WORKLOADS_DIR"; then
|
||||
touch "$EDK2_DIR"/.built
|
||||
touch "$EDK2_PLAT_DIR"/.built
|
||||
touch "$ACPICA_DIR"/.built
|
||||
else
|
||||
echo "Failed to produce aarch64 UEFI firmware. Built markers not created."
|
||||
exit 1
|
||||
fi
|
||||
popd || exit
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -1,8 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
|
||||
rm -f /tmp/ubuntu-cloudinit.img
|
||||
mkdosfs -n CIDATA -C /tmp/ubuntu-cloudinit.img 8192
|
||||
mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/local/user-data ::
|
||||
mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/local/meta-data ::
|
||||
mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/local/network-config ::
|
||||
usage() {
|
||||
echo "Usage: $0 [-o|--output <output_file>]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -o, --output Specify output file path (default: /tmp/ubuntu-cloudinit.img)"
|
||||
echo " -h, --help Show this help message"
|
||||
}
|
||||
|
||||
OUTPUT_FILE=/tmp/ubuntu-cloudinit.img
|
||||
|
||||
while [ "$1" != "" ]; do
|
||||
echo "Processing argument: $1"
|
||||
case $1 in
|
||||
-o | --output)
|
||||
OUTPUT_FILE=$2
|
||||
shift # Remove argument (-o) name from `$@`
|
||||
shift # Remove argument value (file path) from `$@`
|
||||
;;
|
||||
-h | --help)
|
||||
usage # run usage function on help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
usage # run usage function if wrong argument provided
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
rm -f "$OUTPUT_FILE"
|
||||
mkdosfs -n CIDATA -C "$OUTPUT_FILE" 8192
|
||||
mcopy -oi "$OUTPUT_FILE" -s test_data/cloud-init/ubuntu/local/user-data ::
|
||||
mcopy -oi "$OUTPUT_FILE" -s test_data/cloud-init/ubuntu/local/meta-data ::
|
||||
mcopy -oi "$OUTPUT_FILE" -s test_data/cloud-init/ubuntu/local/network-config ::
|
||||
|
||||
@@ -28,6 +28,9 @@ CTR_CLH_ROOT_DIR="/cloud-hypervisor"
|
||||
CTR_CLH_CARGO_BUILT_DIR="${CTR_CLH_ROOT_DIR}/build"
|
||||
CTR_CLH_CARGO_TARGET="${CTR_CLH_CARGO_BUILT_DIR}/cargo_target"
|
||||
CTR_CLH_INTEGRATION_WORKLOADS="/root/workloads"
|
||||
SRC_IGVM_FILES_PATH="/usr/share/cloud-hypervisor/cvm"
|
||||
DEST_IGVM_FILES_PATH="$CLH_INTEGRATION_WORKLOADS/igvm_files"
|
||||
CTR_IGVM_FILES_PATH="/igvm_files"
|
||||
|
||||
# Container networking option
|
||||
CTR_CLH_NET="bridge"
|
||||
@@ -173,6 +176,23 @@ process_volumes_args() {
|
||||
done
|
||||
}
|
||||
|
||||
# Copy IGVM files to the workloads directory
|
||||
# This is needed for the IGVM integration tests to run
|
||||
# $1 - source path
|
||||
# $2 - destination path
|
||||
copy_igvm_files() {
|
||||
src=$1
|
||||
dest=$2
|
||||
|
||||
if [ -d "$src" ]; then
|
||||
say "Copying IGVM files from $src to $dest"
|
||||
cp "$src"/* "$dest"
|
||||
else
|
||||
say_err "IGVM File path '$src' not found on host"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_help() {
|
||||
echo ""
|
||||
echo "Cloud Hypervisor $(basename "$0")"
|
||||
@@ -200,6 +220,7 @@ cmd_help() {
|
||||
echo " --integration-windows Run the Windows guest integration tests."
|
||||
echo " --integration-live-migration Run the live-migration integration tests."
|
||||
echo " --integration-rate-limiter Run the rate-limiter integration tests."
|
||||
echo " --integration-cvm Run the Confidential VM integration tests."
|
||||
echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu"
|
||||
echo " --metrics Generate performance metrics"
|
||||
echo " --coverage Generate code coverage information"
|
||||
@@ -333,6 +354,7 @@ cmd_tests() {
|
||||
integration_windows=false
|
||||
integration_live_migration=false
|
||||
integration_rate_limiter=false
|
||||
integration_cvm=false
|
||||
metrics=false
|
||||
coverage=false
|
||||
libc="gnu"
|
||||
@@ -351,6 +373,7 @@ cmd_tests() {
|
||||
"--integration-windows") { integration_windows=true; } ;;
|
||||
"--integration-live-migration") { integration_live_migration=true; } ;;
|
||||
"--integration-rate-limiter") { integration_rate_limiter=true; } ;;
|
||||
"--integration-cvm") { integration_cvm=true; } ;;
|
||||
"--metrics") { metrics=true; } ;;
|
||||
"--coverage") { coverage=true; } ;;
|
||||
"--libc")
|
||||
@@ -449,6 +472,33 @@ cmd_tests() {
|
||||
dbus-run-session ./scripts/run_integration_tests_"$(uname -m)".sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
if [ "$integration_cvm" = true ]; then
|
||||
mkdir -p "$DEST_IGVM_FILES_PATH"
|
||||
copy_igvm_files "$SRC_IGVM_FILES_PATH" "$DEST_IGVM_FILES_PATH"
|
||||
say "Running CVM integration tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net="$CTR_CLH_NET" \
|
||||
--mount type=tmpfs,destination=/tmp \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||
--volume "$DEST_IGVM_FILES_PATH:$CTR_IGVM_FILES_PATH" \
|
||||
${exported_volumes:+"$exported_volumes"} \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--env BUILD_TARGET="$target" \
|
||||
--env RUSTFLAGS="$rustflags" \
|
||||
--env TARGET_CC="$target_cc" \
|
||||
--env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \
|
||||
--env LLVM_PROFILE_FILE="$LLVM_PROFILE_FILE" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_integration_tests_cvm.sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
if [ "$integration_vfio" = true ]; then
|
||||
say "Running VFIO integration tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
|
||||
@@ -13,7 +13,7 @@ build_virtiofsd() {
|
||||
VIRTIOFSD_DIR="$WORKLOADS_DIR/virtiofsd_build"
|
||||
VIRTIOFSD_REPO="https://gitlab.com/virtio-fs/virtiofsd.git"
|
||||
|
||||
checkout_repo "$VIRTIOFSD_DIR" "$VIRTIOFSD_REPO" v1.8.0 "97ea7908fe7f9bc59916671a771bdcfaf4044b45"
|
||||
checkout_repo "$VIRTIOFSD_DIR" "$VIRTIOFSD_REPO" v1.13.3 "bbf82173682a3e48083771a0a23331e5c23b4924"
|
||||
|
||||
if [ ! -f "$VIRTIOFSD_DIR/.built" ]; then
|
||||
pushd "$VIRTIOFSD_DIR" || exit
|
||||
@@ -100,6 +100,16 @@ update_workloads() {
|
||||
popd || exit
|
||||
fi
|
||||
|
||||
JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE_NAME="jammy-server-cloudimg-arm64-custom-20220329-0-backing-raw.qcow2"
|
||||
JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE_NAME"
|
||||
if [ ! -f "$JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE" ]; then
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
time qemu-img create -f qcow2 \
|
||||
-b "$JAMMY_OS_RAW_IMAGE" \
|
||||
-F raw $JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE_NAME
|
||||
popd || exit
|
||||
fi
|
||||
|
||||
ALPINE_MINIROOTFS_URL="http://dl-cdn.alpinelinux.org/alpine/v3.11/releases/aarch64/alpine-minirootfs-3.11.3-aarch64.tar.gz"
|
||||
ALPINE_MINIROOTFS_TARBALL="$WORKLOADS_DIR/alpine-minirootfs-aarch64.tar.gz"
|
||||
if [ ! -f "$ALPINE_MINIROOTFS_TARBALL" ]; then
|
||||
|
||||
33
scripts/run_integration_tests_cvm.sh
Executable file
33
scripts/run_integration_tests_cvm.sh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC2048,SC2086,SC2154,SC1094
|
||||
set -x
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
source "$HOME"/.cargo/env
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/test-util.sh"
|
||||
|
||||
WORKLOADS_DIR="$HOME/workloads"
|
||||
mkdir -p "$WORKLOADS_DIR"
|
||||
|
||||
process_common_args "$@"
|
||||
|
||||
test_features="--features mshv,igvm,sev_snp"
|
||||
build_features="mshv,igvm,sev_snp"
|
||||
|
||||
download_x86_guest_images
|
||||
cp scripts/sha1sums-x86_64-common "$WORKLOADS_DIR"
|
||||
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
if ! sha1sum sha1sums-x86_64-common --check; then
|
||||
echo "sha1sum validation of images failed, remove invalid images to fix the issue."
|
||||
exit 1
|
||||
fi
|
||||
popd || exit
|
||||
|
||||
cargo build --features $build_features --all --release --target "$BUILD_TARGET"
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
cargo nextest run $test_features "common_cvm::$test_filter" -- ${test_binary_args[*]}
|
||||
RES=$?
|
||||
|
||||
exit $RES
|
||||
@@ -28,7 +28,7 @@ if [ -n "${MIGRATABLE_VERSION}" ]; then
|
||||
fi
|
||||
migratable_version=${MIGRATABLE_VERSION}
|
||||
fi
|
||||
cp scripts/sha1sums-x86_64 "$WORKLOADS_DIR"
|
||||
cp scripts/sha1sums-x86_64* "$WORKLOADS_DIR"
|
||||
|
||||
FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.qcow2"
|
||||
FOCAL_OS_IMAGE_URL="https://ch-images.azureedge.net/$FOCAL_OS_IMAGE_NAME"
|
||||
@@ -48,7 +48,7 @@ if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then
|
||||
fi
|
||||
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
if ! grep focal sha1sums-x86_64 | sha1sum --check; then
|
||||
if ! grep focal sha1sums-x86_64-common | sha1sum --check; then
|
||||
echo "sha1sum validation of images failed, remove invalid images to fix the issue."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -18,7 +18,7 @@ if [ "$hypervisor" = "mshv" ]; then
|
||||
test_features="--features mshv"
|
||||
fi
|
||||
|
||||
cp scripts/sha1sums-x86_64 "$WORKLOADS_DIR"
|
||||
cp scripts/sha1sums-x86_64* "$WORKLOADS_DIR"
|
||||
|
||||
JAMMY_OS_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.qcow2"
|
||||
JAMMY_OS_IMAGE_URL="https://ch-images.azureedge.net/$JAMMY_OS_IMAGE_NAME"
|
||||
@@ -38,7 +38,7 @@ if [ ! -f "$JAMMY_OS_RAW_IMAGE" ]; then
|
||||
fi
|
||||
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
if ! grep jammy sha1sums-x86_64 | sha1sum --check; then
|
||||
if ! grep jammy sha1sums-x86_64-common | sha1sum --check; then
|
||||
echo "sha1sum validation of images failed, remove invalid images to fix the issue."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -18,7 +18,7 @@ if [ "$hypervisor" = "mshv" ]; then
|
||||
test_features="--features mshv"
|
||||
fi
|
||||
|
||||
cp scripts/sha1sums-x86_64 "$WORKLOADS_DIR"
|
||||
cp scripts/sha1sums-x86_64* "$WORKLOADS_DIR"
|
||||
|
||||
if [ ! -f "$WORKLOADS_DIR/hypervisor-fw" ]; then
|
||||
download_hypervisor_fw
|
||||
@@ -28,39 +28,7 @@ if [ ! -f "$WORKLOADS_DIR/CLOUDHV.fd" ]; then
|
||||
download_ovmf
|
||||
fi
|
||||
|
||||
FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.qcow2"
|
||||
FOCAL_OS_IMAGE_URL="https://ch-images.azureedge.net/$FOCAL_OS_IMAGE_NAME"
|
||||
FOCAL_OS_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_IMAGE_NAME"
|
||||
if [ ! -f "$FOCAL_OS_IMAGE" ]; then
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
time wget --quiet $FOCAL_OS_IMAGE_URL || exit 1
|
||||
popd || exit
|
||||
fi
|
||||
|
||||
FOCAL_OS_RAW_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.raw"
|
||||
FOCAL_OS_RAW_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
time qemu-img convert -p -f qcow2 -O raw $FOCAL_OS_IMAGE_NAME $FOCAL_OS_RAW_IMAGE_NAME || exit 1
|
||||
popd || exit
|
||||
fi
|
||||
|
||||
JAMMY_OS_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.qcow2"
|
||||
JAMMY_OS_IMAGE_URL="https://ch-images.azureedge.net/$JAMMY_OS_IMAGE_NAME"
|
||||
JAMMY_OS_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_IMAGE_NAME"
|
||||
if [ ! -f "$JAMMY_OS_IMAGE" ]; then
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
time wget --quiet $JAMMY_OS_IMAGE_URL || exit 1
|
||||
popd || exit
|
||||
fi
|
||||
|
||||
JAMMY_OS_RAW_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.raw"
|
||||
JAMMY_OS_RAW_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$JAMMY_OS_RAW_IMAGE" ]; then
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
time qemu-img convert -p -f qcow2 -O raw $JAMMY_OS_IMAGE_NAME $JAMMY_OS_RAW_IMAGE_NAME || exit 1
|
||||
popd || exit
|
||||
fi
|
||||
download_x86_guest_images
|
||||
|
||||
JAMMY_OS_QCOW_ZLIB_FILE_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0-zlib.qcow2"
|
||||
JAMMY_OS_QCOW_ZLIB_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW_ZLIB_FILE_IMAGE_NAME"
|
||||
@@ -100,6 +68,16 @@ if [ ! -f "$JAMMY_OS_QCOW_BACKING_UNCOMPRESSED_FILE_IMAGE" ]; then
|
||||
popd || exit
|
||||
fi
|
||||
|
||||
JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0-backing-raw.qcow2"
|
||||
JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE_NAME"
|
||||
if [ ! -f "$JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE" ]; then
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
time qemu-img create -f qcow2 \
|
||||
-b "$JAMMY_OS_RAW_IMAGE" \
|
||||
-F raw $JAMMY_OS_QCOW_BACKING_RAW_FILE_IMAGE_NAME
|
||||
popd || exit
|
||||
fi
|
||||
|
||||
ALPINE_MINIROOTFS_URL="http://dl-cdn.alpinelinux.org/alpine/v3.11/releases/x86_64/alpine-minirootfs-3.11.3-x86_64.tar.gz"
|
||||
ALPINE_MINIROOTFS_TARBALL="$WORKLOADS_DIR/alpine-minirootfs-x86_64.tar.gz"
|
||||
if [ ! -f "$ALPINE_MINIROOTFS_TARBALL" ]; then
|
||||
@@ -127,7 +105,7 @@ if [ ! -f "$ALPINE_INITRAMFS_IMAGE" ]; then
|
||||
fi
|
||||
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
if ! sha1sum sha1sums-x86_64 --check; then
|
||||
if ! sha1sum sha1sums-x86_64 sha1sums-x86_64-common --check; then
|
||||
echo "sha1sum validation of images failed, remove invalid images to fix the issue."
|
||||
exit 1
|
||||
fi
|
||||
@@ -146,7 +124,7 @@ if [ ! -f "$VIRTIOFSD" ]; then
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
git clone "https://gitlab.com/virtio-fs/virtiofsd.git" $VIRTIOFSD_DIR
|
||||
pushd $VIRTIOFSD_DIR || exit
|
||||
git checkout v1.8.0
|
||||
git checkout v1.13.3
|
||||
time cargo build --release
|
||||
cp target/release/virtiofsd "$VIRTIOFSD" || exit 1
|
||||
popd || exit
|
||||
|
||||
@@ -28,7 +28,7 @@ build_fio() {
|
||||
|
||||
process_common_args "$@"
|
||||
|
||||
cp scripts/sha1sums-"${TEST_ARCH}" "$WORKLOADS_DIR"
|
||||
cp scripts/sha1sums-"${TEST_ARCH}"-common "$WORKLOADS_DIR"
|
||||
|
||||
if [ "${TEST_ARCH}" == "aarch64" ]; then
|
||||
FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-arm64-custom-20210929-0.qcow2"
|
||||
@@ -58,10 +58,11 @@ if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then
|
||||
fi
|
||||
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
if ! grep focal sha1sums-"${TEST_ARCH}" | sha1sum --check; then
|
||||
if ! grep focal sha1sums-"${TEST_ARCH}"-common | sha1sum --check; then
|
||||
echo "sha1sum validation of images failed, remove invalid images to fix the issue."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
popd || exit
|
||||
|
||||
if [ "${TEST_ARCH}" == "aarch64" ]; then
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
d4a44acc6014d5f83dea1c625c43d677a95fa75f alpine-minirootfs-x86_64.tar.gz
|
||||
f1eccdc5e1b515dbad294426ab081b47ebfb97c0 focal-server-cloudimg-amd64-custom-20210609-0.qcow2
|
||||
7f5a8358243a96adf61f5c20139b29f308f2c0e3 focal-server-cloudimg-amd64-custom-20210609-0.raw
|
||||
5f10738920efb74f0bf854cadcd1b1fd544e49c8 jammy-server-cloudimg-amd64-custom-20241017-0.qcow2
|
||||
c1dfbe7abde400e675844568dbe9d3914222f6de jammy-server-cloudimg-amd64-custom-20241017-0.raw
|
||||
540ac358429305d7aa94e15363665d1c9d845982 hypervisor-fw
|
||||
4e96fd0914a44005d40707b2b0c7e829e4086bd5 CLOUDHV.fd
|
||||
|
||||
4
scripts/sha1sums-x86_64-common
Normal file
4
scripts/sha1sums-x86_64-common
Normal file
@@ -0,0 +1,4 @@
|
||||
f1eccdc5e1b515dbad294426ab081b47ebfb97c0 focal-server-cloudimg-amd64-custom-20210609-0.qcow2
|
||||
7f5a8358243a96adf61f5c20139b29f308f2c0e3 focal-server-cloudimg-amd64-custom-20210609-0.raw
|
||||
5f10738920efb74f0bf854cadcd1b1fd544e49c8 jammy-server-cloudimg-amd64-custom-20241017-0.qcow2
|
||||
c1dfbe7abde400e675844568dbe9d3914222f6de jammy-server-cloudimg-amd64-custom-20241017-0.raw
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC1009,SC2048,SC2086,SC1073,SC1040,SC1072
|
||||
# shellcheck source=/dev/null
|
||||
set -x
|
||||
|
||||
hypervisor="kvm"
|
||||
@@ -212,7 +214,7 @@ mount_and_exec() {
|
||||
local COMMAND_STATUS=0
|
||||
|
||||
# Cleanup function to unmount and detach loop device
|
||||
# shellcheck disable=SC2317
|
||||
# shellcheck disable=SC2317,SC2329
|
||||
cleanup() {
|
||||
if [ -n "$MOUNT_DIR" ]; then
|
||||
echo "Cleanup: Unmounting $MOUNT_DIR..." >&2
|
||||
@@ -301,3 +303,40 @@ copy_to_image() {
|
||||
mount_and_exec "$IMG" "$MOUNT_DIR" /bin/bash -c "$COPY_COMMAND"
|
||||
return $?
|
||||
}
|
||||
|
||||
# Download x86 guest images (Focal and Jammy)
|
||||
download_x86_guest_images() {
|
||||
FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.qcow2"
|
||||
FOCAL_OS_IMAGE_URL="https://ch-images.azureedge.net/$FOCAL_OS_IMAGE_NAME"
|
||||
FOCAL_OS_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_IMAGE_NAME"
|
||||
if [ ! -f "$FOCAL_OS_IMAGE" ]; then
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
time wget --quiet $FOCAL_OS_IMAGE_URL || exit 1
|
||||
popd || exit
|
||||
fi
|
||||
|
||||
FOCAL_OS_RAW_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.raw"
|
||||
FOCAL_OS_RAW_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
time qemu-img convert -p -f qcow2 -O raw $FOCAL_OS_IMAGE_NAME $FOCAL_OS_RAW_IMAGE_NAME || exit 1
|
||||
popd || exit
|
||||
fi
|
||||
|
||||
JAMMY_OS_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.qcow2"
|
||||
JAMMY_OS_IMAGE_URL="https://ch-images.azureedge.net/$JAMMY_OS_IMAGE_NAME"
|
||||
JAMMY_OS_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_IMAGE_NAME"
|
||||
if [ ! -f "$JAMMY_OS_IMAGE" ]; then
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
time wget --quiet $JAMMY_OS_IMAGE_URL || exit 1
|
||||
popd || exit
|
||||
fi
|
||||
|
||||
JAMMY_OS_RAW_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.raw"
|
||||
JAMMY_OS_RAW_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$JAMMY_OS_RAW_IMAGE" ]; then
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
time qemu-img convert -p -f qcow2 -O raw $JAMMY_OS_IMAGE_NAME $JAMMY_OS_RAW_IMAGE_NAME || exit 1
|
||||
popd || exit
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ version = "0.1.0"
|
||||
dirs = { workspace = true }
|
||||
epoll = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
rand = "0.10.0"
|
||||
serde_json = { workspace = true }
|
||||
ssh2 = { version = "0.9.5", features = ["vendored-openssl"] }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -18,6 +18,7 @@ use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use std::{env, fmt, fs, io, thread};
|
||||
|
||||
use rand::Rng;
|
||||
use serde_json::Value;
|
||||
use ssh2::Session;
|
||||
use thiserror::Error;
|
||||
@@ -74,7 +75,8 @@ pub struct GuestNetworkConfig {
|
||||
|
||||
pub const DEFAULT_TCP_LISTENER_MESSAGE: &str = "booted";
|
||||
pub const DEFAULT_TCP_LISTENER_PORT: u16 = 8000;
|
||||
pub const DEFAULT_TCP_LISTENER_TIMEOUT: i32 = 120;
|
||||
pub const DEFAULT_TCP_LISTENER_TIMEOUT: u32 = 120;
|
||||
pub const DEFAULT_CVM_TCP_LISTENER_TIMEOUT: u32 = 120;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum WaitForBootError {
|
||||
@@ -91,16 +93,12 @@ pub enum WaitForBootError {
|
||||
}
|
||||
|
||||
impl GuestNetworkConfig {
|
||||
pub fn wait_vm_boot(&self, custom_timeout: Option<i32>) -> Result<(), WaitForBootError> {
|
||||
pub fn wait_vm_boot(&self, custom_timeout: u32) -> Result<(), WaitForBootError> {
|
||||
let start = std::time::Instant::now();
|
||||
// The 'port' is unique per 'GUEST' and listening to wild-card ip avoids retrying on 'TcpListener::bind()'
|
||||
let listen_addr = format!("0.0.0.0:{}", self.tcp_listener_port);
|
||||
let expected_guest_addr = self.guest_ip0.as_str();
|
||||
let mut s = String::new();
|
||||
let timeout = match custom_timeout {
|
||||
Some(t) => t,
|
||||
None => DEFAULT_TCP_LISTENER_TIMEOUT,
|
||||
};
|
||||
|
||||
let mut closure = || -> Result<(), WaitForBootError> {
|
||||
let listener =
|
||||
@@ -122,7 +120,11 @@ impl GuestNetworkConfig {
|
||||
.expect("Cannot add 'tcp_listener' event to epoll");
|
||||
let mut events = [epoll::Event::new(epoll::Events::empty(), 0); 1];
|
||||
loop {
|
||||
let num_events = match epoll::wait(epoll_fd, timeout * 1000_i32, &mut events[..]) {
|
||||
let num_events = match epoll::wait(
|
||||
epoll_fd,
|
||||
(custom_timeout * 1000).try_into().unwrap(),
|
||||
&mut events[..],
|
||||
) {
|
||||
Ok(num_events) => Ok(num_events),
|
||||
Err(e) => match e.raw_os_error() {
|
||||
Some(libc::EAGAIN) | Some(libc::EINTR) => continue,
|
||||
@@ -162,7 +164,7 @@ impl GuestNetworkConfig {
|
||||
let duration = start.elapsed();
|
||||
eprintln!(
|
||||
"\n\n==== Start 'wait_vm_boot' (FAILED) ==== \
|
||||
\n\nduration =\"{duration:?}, timeout = {timeout}s\" \
|
||||
\n\nduration =\"{duration:?}, timeout = {custom_timeout}s\" \
|
||||
\nlisten_addr=\"{listen_addr}\" \
|
||||
\nexpected_guest_addr=\"{expected_guest_addr}\" \
|
||||
\nmessage=\"{s}\" \
|
||||
@@ -887,6 +889,11 @@ pub struct Guest {
|
||||
pub tmp_dir: TempDir,
|
||||
pub disk_config: Box<dyn DiskConfig>,
|
||||
pub network: GuestNetworkConfig,
|
||||
pub vm_type: GuestVmType,
|
||||
pub boot_timeout: u32,
|
||||
pub kernel_path: Option<String>,
|
||||
pub kernel_cmdline: Option<String>,
|
||||
pub console_type: Option<String>,
|
||||
}
|
||||
|
||||
// Return the next id that can be used for this guest. This is stored in a
|
||||
@@ -951,6 +958,11 @@ impl Guest {
|
||||
tmp_dir,
|
||||
disk_config,
|
||||
network,
|
||||
vm_type: GuestVmType::Regular,
|
||||
boot_timeout: DEFAULT_TCP_LISTENER_TIMEOUT,
|
||||
kernel_path: None,
|
||||
kernel_cmdline: None,
|
||||
console_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1076,7 +1088,17 @@ impl Guest {
|
||||
.map_err(Error::Parsing)
|
||||
}
|
||||
|
||||
pub fn wait_vm_boot(&self, custom_timeout: Option<i32>) -> Result<(), Error> {
|
||||
fn default_boot_timeout(&self) -> u32 {
|
||||
self.boot_timeout
|
||||
}
|
||||
|
||||
pub fn wait_vm_boot(&self) -> Result<(), Error> {
|
||||
self.network
|
||||
.wait_vm_boot(self.default_boot_timeout())
|
||||
.map_err(Error::WaitForBoot)
|
||||
}
|
||||
|
||||
pub fn wait_vm_boot_custom_timeout(&self, custom_timeout: u32) -> Result<(), Error> {
|
||||
self.network
|
||||
.wait_vm_boot(custom_timeout)
|
||||
.map_err(Error::WaitForBoot)
|
||||
@@ -1214,7 +1236,7 @@ impl Guest {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn reboot_linux(&self, current_reboot_count: u32, custom_timeout: Option<i32>) {
|
||||
pub fn reboot_linux(&self, current_reboot_count: u32) {
|
||||
let list_boots_cmd = "sudo last | grep -c reboot";
|
||||
let boot_count = self
|
||||
.ssh_command(list_boots_cmd)
|
||||
@@ -1226,7 +1248,7 @@ impl Guest {
|
||||
assert_eq!(boot_count, current_reboot_count + 1);
|
||||
self.ssh_command("sudo reboot").unwrap();
|
||||
|
||||
self.wait_vm_boot(custom_timeout).unwrap();
|
||||
self.wait_vm_boot().unwrap();
|
||||
let boot_count = self
|
||||
.ssh_command(list_boots_cmd)
|
||||
.unwrap()
|
||||
@@ -1461,6 +1483,29 @@ impl<'a> GuestCommand<'a> {
|
||||
pub fn default_net(&mut self) -> &mut Self {
|
||||
self.args(["--net", self.guest.default_net_string().as_str()])
|
||||
}
|
||||
|
||||
pub fn default_kernel_cmdline(&mut self) -> &mut Self {
|
||||
if self.guest.vm_type == GuestVmType::Confidential {
|
||||
let console_str = if let Some(c) = &self.guest.console_type {
|
||||
c.as_str()
|
||||
} else {
|
||||
"hvc0"
|
||||
};
|
||||
let igvm = direct_igvm_boot_path(Some(console_str))
|
||||
.expect("IGVM boot file not found for console type: {console_str}");
|
||||
self.command.args(["--igvm", igvm.to_str().unwrap()]);
|
||||
self.command
|
||||
.args(["--host-data", generate_host_data().as_str()]);
|
||||
self.command.args(["--platform", "sev_snp=on"]);
|
||||
} else if let Some(kernel) = &self.guest.kernel_path {
|
||||
self.command.args(["--kernel", kernel.as_str()]);
|
||||
if let Some(cmdline) = &self.guest.kernel_cmdline {
|
||||
self.command.args(["--cmdline", cmdline]);
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the absolute path into the workspaces target directory to locate the desired
|
||||
@@ -1858,3 +1903,33 @@ pub fn extract_bar_address(output: &str, device_desc: &str, bar_index: usize) ->
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum GuestVmType {
|
||||
Regular,
|
||||
Confidential,
|
||||
}
|
||||
|
||||
// Get the direct igvm boot file path based on the console type
|
||||
fn direct_igvm_boot_path(console: Option<&str>) -> Option<PathBuf> {
|
||||
// get the default hvc0 igvm file if console string is not passed
|
||||
let console_str = console.unwrap_or("hvc0");
|
||||
|
||||
if console_str != "hvc0" && console_str != "ttyS0" {
|
||||
panic!("IGVM console should be hvc0 or ttyS0, got: {console_str}");
|
||||
}
|
||||
|
||||
let igvm_filepath = format!("/igvm_files/linux-{console_str}.bin");
|
||||
if Path::new(&igvm_filepath).exists() {
|
||||
Some(PathBuf::from(igvm_filepath))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a random 64-character hex string for host data
|
||||
fn generate_host_data() -> String {
|
||||
let mut bytes = [0u8; 32];
|
||||
rand::rng().fill_bytes(&mut bytes);
|
||||
bytes.iter().map(|b| format!("{b:02x}")).collect()
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ use std::{convert, io, process, result};
|
||||
use block::qcow::{self, ImageType, QcowFile};
|
||||
use block::{Request, VirtioBlockConfig, build_serial};
|
||||
use libc::EFD_NONBLOCK;
|
||||
use log::{debug, error, info};
|
||||
use log::{debug, error, info, warn};
|
||||
use option_parser::{OptionParser, OptionParserError, Toggle};
|
||||
use thiserror::Error;
|
||||
use vhost::vhost_user::Listener;
|
||||
@@ -224,9 +224,9 @@ impl VhostUserBlkBackend {
|
||||
let image_type = qcow::detect_image_type(&mut raw_img).unwrap();
|
||||
let image = match image_type {
|
||||
ImageType::Raw => Arc::new(Mutex::new(raw_img)) as Arc<Mutex<dyn DiskFile>>,
|
||||
ImageType::Qcow2 => {
|
||||
Arc::new(Mutex::new(QcowFile::from(raw_img).unwrap())) as Arc<Mutex<dyn DiskFile>>
|
||||
}
|
||||
ImageType::Qcow2 => Arc::new(Mutex::new(
|
||||
QcowFile::from_with_nesting_depth(raw_img, 0, true).unwrap(),
|
||||
)) as Arc<Mutex<dyn DiskFile>>,
|
||||
};
|
||||
|
||||
let nsectors = (image.lock().unwrap().seek(SeekFrom::End(0)).unwrap()) / SECTOR_SIZE;
|
||||
@@ -395,8 +395,18 @@ impl VhostUserBackendMut for VhostUserBlkBackend {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_config(&self, _offset: u32, _size: u32) -> Vec<u8> {
|
||||
self.config.as_slice().to_vec()
|
||||
fn get_config(&self, offset: u32, size: u32) -> Vec<u8> {
|
||||
let subset = self
|
||||
.config
|
||||
.as_slice()
|
||||
.get(offset as usize..(offset + size) as usize);
|
||||
|
||||
if let Some(subset) = subset {
|
||||
subset.to_vec()
|
||||
} else {
|
||||
warn!("Invalid config offset {offset} or size {size}");
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn set_config(&mut self, offset: u32, data: &[u8]) -> result::Result<(), io::Error> {
|
||||
|
||||
@@ -160,6 +160,7 @@ struct BlockEpollHandler {
|
||||
access_platform: Option<Arc<dyn AccessPlatform>>,
|
||||
host_cpus: Option<Vec<usize>>,
|
||||
acked_features: u64,
|
||||
disable_sector0_writes: bool,
|
||||
}
|
||||
|
||||
fn has_feature(features: u64, feature_flag: u64) -> bool {
|
||||
@@ -167,15 +168,27 @@ fn has_feature(features: u64, feature_flag: u64) -> bool {
|
||||
}
|
||||
|
||||
impl BlockEpollHandler {
|
||||
fn check_request(features: u64, request_type: RequestType) -> result::Result<(), ExecuteError> {
|
||||
if has_feature(features, VIRTIO_BLK_F_RO.into())
|
||||
&& !(request_type == RequestType::In || request_type == RequestType::GetDeviceId)
|
||||
fn check_request(
|
||||
features: u64,
|
||||
request: &Request,
|
||||
disable_sector0_writes: bool,
|
||||
) -> result::Result<(), ExecuteError> {
|
||||
let request_type = request.request_type;
|
||||
if (has_feature(features, VIRTIO_BLK_F_RO.into()))
|
||||
&& !(request_type == RequestType::In
|
||||
|| request_type == RequestType::GetDeviceId
|
||||
|| request_type == RequestType::Flush)
|
||||
{
|
||||
// For virtio spec compliance
|
||||
// "A device MUST set the status byte to VIRTIO_BLK_S_IOERR for a write request
|
||||
// if the VIRTIO_BLK_F_RO feature if offered, and MUST NOT write any data."
|
||||
return Err(ExecuteError::ReadOnly);
|
||||
}
|
||||
|
||||
if request_type == RequestType::Out && disable_sector0_writes && request.sector == 0 {
|
||||
return Err(ExecuteError::ReadOnly);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -191,7 +204,10 @@ impl BlockEpollHandler {
|
||||
// For virtio spec compliance
|
||||
// "A device MUST set the status byte to VIRTIO_BLK_S_IOERR for a write request
|
||||
// if the VIRTIO_BLK_F_RO feature if offered, and MUST NOT write any data."
|
||||
if let Err(e) = Self::check_request(self.acked_features, request.request_type) {
|
||||
// Also, if sector 0 writes are disabled, treat writes to sector 0 as read-only as well.
|
||||
if let Err(e) =
|
||||
Self::check_request(self.acked_features, &request, self.disable_sector0_writes)
|
||||
{
|
||||
warn!("Request check failed: {request:x?} {e:?}");
|
||||
desc_chain
|
||||
.memory()
|
||||
@@ -247,6 +263,7 @@ impl BlockEpollHandler {
|
||||
self.disk_nsectors.load(Ordering::SeqCst),
|
||||
self.disk_image.as_mut(),
|
||||
&self.serial,
|
||||
self.disable_sector0_writes,
|
||||
desc_chain.head_index() as u64,
|
||||
);
|
||||
|
||||
@@ -644,6 +661,7 @@ pub struct Block {
|
||||
exit_evt: EventFd,
|
||||
serial: Vec<u8>,
|
||||
queue_affinity: BTreeMap<u16, Vec<usize>>,
|
||||
disable_sector0_writes: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -672,6 +690,8 @@ impl Block {
|
||||
exit_evt: EventFd,
|
||||
state: Option<BlockState>,
|
||||
queue_affinity: BTreeMap<u16, Vec<usize>>,
|
||||
sparse: bool,
|
||||
disable_sector0_writes: bool,
|
||||
) -> io::Result<Self> {
|
||||
let (disk_nsectors, avail_features, acked_features, config, paused) =
|
||||
if let Some(state) = state {
|
||||
@@ -702,6 +722,20 @@ impl Block {
|
||||
| (1u64 << VIRTIO_BLK_F_SEG_MAX)
|
||||
| (1u64 << VIRTIO_RING_F_EVENT_IDX)
|
||||
| (1u64 << VIRTIO_RING_F_INDIRECT_DESC);
|
||||
|
||||
// When backend supports sparse operations:
|
||||
// - Always advertise WRITE_ZEROES
|
||||
// - Advertise DISCARD only if sparse=true OR format supports marking
|
||||
// clusters as zero without deallocating
|
||||
if disk_image.supports_sparse_operations() {
|
||||
avail_features |= 1u64 << VIRTIO_BLK_F_WRITE_ZEROES;
|
||||
if sparse || disk_image.supports_zero_flag() {
|
||||
avail_features |= 1u64 << VIRTIO_BLK_F_DISCARD;
|
||||
}
|
||||
} else if sparse {
|
||||
warn!("sparse=on requested but backend does not support sparse operations");
|
||||
}
|
||||
|
||||
if iommu {
|
||||
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
||||
}
|
||||
@@ -772,6 +806,7 @@ impl Block {
|
||||
exit_evt,
|
||||
serial,
|
||||
queue_affinity,
|
||||
disable_sector0_writes,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -969,6 +1004,13 @@ impl VirtioDevice for Block {
|
||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
||||
mut queues: Vec<(usize, Queue, EventFd)>,
|
||||
) -> ActivateResult {
|
||||
// See if the guest didn't ack the device being read-only.
|
||||
// If so, warn and pretend it did.
|
||||
let original_acked_features = self.common.acked_features;
|
||||
self.common.acked_features |= self.common.avail_features & (1u64 << VIRTIO_BLK_F_RO);
|
||||
if original_acked_features != self.common.acked_features {
|
||||
warn!("Guest did not acknowledge that device is read-only, acting as if it did!");
|
||||
}
|
||||
self.common.activate(&queues, interrupt_cb.clone())?;
|
||||
|
||||
self.update_writeback();
|
||||
@@ -1016,6 +1058,7 @@ impl VirtioDevice for Block {
|
||||
access_platform: self.common.access_platform.clone(),
|
||||
host_cpus: self.queue_affinity.get(&queue_idx).cloned(),
|
||||
acked_features: self.common.acked_features,
|
||||
disable_sector0_writes: self.disable_sector0_writes,
|
||||
};
|
||||
|
||||
let paused = self.common.paused.clone();
|
||||
|
||||
@@ -293,6 +293,7 @@ fn virtio_thread_common() -> Vec<(i64, Vec<SeccompRule>)> {
|
||||
(libc::SYS_epoll_wait, vec![]),
|
||||
(libc::SYS_exit, vec![]),
|
||||
(libc::SYS_futex, vec![]),
|
||||
(libc::SYS_gettid, vec![]),
|
||||
(libc::SYS_madvise, vec![]),
|
||||
(libc::SYS_mmap, vec![]),
|
||||
(libc::SYS_mprotect, vec![]),
|
||||
|
||||
@@ -11,8 +11,7 @@ use log::{error, info};
|
||||
use seccompiler::SeccompAction;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vhost::vhost_user::message::{
|
||||
VHOST_USER_CONFIG_OFFSET, VhostUserConfigFlags, VhostUserProtocolFeatures,
|
||||
VhostUserVirtioFeatures,
|
||||
VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserVirtioFeatures,
|
||||
};
|
||||
use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler};
|
||||
use virtio_bindings::virtio_blk::{
|
||||
@@ -147,7 +146,7 @@ impl Blk {
|
||||
let (_, config_space) = vu
|
||||
.socket_handle()
|
||||
.get_config(
|
||||
VHOST_USER_CONFIG_OFFSET,
|
||||
0,
|
||||
config_len as u32,
|
||||
VhostUserConfigFlags::WRITABLE,
|
||||
config_space.as_slice(),
|
||||
|
||||
@@ -375,15 +375,13 @@ impl VhostUserCommon {
|
||||
}
|
||||
|
||||
pub fn shutdown(&mut self) {
|
||||
if let Some(vu) = &self.vu {
|
||||
// SAFETY: trivially safe
|
||||
let _ = unsafe { libc::close(vu.lock().unwrap().socket_handle().as_raw_fd()) };
|
||||
}
|
||||
|
||||
// Remove socket path if needed
|
||||
if self.server {
|
||||
let _ = std::fs::remove_file(&self.socket_path);
|
||||
}
|
||||
|
||||
// Drop the vhost-user handle
|
||||
self.vu = None;
|
||||
}
|
||||
|
||||
pub fn add_memory_region(
|
||||
|
||||
@@ -28,11 +28,10 @@ use vmm_sys_util::eventfd::EventFd;
|
||||
use crate::seccomp_filters::Thread;
|
||||
use crate::thread_helper::spawn_virtio_thread;
|
||||
use crate::vhost_user::vu_common_ctrl::{VhostUserConfig, VhostUserHandle};
|
||||
use crate::vhost_user::{Error, Result, VhostUserCommon};
|
||||
use crate::vhost_user::{DEFAULT_VIRTIO_FEATURES, Error, Result, VhostUserCommon};
|
||||
use crate::{
|
||||
ActivateResult, GuestMemoryMmap, GuestRegionMmap, NetCtrlEpollHandler, VIRTIO_F_IOMMU_PLATFORM,
|
||||
VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, VirtioDeviceType,
|
||||
VirtioInterrupt,
|
||||
VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt,
|
||||
};
|
||||
|
||||
const DEFAULT_QUEUE_NUMBER: usize = 2;
|
||||
@@ -122,9 +121,7 @@ impl Net {
|
||||
// Filling device and vring features VMM supports.
|
||||
let mut avail_features = (1 << VIRTIO_NET_F_MRG_RXBUF)
|
||||
| (1 << VIRTIO_NET_F_CTRL_VQ)
|
||||
| (1 << VIRTIO_F_RING_EVENT_IDX)
|
||||
| (1 << VIRTIO_F_VERSION_1)
|
||||
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
| DEFAULT_VIRTIO_FEATURES;
|
||||
|
||||
if mtu.is_some() {
|
||||
avail_features |= 1u64 << VIRTIO_NET_F_MTU;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user