mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
292 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea3e2ff625 | ||
|
|
3320015793 | ||
|
|
9113544e21 | ||
|
|
8c526891ac | ||
|
|
78a30012fb | ||
|
|
c5c751c478 | ||
|
|
f92cce888b | ||
|
|
00b4d97826 | ||
|
|
3fe7d6d904 | ||
|
|
02f146fef8 | ||
|
|
36cdd67b9c | ||
|
|
92b12ce2f4 | ||
|
|
cbb588c380 | ||
|
|
1c7997c5c3 | ||
|
|
fee769bed4 | ||
|
|
6b2c2c938a | ||
|
|
1e967697c2 | ||
|
|
959ea7115f | ||
|
|
398e9338a7 | ||
|
|
5c4b5c0e40 | ||
|
|
f9bd2aaf9f | ||
|
|
f126288159 | ||
|
|
9f9a4e657f | ||
|
|
3097d78c6e | ||
|
|
7c2a252c02 | ||
|
|
a87b25a962 | ||
|
|
24ed063dbe | ||
|
|
fbac81843f | ||
|
|
610a2234dc | ||
|
|
5f18ac3bc0 | ||
|
|
4bf2d4f7dd | ||
|
|
954f3dd057 | ||
|
|
026e2c6aa8 | ||
|
|
be9f57dcf9 | ||
|
|
bd180bc3eb | ||
|
|
b9f086bcb3 | ||
|
|
d2f0e8aebb | ||
|
|
513973873c | ||
|
|
4c52fa344e | ||
|
|
e18d32bac2 | ||
|
|
59185050d5 | ||
|
|
dc72ef42dc | ||
|
|
68bb32a3e7 | ||
|
|
0f89387475 | ||
|
|
f5b2eb5c76 | ||
|
|
82ac114b8a | ||
|
|
2ffd5df018 | ||
|
|
006a12d911 | ||
|
|
bb55976454 | ||
|
|
a15e041138 | ||
|
|
0fa96402b8 | ||
|
|
bc0ef1d8ba | ||
|
|
86153225cf | ||
|
|
ddc3f194aa | ||
|
|
c6cc3d3ba9 | ||
|
|
0fbb3e2c2c | ||
|
|
330e1aac36 | ||
|
|
5fddf76a3b | ||
|
|
b50dfb3538 | ||
|
|
502995746a | ||
|
|
8aa2d7ab2a | ||
|
|
bd8c28d341 | ||
|
|
422906a0c7 | ||
|
|
b7512263be | ||
|
|
7e749c0390 | ||
|
|
61a5bae25c | ||
|
|
a2438700e4 | ||
|
|
bb4af57219 | ||
|
|
824e83ab0d | ||
|
|
29675cfe68 | ||
|
|
43632f255b | ||
|
|
a1941ad10b | ||
|
|
70db454b90 | ||
|
|
7b3ffd89a5 | ||
|
|
c1f18fa634 | ||
|
|
efb92d409f | ||
|
|
e4aa3617c7 | ||
|
|
d7e8cd8258 | ||
|
|
8803e4a2e7 | ||
|
|
d2a01f7cec | ||
|
|
dec4a82058 | ||
|
|
5fc143205c | ||
|
|
b73d94f969 | ||
|
|
ead4f767ff | ||
|
|
bc6acb842f | ||
|
|
d89f1f4f21 | ||
|
|
dc90744ec3 | ||
|
|
18340d9761 | ||
|
|
cfaa192eb4 | ||
|
|
c67f799717 | ||
|
|
b47efc3bc2 | ||
|
|
498f35a1ab | ||
|
|
2d61bc36ed | ||
|
|
46447962b8 | ||
|
|
9a6bc025a7 | ||
|
|
de1abe0e30 | ||
|
|
e97cee99ef | ||
|
|
3103526153 | ||
|
|
08ff89ba6e | ||
|
|
514f36fb5a | ||
|
|
fe3506976e | ||
|
|
94929889ac | ||
|
|
8452edfcc7 | ||
|
|
466cc5e043 | ||
|
|
034c674c4c | ||
|
|
b785e00317 | ||
|
|
7f6731cd05 | ||
|
|
457fd9ef96 | ||
|
|
eea45a2c78 | ||
|
|
11c17ca319 | ||
|
|
249e362c70 | ||
|
|
b3e5738b40 | ||
|
|
1dd53c3d24 | ||
|
|
130c988380 | ||
|
|
8c76a3e4b5 | ||
|
|
af5a9677c8 | ||
|
|
1d89f98edf | ||
|
|
287dbd4fc9 | ||
|
|
c50ea2c708 | ||
|
|
14b45e4d2e | ||
|
|
519476e842 | ||
|
|
5fec858130 | ||
|
|
67f22b6aa4 | ||
|
|
1eb4133034 | ||
|
|
9f3bea3e3f | ||
|
|
e7c7a304e8 | ||
|
|
b5cce0d371 | ||
|
|
7c608f6380 | ||
|
|
3ad8d24943 | ||
|
|
56c6c02724 | ||
|
|
19b0ea842b | ||
|
|
16bd88b4b5 | ||
|
|
09136c50b5 | ||
|
|
380b7d398c | ||
|
|
764959c9a8 | ||
|
|
7d84654a79 | ||
|
|
246cb94dc5 | ||
|
|
165c2c476f | ||
|
|
5e8f380698 | ||
|
|
e9b2639757 | ||
|
|
98d0aabb99 | ||
|
|
d206586683 | ||
|
|
19f2800d9e | ||
|
|
08cf983d42 | ||
|
|
243dc5918c | ||
|
|
544de7d000 | ||
|
|
796db588ea | ||
|
|
397c76dd20 | ||
|
|
cdd3ff5e5a | ||
|
|
6cb76abbf1 | ||
|
|
3a5e5364b9 | ||
|
|
f2b2d033f7 | ||
|
|
c41461c9c2 | ||
|
|
9257322330 | ||
|
|
220455caaf | ||
|
|
c206c14318 | ||
|
|
777757254b | ||
|
|
9c42d98e6a | ||
|
|
254db7b96a | ||
|
|
b3a73d6634 | ||
|
|
9cd214b0a1 | ||
|
|
9f969ee18d | ||
|
|
c3fcddf830 | ||
|
|
11d98fccac | ||
|
|
a8fa2af64b | ||
|
|
dc723171a7 | ||
|
|
52eebaf6b2 | ||
|
|
380ba564f4 | ||
|
|
385f9a9aa9 | ||
|
|
d784bf0c75 | ||
|
|
cf6115a73c | ||
|
|
0991b881de | ||
|
|
e048da6f73 | ||
|
|
6a08133efb | ||
|
|
2e50f5769f | ||
|
|
42e9632c53 | ||
|
|
46c5fb5f2c | ||
|
|
81b41b55fd | ||
|
|
112a971c56 | ||
|
|
9b87a7e699 | ||
|
|
1bd8afeffa | ||
|
|
3414586995 | ||
|
|
06e8d1c40c | ||
|
|
b70b8dfa4c | ||
|
|
57e450a7a8 | ||
|
|
d82846c954 | ||
|
|
714b61846c | ||
|
|
6bb3ad1b96 | ||
|
|
dd820d69ba | ||
|
|
77c80f67ed | ||
|
|
eb4dd90532 | ||
|
|
d3cd25566b | ||
|
|
925ab090af | ||
|
|
8caf29ec01 | ||
|
|
091ce85473 | ||
|
|
133ac463c2 | ||
|
|
400837ff99 | ||
|
|
a9e41c417a | ||
|
|
1845e63e7c | ||
|
|
f13d8f1412 | ||
|
|
bfe0106566 | ||
|
|
7c46e610e7 | ||
|
|
b13de200f5 | ||
|
|
c3d69a9bac | ||
|
|
51a9f78625 | ||
|
|
89b429c768 | ||
|
|
f25315d151 | ||
|
|
193c006669 | ||
|
|
5d0d56f50b | ||
|
|
30b6e412af | ||
|
|
6c90623c8e | ||
|
|
94fe22da62 | ||
|
|
036e7e3797 | ||
|
|
5a811af099 | ||
|
|
7ac2e56fa6 | ||
|
|
eb8f959361 | ||
|
|
27fd114cbf | ||
|
|
ba488f5c33 | ||
|
|
af1f94e5b8 | ||
|
|
22621df084 | ||
|
|
972b4bf248 | ||
|
|
f4aa3de335 | ||
|
|
b2d25a582d | ||
|
|
a3262ad570 | ||
|
|
034283fc83 | ||
|
|
860939d677 | ||
|
|
a5a41bf797 | ||
|
|
aabfc9513e | ||
|
|
3fe9b87736 | ||
|
|
9e338d86f5 | ||
|
|
5f9c6bc84f | ||
|
|
de38e4b777 | ||
|
|
6c4947e9ba | ||
|
|
63df8599dc | ||
|
|
b6003cb428 | ||
|
|
340a51d08e | ||
|
|
507a03e7ee | ||
|
|
fe86f90ba6 | ||
|
|
3769b18a70 | ||
|
|
6d74393833 | ||
|
|
26670e4778 | ||
|
|
555c4c41ab | ||
|
|
b82f25572b | ||
|
|
584784a0f8 | ||
|
|
4fd5070f5d | ||
|
|
241d1d5cdb | ||
|
|
c07671edb4 | ||
|
|
8093820965 | ||
|
|
86cf50565e | ||
|
|
95fd684ad7 | ||
|
|
ce8e76cf94 | ||
|
|
3f8cd52ffd | ||
|
|
f9d3c73c15 | ||
|
|
7e25cc2aa0 | ||
|
|
8b86c7724b | ||
|
|
ea23c16c5a | ||
|
|
3def18f502 | ||
|
|
2bf6f9300a | ||
|
|
fd43b79f96 | ||
|
|
a70808bae9 | ||
|
|
97d617f071 | ||
|
|
0194a635f7 | ||
|
|
e5d0acf0a9 | ||
|
|
622a1a3735 | ||
|
|
f5abb168e3 | ||
|
|
1e3ef1a548 | ||
|
|
275a63655b | ||
|
|
16651db712 | ||
|
|
d03e43facc | ||
|
|
c2ed82f3af | ||
|
|
5fc71dec34 | ||
|
|
d10f20eb71 | ||
|
|
6c0dedd560 | ||
|
|
b29edfbee8 | ||
|
|
75e1dc2bce | ||
|
|
6ba4f6de95 | ||
|
|
de1f055481 | ||
|
|
1a94c6c932 | ||
|
|
1aeaee221d | ||
|
|
862a056105 | ||
|
|
fe704bd44a | ||
|
|
f6cd3bd86d | ||
|
|
0816c8292f | ||
|
|
319538fbed | ||
|
|
4f1e74b553 | ||
|
|
24f8d15b14 | ||
|
|
f4b0443489 | ||
|
|
030d84eb08 | ||
|
|
4847f5c4f6 | ||
|
|
bf6c9e6447 | ||
|
|
f6d99d9a9b | ||
|
|
e38a69f0d2 |
5
.github/workflows/build.yaml
vendored
5
.github/workflows/build.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
- "1.74.1"
|
||||
- "1.77.0"
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
@@ -49,6 +49,9 @@ jobs:
|
||||
- name: Build (default features + guest_debug)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (default features + pvmemcontrol)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (mshv)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
|
||||
2
.github/workflows/docker-image.yaml
vendored
2
.github/workflows/docker-image.yaml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
# generate Docker tags based on the following events/attributes
|
||||
tags: |
|
||||
type=raw,value=20240407-0
|
||||
type=raw,value=20240507-0
|
||||
type=sha
|
||||
|
||||
- name: Build and push
|
||||
|
||||
7
.github/workflows/quality.yaml
vendored
7
.github/workflows/quality.yaml
vendored
@@ -80,6 +80,13 @@ jobs:
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Clippy (default features + pvmemcontrol)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Clippy (default features + tracing)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
||||
3
.github/workflows/release.yaml
vendored
3
.github/workflows/release.yaml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
target: ${{ matrix.platform.target }}
|
||||
args: ${{ matrix.platform.args }}
|
||||
strip: true
|
||||
toolchain: 1.74.1
|
||||
toolchain: "1.77.0"
|
||||
- name: Copy Release Binaries
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
shell: bash
|
||||
@@ -88,6 +88,7 @@ jobs:
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
files: |
|
||||
./${{ matrix.platform.name_ch }}
|
||||
./${{ matrix.platform.name_ch_remote }}
|
||||
|
||||
21
.github/workflows/taplo.yaml
vendored
Normal file
21
.github/workflows/taplo.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Cargo.toml Formatting (taplo)
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
|
||||
jobs:
|
||||
cargo_toml_format:
|
||||
name: Cargo.toml Formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install build dependencies
|
||||
run: sudo apt-get update && sudo apt-get -yqq install build-essential libssl-dev
|
||||
- name: Install taplo
|
||||
run: cargo install taplo-cli --locked
|
||||
- name: Check formatting
|
||||
run: taplo fmt --check
|
||||
5
.taplo.toml
Normal file
5
.taplo.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
include = ["**/Cargo.toml"]
|
||||
|
||||
[formatting]
|
||||
reorder_arrays = true
|
||||
reorder_keys = true
|
||||
@@ -112,5 +112,5 @@ Fixes #88
|
||||
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
```
|
||||
|
||||
Then, after the corresponding PR is merged, Github will automatically close that issue when parsing the
|
||||
Then, after the corresponding PR is merged, GitHub will automatically close that issue when parsing the
|
||||
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
||||
|
||||
715
Cargo.lock
generated
715
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
95
Cargo.toml
95
Cargo.toml
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "cloud-hypervisor"
|
||||
version = "39.0.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
default-run = "cloud-hypervisor"
|
||||
build = "build.rs"
|
||||
license = "LICENSE-APACHE & LICENSE-BSD-3-Clause"
|
||||
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM"
|
||||
default-run = "cloud-hypervisor"
|
||||
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||
license = "Apache-2.0 AND BSD-3-Clause"
|
||||
name = "cloud-hypervisor"
|
||||
version = "41.0.0"
|
||||
# Minimum buildable version:
|
||||
# Keep in sync with version in .github/workflows/build.yaml
|
||||
# Policy on MSRV (see #4318):
|
||||
@@ -15,87 +15,88 @@ homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||
# a.) A dependency requires it,
|
||||
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
||||
# c.) There is a security issue that is addressed by the toolchain update.
|
||||
rust-version = "1.74.1"
|
||||
rust-version = "1.77.0"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
opt-level = "s"
|
||||
strip = true
|
||||
|
||||
[profile.profiling]
|
||||
debug = true
|
||||
inherits = "release"
|
||||
strip = false
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.81"
|
||||
anyhow = "1.0.86"
|
||||
api_client = { path = "api_client" }
|
||||
clap = { version = "4.5.4", features = ["string"] }
|
||||
dhat = { version = "0.3.3", optional = true }
|
||||
epoll = "4.3.3"
|
||||
event_monitor = { path = "event_monitor" }
|
||||
hypervisor = { path = "hypervisor" }
|
||||
libc = "0.2.153"
|
||||
log = { version = "0.4.21", features = ["std"] }
|
||||
libc = "0.2.155"
|
||||
log = { version = "0.4.22", features = ["std"] }
|
||||
option_parser = { path = "option_parser" }
|
||||
seccompiler = "0.4.0"
|
||||
serde_json = "1.0.115"
|
||||
serde_json = "1.0.120"
|
||||
signal-hook = "0.3.17"
|
||||
thiserror = "1.0.58"
|
||||
tpm = { path = "tpm"}
|
||||
thiserror = "1.0.62"
|
||||
tpm = { path = "tpm" }
|
||||
tracer = { path = "tracer" }
|
||||
vm-memory = "0.14.1"
|
||||
vmm = { path = "vmm" }
|
||||
vmm-sys-util = "0.12.1"
|
||||
vm-memory = "0.14.1"
|
||||
zbus = { version = "3.15.2", optional = true }
|
||||
zbus = { version = "4.1.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
dirs = "5.0.1"
|
||||
net_util = { path = "net_util" }
|
||||
once_cell = "1.19.0"
|
||||
serde_json = "1.0.115"
|
||||
serde_json = "1.0.120"
|
||||
test_infra = { path = "test_infra" }
|
||||
wait-timeout = "0.2.0"
|
||||
|
||||
# Please adjust `vmm::feature_list()` accordingly when changing the
|
||||
# feature list below
|
||||
[features]
|
||||
default = ["kvm", "io_uring"]
|
||||
dbus_api = ["zbus", "vmm/dbus_api"]
|
||||
dhat-heap = ["dhat"] # For heap profiling
|
||||
dbus_api = ["vmm/dbus_api", "zbus"]
|
||||
default = ["io_uring", "kvm"]
|
||||
dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling
|
||||
guest_debug = ["vmm/guest_debug"]
|
||||
igvm = ["vmm/igvm", "mshv"]
|
||||
igvm = ["mshv", "vmm/igvm"]
|
||||
io_uring = ["vmm/io_uring"]
|
||||
kvm = ["vmm/kvm"]
|
||||
mshv = ["vmm/mshv"]
|
||||
sev_snp = ["igvm", "vmm/sev_snp", "mshv"]
|
||||
pvmemcontrol = ["vmm/pvmemcontrol"]
|
||||
sev_snp = ["igvm", "mshv", "vmm/sev_snp"]
|
||||
tdx = ["vmm/tdx"]
|
||||
tracing = ["vmm/tracing", "tracer/tracing"]
|
||||
tracing = ["tracer/tracing", "vmm/tracing"]
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"api_client",
|
||||
"arch",
|
||||
"block",
|
||||
"devices",
|
||||
"event_monitor",
|
||||
"hypervisor",
|
||||
"net_gen",
|
||||
"net_util",
|
||||
"option_parser",
|
||||
"pci",
|
||||
"performance-metrics",
|
||||
"rate_limiter",
|
||||
"serial_buffer",
|
||||
"test_infra",
|
||||
"tracer",
|
||||
"vhost_user_block",
|
||||
"vhost_user_net",
|
||||
"virtio-devices",
|
||||
"vmm",
|
||||
"vm-allocator",
|
||||
"vm-device",
|
||||
"vm-migration",
|
||||
"vm-virtio"
|
||||
"api_client",
|
||||
"arch",
|
||||
"block",
|
||||
"devices",
|
||||
"event_monitor",
|
||||
"hypervisor",
|
||||
"net_gen",
|
||||
"net_util",
|
||||
"option_parser",
|
||||
"pci",
|
||||
"performance-metrics",
|
||||
"rate_limiter",
|
||||
"serial_buffer",
|
||||
"test_infra",
|
||||
"tracer",
|
||||
"vhost_user_block",
|
||||
"vhost_user_net",
|
||||
"virtio-devices",
|
||||
"vm-allocator",
|
||||
"vm-device",
|
||||
"vm-migration",
|
||||
"vm-virtio",
|
||||
"vmm",
|
||||
]
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
[package]
|
||||
name = "api_client"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
name = "api_client"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0.62"
|
||||
vmm-sys-util = "0.12.1"
|
||||
|
||||
@@ -3,41 +3,27 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::fmt;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::io::RawFd;
|
||||
use thiserror::Error;
|
||||
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Error writing to or reading from HTTP socket: {0}")]
|
||||
Socket(std::io::Error),
|
||||
#[error("Error sending file descriptors: {0}")]
|
||||
SocketSendFds(vmm_sys_util::errno::Error),
|
||||
#[error("Error parsing HTTP status code: {0}")]
|
||||
StatusCodeParsing(std::num::ParseIntError),
|
||||
#[error("HTTP output is missing protocol statement")]
|
||||
MissingProtocol,
|
||||
#[error("Error parsing HTTP Content-Length field: {0}")]
|
||||
ContentLengthParsing(std::num::ParseIntError),
|
||||
#[error("Server responded with an error: {0:?}")]
|
||||
ServerResponse(StatusCode, Option<String>),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use Error::*;
|
||||
match self {
|
||||
Socket(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
|
||||
SocketSendFds(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
|
||||
StatusCodeParsing(e) => write!(f, "Error parsing HTTP status code: {e}"),
|
||||
MissingProtocol => write!(f, "HTTP output is missing protocol statement"),
|
||||
ContentLengthParsing(e) => write!(f, "Error parsing HTTP Content-Length field: {e}"),
|
||||
ServerResponse(s, o) => {
|
||||
if let Some(o) = o {
|
||||
write!(f, "Server responded with an error: {s:?}: {o}")
|
||||
} else {
|
||||
write!(f, "Server responded with an error: {s:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum StatusCode {
|
||||
Continue,
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
[package]
|
||||
name = "arch"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
name = "arch"
|
||||
version = "0.1.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
kvm = []
|
||||
sev_snp = []
|
||||
tdx = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.81"
|
||||
anyhow = "1.0.86"
|
||||
byteorder = "1.5.0"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.153"
|
||||
linux-loader = { version = "0.11.0", features = ["elf", "bzimage", "pe"] }
|
||||
log = "0.4.21"
|
||||
serde = { version = "1.0.197", features = ["rc", "derive"] }
|
||||
thiserror = "1.0.58"
|
||||
libc = "0.2.155"
|
||||
linux-loader = { version = "0.11.0", features = ["bzimage", "elf", "pe"] }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
thiserror = "1.0.62"
|
||||
uuid = "1.8.0"
|
||||
vm-memory = { version = "0.14.1", features = ["backend-mmap", "backend-bitmap"] }
|
||||
vm-memory = { version = "0.14.1", features = [
|
||||
"backend-bitmap",
|
||||
"backend-mmap",
|
||||
] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = { version = "0.12.1", features = ["with-serde"] }
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ use super::layout::{
|
||||
};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use thiserror::Error;
|
||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
||||
|
||||
@@ -80,9 +81,10 @@ pub trait DeviceInfoForFdt {
|
||||
}
|
||||
|
||||
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Failure in writing FDT in memory.
|
||||
#[error("Failure in writing FDT in memory: {0}")]
|
||||
WriteFdtToMemory(GuestMemoryError),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
@@ -523,7 +525,7 @@ fn create_memory_node(
|
||||
let memory_region_size: u64 = memory_region.size() as u64;
|
||||
mem_reg_prop.push(memory_region_start_addr);
|
||||
mem_reg_prop.push(memory_region_size);
|
||||
// Set the node address the first non-zero regison address
|
||||
// Set the node address the first non-zero region address
|
||||
if node_memory_addr == 0 {
|
||||
node_memory_addr = memory_region_start_addr;
|
||||
}
|
||||
@@ -569,7 +571,7 @@ fn create_memory_node(
|
||||
|
||||
if ram_regions.len() > 2 {
|
||||
panic!(
|
||||
"There should be up to two non-continuous regions, devidided by the
|
||||
"There should be up to two non-continuous regions, divided by the
|
||||
gap at the end of 32bit address space."
|
||||
);
|
||||
}
|
||||
@@ -894,7 +896,7 @@ fn create_pci_nodes(
|
||||
for pci_device_info_elem in pci_device_info.iter() {
|
||||
// EDK2 requires the PCIe high space above 4G address.
|
||||
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
||||
// could fall bellow 4G.
|
||||
// could fall below 4G.
|
||||
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
||||
// But the address written in ACPI is not impacted.
|
||||
let (pci_device_base_64bit, pci_device_size_64bit) =
|
||||
|
||||
@@ -18,32 +18,40 @@ use log::{log_enabled, Level};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use thiserror::Error;
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
||||
|
||||
pub const _NSIG: i32 = 65;
|
||||
|
||||
/// Errors thrown while configuring aarch64 system.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Failed to create a FDT.
|
||||
#[error("Failed to create a FDT")]
|
||||
SetupFdt,
|
||||
|
||||
/// Failed to write FDT to memory.
|
||||
#[error("Failed to write FDT to memory: {0}")]
|
||||
WriteFdtToMemory(fdt::Error),
|
||||
|
||||
/// Failed to create a GIC.
|
||||
#[error("Failed to create a GIC")]
|
||||
SetupGic,
|
||||
|
||||
/// Failed to compute the initramfs address.
|
||||
#[error("Failed to compute the initramfs address")]
|
||||
InitramfsAddress,
|
||||
|
||||
/// Error configuring the general purpose registers
|
||||
#[error("Error configuring the general purpose registers: {0}")]
|
||||
RegsConfiguration(hypervisor::HypervisorCpuError),
|
||||
|
||||
/// Error configuring the MPIDR register
|
||||
#[error("Error configuring the MPIDR register: {0}")]
|
||||
VcpuRegMpidr(hypervisor::HypervisorCpuError),
|
||||
|
||||
/// Error initializing PMU for vcpu
|
||||
#[error("Error initializing PMU for vcpu")]
|
||||
VcpuInitPmu,
|
||||
}
|
||||
|
||||
|
||||
@@ -5,18 +5,23 @@
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::os::fd::AsFd;
|
||||
use std::result;
|
||||
use thiserror::Error;
|
||||
use vm_memory::{GuestAddress, GuestMemory};
|
||||
|
||||
/// Errors thrown while loading UEFI binary
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Unable to seek to UEFI image start.
|
||||
#[error("Unable to seek to UEFI image start")]
|
||||
SeekUefiStart,
|
||||
/// Unable to seek to UEFI image end.
|
||||
#[error("Unable to seek to UEFI image end")]
|
||||
SeekUefiEnd,
|
||||
/// UEFI image too big.
|
||||
#[error("UEFI image too big")]
|
||||
UefiTooBig,
|
||||
/// Unable to read UEFI image
|
||||
#[error("Unable to read UEFI image")]
|
||||
ReadUefiImage,
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
@@ -23,6 +23,7 @@ use linux_loader::loader::elf::start_info::{
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
use std::mem;
|
||||
use thiserror::Error;
|
||||
use vm_memory::{
|
||||
Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
||||
GuestMemoryRegion, GuestUsize,
|
||||
@@ -33,6 +34,7 @@ use std::arch::x86_64;
|
||||
pub mod tdx;
|
||||
|
||||
// CPUID feature bits
|
||||
#[cfg(feature = "kvm")]
|
||||
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
||||
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
|
||||
const MTRR_EDX_BIT: u8 = 12; // Hypervisor ecx bit.
|
||||
@@ -127,64 +129,83 @@ pub struct CpuidConfig {
|
||||
pub amx: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Error writing MP table to memory.
|
||||
#[error("Error writing MP table to memory: {0}")]
|
||||
MpTableSetup(mptable::Error),
|
||||
|
||||
/// Error configuring the general purpose registers
|
||||
#[error("Error configuring the general purpose registers: {0}")]
|
||||
RegsConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the special registers
|
||||
#[error("Error configuring the special registers: {0}")]
|
||||
SregsConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the floating point related registers
|
||||
#[error("Error configuring the floating point related registers: {0}")]
|
||||
FpuConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the MSR registers
|
||||
#[error("Error configuring the MSR registers: {0}")]
|
||||
MsrsConfiguration(regs::Error),
|
||||
|
||||
/// Failed to set supported CPUs.
|
||||
#[error("Failed to set supported CPUs: {0}")]
|
||||
SetSupportedCpusFailed(anyhow::Error),
|
||||
|
||||
/// Cannot set the local interruption due to bad configuration.
|
||||
#[error("Cannot set the local interruption due to bad configuration: {0}")]
|
||||
LocalIntConfiguration(anyhow::Error),
|
||||
|
||||
/// Error setting up SMBIOS table
|
||||
#[error("Error setting up SMBIOS table: {0}")]
|
||||
SmbiosSetup(smbios::Error),
|
||||
|
||||
/// Could not find any SGX EPC section
|
||||
#[error("Could not find any SGX EPC section")]
|
||||
NoSgxEpcSection,
|
||||
|
||||
/// Missing SGX CPU feature
|
||||
#[error("Missing SGX CPU feature")]
|
||||
MissingSgxFeature,
|
||||
|
||||
/// Missing SGX_LC CPU feature
|
||||
#[error("Missing SGX_LC CPU feature")]
|
||||
MissingSgxLaunchControlFeature,
|
||||
|
||||
/// Error getting supported CPUID through the hypervisor (kvm/mshv) API
|
||||
#[error("Error getting supported CPUID through the hypervisor API: {0}")]
|
||||
CpuidGetSupported(HypervisorError),
|
||||
|
||||
/// Error populating CPUID with KVM HyperV emulation details
|
||||
#[error("Error populating CPUID with KVM HyperV emulation details: {0}")]
|
||||
CpuidKvmHyperV(vmm_sys_util::fam::Error),
|
||||
|
||||
/// Error populating CPUID with CPU identification
|
||||
#[error("Error populating CPUID with CPU identification: {0}")]
|
||||
CpuidIdentification(vmm_sys_util::fam::Error),
|
||||
|
||||
/// Error checking CPUID compatibility
|
||||
#[error("Error checking CPUID compatibility")]
|
||||
CpuidCheckCompatibility,
|
||||
|
||||
// Error writing EBDA address
|
||||
#[error("Error writing EBDA address: {0}")]
|
||||
EbdaSetup(vm_memory::GuestMemoryError),
|
||||
|
||||
// Error getting CPU TSC frequency
|
||||
#[error("Error getting CPU TSC frequency: {0}")]
|
||||
GetTscFrequency(HypervisorCpuError),
|
||||
|
||||
/// Error retrieving TDX capabilities through the hypervisor (kvm/mshv) API
|
||||
#[cfg(feature = "tdx")]
|
||||
#[error("Error retrieving TDX capabilities through the hypervisor API: {0}")]
|
||||
TdxCapabilities(HypervisorError),
|
||||
|
||||
/// Failed to configure E820 map for bzImage
|
||||
#[error("Failed to configure E820 map for bzImage")]
|
||||
E820Configuration,
|
||||
}
|
||||
|
||||
@@ -557,7 +578,7 @@ impl CpuidFeatureEntry {
|
||||
if !entry_compatible {
|
||||
error!(
|
||||
"Detected incompatible CPUID entry: leaf={:#02x} (subleaf={:#02x}), register='{:?}', \
|
||||
compatilbe_check='{:?}', source VM feature='{:#04x}', destination VM feature'{:#04x}'.",
|
||||
compatible_check='{:?}', source VM feature='{:#04x}', destination VM feature'{:#04x}'.",
|
||||
entry.function, entry.index, entry.feature_reg,
|
||||
entry.compatible_check, src_vm_feature, dest_vm_feature
|
||||
);
|
||||
@@ -599,17 +620,8 @@ pub fn generate_common_cpuid(
|
||||
"Generating guest CPUID for with physical address size: {}",
|
||||
config.phys_bits
|
||||
);
|
||||
let cpuid_patches = vec![
|
||||
// Patch tsc deadline timer bit
|
||||
CpuidPatch {
|
||||
function: 1,
|
||||
index: 0,
|
||||
flags_bit: None,
|
||||
eax_bit: None,
|
||||
ebx_bit: None,
|
||||
ecx_bit: Some(TSC_DEADLINE_TIMER_ECX_BIT),
|
||||
edx_bit: None,
|
||||
},
|
||||
#[allow(unused_mut)]
|
||||
let mut cpuid_patches = vec![
|
||||
// Patch hypervisor bit
|
||||
CpuidPatch {
|
||||
function: 1,
|
||||
@@ -632,6 +644,23 @@ pub fn generate_common_cpuid(
|
||||
},
|
||||
];
|
||||
|
||||
#[cfg(feature = "kvm")]
|
||||
if matches!(
|
||||
hypervisor.hypervisor_type(),
|
||||
hypervisor::HypervisorType::Kvm
|
||||
) {
|
||||
// Patch tsc deadline timer bit
|
||||
cpuid_patches.push(CpuidPatch {
|
||||
function: 1,
|
||||
index: 0,
|
||||
flags_bit: None,
|
||||
eax_bit: None,
|
||||
ebx_bit: None,
|
||||
ecx_bit: Some(TSC_DEADLINE_TIMER_ECX_BIT),
|
||||
edx_bit: None,
|
||||
});
|
||||
}
|
||||
|
||||
// Supported CPUID
|
||||
let mut cpuid = hypervisor
|
||||
.get_supported_cpuid()
|
||||
@@ -682,6 +711,20 @@ 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 {
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0005 {
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0005) };
|
||||
entry.eax = leaf.eax;
|
||||
entry.ebx = leaf.ebx;
|
||||
entry.ecx = leaf.ecx;
|
||||
entry.edx = leaf.edx;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
@@ -800,12 +843,17 @@ pub fn configure_vcpu(
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x8000_001e, Some(0), CpuidReg::EAX, x2apic_id);
|
||||
}
|
||||
|
||||
// Set ApicId in cpuid for each vcpu
|
||||
// SAFETY: get host cpuid when eax=1
|
||||
let mut cpu_ebx = unsafe { core::arch::x86_64::__cpuid(1) }.ebx;
|
||||
cpu_ebx &= 0xffffff;
|
||||
cpu_ebx |= x2apic_id << 24;
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x1, None, CpuidReg::EBX, cpu_ebx);
|
||||
// Set ApicId in cpuid for each vcpu - found in cpuid ebx when eax = 1
|
||||
let mut apic_id_patched = false;
|
||||
for entry in &mut cpuid {
|
||||
if entry.function == 1 {
|
||||
entry.ebx &= 0xffffff;
|
||||
entry.ebx |= x2apic_id << 24;
|
||||
apic_id_patched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(apic_id_patched);
|
||||
|
||||
if let Some(t) = topology {
|
||||
update_cpuid_topology(&mut cpuid, t.0, t.1, t.2, cpu_vendor, id);
|
||||
|
||||
@@ -17,7 +17,7 @@ pub const MP_IRQDIR_DEFAULT: ::std::os::raw::c_uint = 0;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpf_intel {
|
||||
pub signature: [::std::os::raw::c_char; 4usize],
|
||||
pub signature: [::std::os::raw::c_uchar; 4usize],
|
||||
pub physptr: ::std::os::raw::c_uint,
|
||||
pub length: ::std::os::raw::c_uchar,
|
||||
pub specification: ::std::os::raw::c_uchar,
|
||||
@@ -32,12 +32,12 @@ pub struct mpf_intel {
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpc_table {
|
||||
pub signature: [::std::os::raw::c_char; 4usize],
|
||||
pub signature: [::std::os::raw::c_uchar; 4usize],
|
||||
pub length: ::std::os::raw::c_ushort,
|
||||
pub spec: ::std::os::raw::c_char,
|
||||
pub checksum: ::std::os::raw::c_char,
|
||||
pub oem: [::std::os::raw::c_char; 8usize],
|
||||
pub productid: [::std::os::raw::c_char; 12usize],
|
||||
pub spec: ::std::os::raw::c_uchar,
|
||||
pub checksum: ::std::os::raw::c_uchar,
|
||||
pub oem: [::std::os::raw::c_uchar; 8usize],
|
||||
pub productid: [::std::os::raw::c_uchar; 12usize],
|
||||
pub oemptr: ::std::os::raw::c_uint,
|
||||
pub oemsize: ::std::os::raw::c_ushort,
|
||||
pub oemcount: ::std::os::raw::c_ushort,
|
||||
@@ -106,9 +106,9 @@ pub struct mpc_lintsrc {
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpc_oemtable {
|
||||
pub signature: [::std::os::raw::c_char; 4usize],
|
||||
pub signature: [::std::os::raw::c_uchar; 4usize],
|
||||
pub length: ::std::os::raw::c_ushort,
|
||||
pub rev: ::std::os::raw::c_char,
|
||||
pub checksum: ::std::os::raw::c_char,
|
||||
pub mpc: [::std::os::raw::c_char; 8usize],
|
||||
pub rev: ::std::os::raw::c_uchar,
|
||||
pub checksum: ::std::os::raw::c_uchar,
|
||||
pub mpc: [::std::os::raw::c_uchar; 8usize],
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
||||
use crate::x86_64::{get_x2apic_id, mpspec};
|
||||
use crate::GuestMemoryMmap;
|
||||
use libc::c_char;
|
||||
use libc::c_uchar;
|
||||
use std::mem;
|
||||
use std::result;
|
||||
use std::slice;
|
||||
use thiserror::Error;
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
@@ -49,29 +50,40 @@ unsafe impl ByteValued for MpcLintsrcWrapper {}
|
||||
// SAFETY: see above
|
||||
unsafe impl ByteValued for MpfIntelWrapper {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// There was too little guest memory to store the entire MP table.
|
||||
#[error("There was too little guest memory to store the entire MP table")]
|
||||
NotEnoughMemory,
|
||||
/// The MP table has too little address space to be stored.
|
||||
#[error("The MP table has too little address space to be stored")]
|
||||
AddressOverflow,
|
||||
/// Failure while zeroing out the memory for the MP table.
|
||||
#[error("Failure while zeroing out the memory for the MP table: {0}")]
|
||||
Clear(GuestMemoryError),
|
||||
/// Number of CPUs exceeds the maximum supported CPUs
|
||||
#[error("Number of CPUs exceeds the maximum supported CPUs")]
|
||||
TooManyCpus,
|
||||
/// Failure to write the MP floating pointer.
|
||||
#[error("Failure to write the MP floating pointer: {0}")]
|
||||
WriteMpfIntel(GuestMemoryError),
|
||||
/// Failure to write MP CPU entry.
|
||||
#[error("Failure to write MP CPU entry: {0}")]
|
||||
WriteMpcCpu(GuestMemoryError),
|
||||
/// Failure to write MP ioapic entry.
|
||||
#[error("Failure to write MP ioapic entry: {0}")]
|
||||
WriteMpcIoapic(GuestMemoryError),
|
||||
/// Failure to write MP bus entry.
|
||||
#[error("Failure to write MP bus entry: {0}")]
|
||||
WriteMpcBus(GuestMemoryError),
|
||||
/// Failure to write MP interrupt source entry.
|
||||
#[error("Failure to write MP interrupt source entry: {0}")]
|
||||
WriteMpcIntsrc(GuestMemoryError),
|
||||
/// Failure to write MP local interrupt source entry.
|
||||
#[error("Failure to write MP local interrupt source entry: {0}")]
|
||||
WriteMpcLintsrc(GuestMemoryError),
|
||||
/// Failure to write MP table header.
|
||||
#[error("Failure to write MP table header: {0}")]
|
||||
WriteMpcTable(GuestMemoryError),
|
||||
}
|
||||
|
||||
@@ -82,18 +94,13 @@ pub type Result<T> = result::Result<T, Error>;
|
||||
// a large number for FC usecases.
|
||||
pub const MAX_SUPPORTED_CPUS: u32 = 254;
|
||||
|
||||
// Convenience macro for making arrays of diverse character types.
|
||||
macro_rules! char_array {
|
||||
($t:ty; $( $c:expr ),*) => ( [ $( $c as $t ),* ] )
|
||||
}
|
||||
|
||||
// Most of these variables are sourced from the Intel MP Spec 1.4.
|
||||
const SMP_MAGIC_IDENT: [c_char; 4] = char_array!(c_char; '_', 'M', 'P', '_');
|
||||
const MPC_SIGNATURE: [c_char; 4] = char_array!(c_char; 'P', 'C', 'M', 'P');
|
||||
const MPC_SPEC: i8 = 4;
|
||||
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'F', 'C', ' ', ' ', ' ', ' ', ' ', ' ');
|
||||
const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
|
||||
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
|
||||
const SMP_MAGIC_IDENT: &[c_uchar; 4] = b"_MP_";
|
||||
const MPC_SIGNATURE: &[c_uchar; 4] = b"PCMP";
|
||||
const MPC_SPEC: u8 = 4;
|
||||
const MPC_OEM: &[c_uchar; 8] = b"FC ";
|
||||
const MPC_PRODUCT_ID: &[c_uchar; 12] = &[b'0'; 12];
|
||||
const BUS_TYPE_ISA: &[c_uchar; 6] = b"ISA ";
|
||||
const APIC_VERSION: u8 = 0x14;
|
||||
const CPU_STEPPING: u32 = 0x600;
|
||||
const CPU_FEATURE_APIC: u32 = 0x200;
|
||||
@@ -168,7 +175,7 @@ pub fn setup_mptable(
|
||||
{
|
||||
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
||||
let size = mem::size_of::<MpfIntelWrapper>() as u64;
|
||||
mpf_intel.0.signature = SMP_MAGIC_IDENT;
|
||||
mpf_intel.0.signature = *SMP_MAGIC_IDENT;
|
||||
mpf_intel.0.length = 1;
|
||||
mpf_intel.0.specification = 4;
|
||||
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
|
||||
@@ -209,7 +216,7 @@ pub fn setup_mptable(
|
||||
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
|
||||
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
|
||||
mpc_bus.0.busid = 0;
|
||||
mpc_bus.0.bustype = BUS_TYPE_ISA;
|
||||
mpc_bus.0.bustype = *BUS_TYPE_ISA;
|
||||
mem.write_obj(mpc_bus, base_mp)
|
||||
.map_err(Error::WriteMpcBus)?;
|
||||
base_mp = base_mp.unchecked_add(size as u64);
|
||||
@@ -280,14 +287,14 @@ pub fn setup_mptable(
|
||||
|
||||
{
|
||||
let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default());
|
||||
mpc_table.0.signature = MPC_SIGNATURE;
|
||||
mpc_table.0.signature = *MPC_SIGNATURE;
|
||||
mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16;
|
||||
mpc_table.0.spec = MPC_SPEC;
|
||||
mpc_table.0.oem = MPC_OEM;
|
||||
mpc_table.0.productid = MPC_PRODUCT_ID;
|
||||
mpc_table.0.oem = *MPC_OEM;
|
||||
mpc_table.0.productid = *MPC_PRODUCT_ID;
|
||||
mpc_table.0.lapic = APIC_START.0 as u32;
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
||||
mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8;
|
||||
mpc_table.0.checksum = (!checksum).wrapping_add(1);
|
||||
mem.write_obj(mpc_table, table_base)
|
||||
.map_err(Error::WriteMpcTable)?;
|
||||
}
|
||||
|
||||
@@ -15,33 +15,46 @@ use hypervisor::arch::x86::regs::CR0_PE;
|
||||
use hypervisor::arch::x86::{FpuState, SpecialRegisters, StandardRegisters};
|
||||
use std::sync::Arc;
|
||||
use std::{mem, result};
|
||||
use thiserror::Error;
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Failed to get SREGs for this CPU.
|
||||
#[error("Failed to get SREGs for this CPU: {0}")]
|
||||
GetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Failed to set base registers for this CPU.
|
||||
#[error("Failed to set base registers for this CPU: {0}")]
|
||||
SetBaseRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Failed to configure the FPU.
|
||||
#[error("Failed to configure the FPU: {0}")]
|
||||
SetFpuRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Setting up MSRs failed.
|
||||
#[error("Setting up MSRs failed: {0}")]
|
||||
SetModelSpecificRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Failed to set SREGs for this CPU.
|
||||
#[error("Failed to set SREGs for this CPU: {0}")]
|
||||
SetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Checking the GDT address failed.
|
||||
#[error("Checking the GDT address failed")]
|
||||
CheckGdtAddr,
|
||||
/// Writing the GDT to RAM failed.
|
||||
#[error("Writing the GDT to RAM failed: {0}")]
|
||||
WriteGdt(GuestMemoryError),
|
||||
/// Writing the IDT to RAM failed.
|
||||
#[error("Writing the IDT to RAM failed: {0}")]
|
||||
WriteIdt(GuestMemoryError),
|
||||
/// Writing PDPTE to RAM failed.
|
||||
#[error("Writing PDPTE to RAM failed: {0}")]
|
||||
WritePdpteAddress(GuestMemoryError),
|
||||
/// Writing PDE to RAM failed.
|
||||
#[error("Writing PDE to RAM failed: {0}")]
|
||||
WritePdeAddress(GuestMemoryError),
|
||||
/// Writing PML4 to RAM failed.
|
||||
#[error("Writing PML4 to RAM failed: {0}")]
|
||||
WritePml4Address(GuestMemoryError),
|
||||
/// Writing PML5 to RAM failed.
|
||||
#[error("Writing PML5 to RAM failed: {0}")]
|
||||
WritePml5Address(GuestMemoryError),
|
||||
}
|
||||
|
||||
|
||||
@@ -8,53 +8,36 @@
|
||||
|
||||
use crate::layout::SMBIOS_START;
|
||||
use crate::GuestMemoryMmap;
|
||||
use std::fmt::{self, Display};
|
||||
use std::mem;
|
||||
use std::result;
|
||||
use std::slice;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
use vm_memory::ByteValued;
|
||||
use vm_memory::{Address, Bytes, GuestAddress};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// There was too little guest memory to store the entire SMBIOS table.
|
||||
#[error("There was too little guest memory to store the SMBIOS table")]
|
||||
NotEnoughMemory,
|
||||
/// The SMBIOS table has too little address space to be stored.
|
||||
#[error("The SMBIOS table has too little address space to be stored")]
|
||||
AddressOverflow,
|
||||
/// Failure while zeroing out the memory for the SMBIOS table.
|
||||
#[error("Failure while zeroing out the memory for the SMBIOS table")]
|
||||
Clear,
|
||||
/// Failure to write SMBIOS entrypoint structure
|
||||
#[error("Failure to write SMBIOS entrypoint structure")]
|
||||
WriteSmbiosEp,
|
||||
/// Failure to write additional data to memory
|
||||
#[error("Failure to write additional data to memory")]
|
||||
WriteData,
|
||||
/// Failure to parse uuid, uuid format may be error
|
||||
#[error("Failure to parse uuid: {0}")]
|
||||
ParseUuid(uuid::Error),
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
let description = match self {
|
||||
NotEnoughMemory => {
|
||||
"There was too little guest memory to store the SMBIOS table".to_string()
|
||||
}
|
||||
AddressOverflow => {
|
||||
"The SMBIOS table has too little address space to be stored".to_string()
|
||||
}
|
||||
Clear => "Failure while zeroing out the memory for the SMBIOS table".to_string(),
|
||||
WriteSmbiosEp => "Failure to write SMBIOS entrypoint structure".to_string(),
|
||||
WriteData => "Failure to write additional data to memory".to_string(),
|
||||
ParseUuid(e) => format!("Failure to parse uuid: {e}"),
|
||||
};
|
||||
|
||||
write!(f, "SMBIOS error: {description}")
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Constants sourced from SMBIOS Spec 3.2.0.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
authors = ["The Chromium OS Authors", "The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
name = "block"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["The Cloud Hypervisor Authors", "The Chromium OS Authors"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
@@ -12,15 +12,19 @@ io_uring = ["dep:io-uring"]
|
||||
byteorder = "1.5.0"
|
||||
crc-any = "2.4.4"
|
||||
io-uring = { version = "0.6.3", optional = true }
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
remain = "0.2.13"
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
remain = "0.2.14"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
smallvec = "1.13.2"
|
||||
thiserror = "1.0.58"
|
||||
thiserror = "1.0.62"
|
||||
uuid = { version = "1.8.0", features = ["v4"] }
|
||||
virtio-bindings = { version = "0.2.2", features = ["virtio-v5_0_0"] }
|
||||
virtio-queue = "0.11.0"
|
||||
vm-memory = { version = "0.14.1", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
|
||||
virtio-queue = "0.12.0"
|
||||
vm-memory = { version = "0.14.1", features = [
|
||||
"backend-atomic",
|
||||
"backend-bitmap",
|
||||
"backend-mmap",
|
||||
] }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = "0.12.1"
|
||||
|
||||
@@ -56,16 +56,14 @@ use thiserror::Error;
|
||||
use virtio_bindings::virtio_blk::*;
|
||||
use virtio_queue::DescriptorChain;
|
||||
use vm_memory::{
|
||||
bitmap::AtomicBitmap, bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory,
|
||||
GuestMemoryError, GuestMemoryLoadGuard,
|
||||
bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError,
|
||||
GuestMemoryLoadGuard,
|
||||
};
|
||||
use vm_virtio::{AccessPlatform, Translatable};
|
||||
use vmm_sys_util::aio;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::{ioctl_io_nr, ioctl_ioc_nr};
|
||||
|
||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
||||
|
||||
const SECTOR_SHIFT: u8 = 9;
|
||||
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
||||
|
||||
@@ -166,8 +164,8 @@ pub enum ExecuteError {
|
||||
}
|
||||
|
||||
impl ExecuteError {
|
||||
pub fn status(&self) -> u32 {
|
||||
match *self {
|
||||
pub fn status(&self) -> u8 {
|
||||
let status = match *self {
|
||||
ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
|
||||
@@ -182,7 +180,8 @@ impl ExecuteError {
|
||||
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::TemporaryBufferAllocation(_) => VIRTIO_BLK_S_IOERR,
|
||||
}
|
||||
};
|
||||
status as u8
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,8 +194,8 @@ pub enum RequestType {
|
||||
Unsupported(u32),
|
||||
}
|
||||
|
||||
pub fn request_type(
|
||||
mem: &GuestMemoryMmap,
|
||||
pub fn request_type<B: Bitmap + 'static>(
|
||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||
desc_addr: GuestAddress,
|
||||
) -> result::Result<RequestType, Error> {
|
||||
let type_ = mem.read_obj(desc_addr).map_err(Error::GuestMemory)?;
|
||||
@@ -209,7 +208,10 @@ pub fn request_type(
|
||||
}
|
||||
}
|
||||
|
||||
fn sector(mem: &GuestMemoryMmap, desc_addr: GuestAddress) -> result::Result<u64, Error> {
|
||||
fn sector<B: Bitmap + 'static>(
|
||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||
desc_addr: GuestAddress,
|
||||
) -> result::Result<u64, Error> {
|
||||
const SECTOR_OFFSET: usize = 8;
|
||||
let addr = match mem.checked_offset(desc_addr, SECTOR_OFFSET) {
|
||||
Some(v) => v,
|
||||
@@ -239,16 +241,15 @@ pub struct Request {
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn parse(
|
||||
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap>>,
|
||||
pub fn parse<B: Bitmap + 'static>(
|
||||
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<vm_memory::GuestMemoryMmap<B>>>,
|
||||
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
||||
) -> result::Result<Request, Error> {
|
||||
let hdr_desc = desc_chain
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.map_err(|e| {
|
||||
.inspect_err(|_| {
|
||||
error!("Missing head descriptor");
|
||||
e
|
||||
})?;
|
||||
|
||||
// The head contains the request type which MUST be readable.
|
||||
@@ -274,9 +275,8 @@ impl Request {
|
||||
let mut desc = desc_chain
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.map_err(|e| {
|
||||
.inspect_err(|_| {
|
||||
error!("Only head descriptor present: request = {:?}", req);
|
||||
e
|
||||
})?;
|
||||
|
||||
if !desc.has_next() {
|
||||
@@ -307,9 +307,8 @@ impl Request {
|
||||
desc = desc_chain
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.map_err(|e| {
|
||||
.inspect_err(|_| {
|
||||
error!("DescriptorChain corrupted: request = {:?}", req);
|
||||
e
|
||||
})?;
|
||||
}
|
||||
status_desc = desc;
|
||||
@@ -331,11 +330,11 @@ impl Request {
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
pub fn execute<T: Seek + Read + Write>(
|
||||
pub fn execute<T: Seek + Read + Write, B: Bitmap + 'static>(
|
||||
&self,
|
||||
disk: &mut T,
|
||||
disk_nsectors: u64,
|
||||
mem: &GuestMemoryMmap,
|
||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||
serial: &[u8],
|
||||
) -> result::Result<u32, ExecuteError> {
|
||||
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
|
||||
@@ -388,9 +387,9 @@ impl Request {
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
pub fn execute_async(
|
||||
pub fn execute_async<B: Bitmap + 'static>(
|
||||
&mut self,
|
||||
mem: &GuestMemoryMmap,
|
||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||
disk_nsectors: u64,
|
||||
disk_image: &mut dyn AsyncIo,
|
||||
serial: &[u8],
|
||||
@@ -658,7 +657,9 @@ where
|
||||
let mut slices: SmallVec<[IoSliceMut; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||
for iovec in iovecs.iter() {
|
||||
// SAFETY: on Linux IoSliceMut wraps around libc::iovec
|
||||
slices.push(IoSliceMut::new(unsafe { std::mem::transmute(*iovec) }));
|
||||
slices.push(IoSliceMut::new(unsafe {
|
||||
std::mem::transmute::<libc::iovec, &mut [u8]>(*iovec)
|
||||
}));
|
||||
}
|
||||
|
||||
let result = {
|
||||
@@ -691,7 +692,9 @@ where
|
||||
let mut slices: SmallVec<[IoSlice; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||
for iovec in iovecs.iter() {
|
||||
// SAFETY: on Linux IoSlice wraps around libc::iovec
|
||||
slices.push(IoSlice::new(unsafe { std::mem::transmute(*iovec) }));
|
||||
slices.push(IoSlice::new(unsafe {
|
||||
std::mem::transmute::<libc::iovec, &mut [u8]>(*iovec)
|
||||
}));
|
||||
}
|
||||
|
||||
let result = {
|
||||
|
||||
@@ -19,11 +19,11 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use libc::{EINVAL, ENOSPC, ENOTSUP};
|
||||
use remain::sorted;
|
||||
use std::cmp::{max, min};
|
||||
use std::fmt::{self, Display};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::mem::size_of;
|
||||
use std::str;
|
||||
use thiserror::Error;
|
||||
use vmm_sys_util::{
|
||||
file_traits::FileSetLen, file_traits::FileSync, seek_hole::SeekHole, write_zeroes::PunchHole,
|
||||
write_zeroes::WriteZeroesAt,
|
||||
@@ -31,106 +31,98 @@ use vmm_sys_util::{
|
||||
|
||||
pub use crate::qcow::raw_file::RawFile;
|
||||
|
||||
/// Nesting depth limit for disk formats that can open other disk files.
|
||||
const MAX_NESTING_DEPTH: u32 = 10;
|
||||
|
||||
#[sorted]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Backing file io error: {0}")]
|
||||
BackingFileIo(io::Error),
|
||||
BackingFileOpen(Box<crate::Error>),
|
||||
#[error("Backing file open error: {0}")]
|
||||
BackingFileOpen(Box<Error>),
|
||||
#[error("Backing file name is too long: {0} bytes over")]
|
||||
BackingFileTooLong(usize),
|
||||
#[error("Compressed blocks not supported")]
|
||||
CompressedBlocksNotSupported,
|
||||
#[error("Failed to evict cache: {0}")]
|
||||
EvictingCache(io::Error),
|
||||
#[error("File larger than max of {}: {0}", MAX_QCOW_FILE_SIZE)]
|
||||
FileTooBig(u64),
|
||||
#[error("Failed to get file size: {0}")]
|
||||
GettingFileSize(io::Error),
|
||||
#[error("Failed to get refcount: {0}")]
|
||||
GettingRefcount(refcount::Error),
|
||||
#[error("Failed to parse filename: {0}")]
|
||||
InvalidBackingFileName(str::Utf8Error),
|
||||
#[error("Invalid cluster index")]
|
||||
InvalidClusterIndex,
|
||||
#[error("Invalid cluster size")]
|
||||
InvalidClusterSize,
|
||||
#[error("Invalid index")]
|
||||
InvalidIndex,
|
||||
#[error("Invalid L1 table offset")]
|
||||
InvalidL1TableOffset,
|
||||
#[error("Invalid L1 table size: {0}")]
|
||||
InvalidL1TableSize(u32),
|
||||
#[error("Invalid magic")]
|
||||
InvalidMagic,
|
||||
#[error("Invalid offset: {0}")]
|
||||
InvalidOffset(u64),
|
||||
#[error("Invalid refcount table offset")]
|
||||
InvalidRefcountTableOffset,
|
||||
#[error("Invalid refcount table size: {0}")]
|
||||
InvalidRefcountTableSize(u64),
|
||||
#[error("Maximum disk nesting depth exceeded")]
|
||||
MaxNestingDepthExceeded,
|
||||
#[error("No free clusters")]
|
||||
NoFreeClusters,
|
||||
#[error("No refcount clusters")]
|
||||
NoRefcountClusters,
|
||||
#[error("Not enough space for refcounts")]
|
||||
NotEnoughSpaceForRefcounts,
|
||||
#[error("Failed to open file {0}")]
|
||||
OpeningFile(io::Error),
|
||||
#[error("Failed to read data: {0}")]
|
||||
ReadingData(io::Error),
|
||||
#[error("Failed to read header: {0}")]
|
||||
ReadingHeader(io::Error),
|
||||
#[error("Failed to read pointers: {0}")]
|
||||
ReadingPointers(io::Error),
|
||||
#[error("Failed to read ref count block: {0}")]
|
||||
ReadingRefCountBlock(refcount::Error),
|
||||
#[error("Failed to read ref counts: {0}")]
|
||||
ReadingRefCounts(io::Error),
|
||||
#[error("Failed to rebuild ref counts: {0}")]
|
||||
RebuildingRefCounts(io::Error),
|
||||
#[error("Refcount table offset past file end")]
|
||||
RefcountTableOffEnd,
|
||||
#[error("Too many clusters specified for refcount")]
|
||||
RefcountTableTooLarge,
|
||||
#[error("Failed to seek file: {0}")]
|
||||
SeekingFile(io::Error),
|
||||
#[error("Failed to set file size: {0}")]
|
||||
SettingFileSize(io::Error),
|
||||
#[error("Failed to set refcount refcount: {0}")]
|
||||
SettingRefcountRefcount(io::Error),
|
||||
#[error("Size too small for number of clusters")]
|
||||
SizeTooSmallForNumberOfClusters,
|
||||
#[error("L1 entry table too large: {0}")]
|
||||
TooManyL1Entries(u64),
|
||||
#[error("Ref count table too large: {0}")]
|
||||
TooManyRefcounts(u64),
|
||||
#[error("Unsupported refcount order")]
|
||||
UnsupportedRefcountOrder,
|
||||
#[error("Unsupported version: {0}")]
|
||||
UnsupportedVersion(u32),
|
||||
#[error("Failed to write data: {0}")]
|
||||
WritingData(io::Error),
|
||||
#[error("Failed to write header: {0}")]
|
||||
WritingHeader(io::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl Display for Error {
|
||||
#[remain::check]
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
#[sorted]
|
||||
match self {
|
||||
BackingFileIo(e) => write!(f, "backing file io error: {}", e),
|
||||
BackingFileOpen(e) => write!(f, "backing file open error: {}", *e),
|
||||
BackingFileTooLong(len) => {
|
||||
write!(f, "backing file name is too long: {} bytes over", len)
|
||||
}
|
||||
CompressedBlocksNotSupported => write!(f, "compressed blocks not supported"),
|
||||
EvictingCache(e) => write!(f, "failed to evict cache: {e}"),
|
||||
FileTooBig(size) => write!(f, "file larger than max of {MAX_QCOW_FILE_SIZE}: {size}"),
|
||||
GettingFileSize(e) => write!(f, "failed to get file size: {e}"),
|
||||
GettingRefcount(e) => write!(f, "failed to get refcount: {e}"),
|
||||
InvalidBackingFileName(e) => write!(f, "failed to parse filename: {}", e),
|
||||
InvalidClusterIndex => write!(f, "invalid cluster index"),
|
||||
InvalidClusterSize => write!(f, "invalid cluster size"),
|
||||
InvalidIndex => write!(f, "invalid index"),
|
||||
InvalidL1TableOffset => write!(f, "invalid L1 table offset"),
|
||||
InvalidL1TableSize(size) => write!(f, "invalid L1 table size {size}"),
|
||||
InvalidMagic => write!(f, "invalid magic"),
|
||||
InvalidOffset(_) => write!(f, "invalid offset"),
|
||||
InvalidRefcountTableOffset => write!(f, "invalid refcount table offset"),
|
||||
InvalidRefcountTableSize(size) => write!(f, "invalid refcount table size: {size}"),
|
||||
NoFreeClusters => write!(f, "no free clusters"),
|
||||
NoRefcountClusters => write!(f, "no refcount clusters"),
|
||||
NotEnoughSpaceForRefcounts => write!(f, "not enough space for refcounts"),
|
||||
OpeningFile(e) => write!(f, "failed to open file: {e}"),
|
||||
ReadingData(e) => write!(f, "failed to read data: {e}"),
|
||||
ReadingHeader(e) => write!(f, "failed to read header: {e}"),
|
||||
ReadingPointers(e) => write!(f, "failed to read pointers: {e}"),
|
||||
ReadingRefCountBlock(e) => write!(f, "failed to read ref count block: {e}"),
|
||||
ReadingRefCounts(e) => write!(f, "failed to read ref counts: {e}"),
|
||||
RebuildingRefCounts(e) => write!(f, "failed to rebuild ref counts: {e}"),
|
||||
RefcountTableOffEnd => write!(f, "refcount table offset past file end"),
|
||||
RefcountTableTooLarge => write!(f, "too many clusters specified for refcount table"),
|
||||
SeekingFile(e) => write!(f, "failed to seek file: {e}"),
|
||||
SettingFileSize(e) => write!(f, "failed to set file size: {e}"),
|
||||
SettingRefcountRefcount(e) => write!(f, "failed to set refcount refcount: {e}"),
|
||||
SizeTooSmallForNumberOfClusters => write!(f, "size too small for number of clusters"),
|
||||
TooManyL1Entries(count) => write!(f, "l1 entry table too large: {count}"),
|
||||
TooManyRefcounts(count) => write!(f, "ref count table too large: {count}"),
|
||||
UnsupportedRefcountOrder => write!(f, "unsupported refcount order"),
|
||||
UnsupportedVersion(v) => write!(f, "unsupported version: {v}"),
|
||||
WritingData(e) => write!(f, "failed to write data: {e}"),
|
||||
WritingHeader(e) => write!(f, "failed to write header: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ImageType {
|
||||
Raw,
|
||||
Qcow2,
|
||||
@@ -447,12 +439,20 @@ pub struct QcowFile {
|
||||
// List of unreferenced clusters available to be used. unref clusters become available once the
|
||||
// removal of references to them have been synced to disk.
|
||||
avail_clusters: Vec<u64>,
|
||||
backing_file: Option<Box<dyn BlockBackend>>,
|
||||
backing_file: Option<Box<Self>>,
|
||||
}
|
||||
|
||||
impl QcowFile {
|
||||
/// Creates a QcowFile from `file`. File must be a valid qcow2 image.
|
||||
pub fn from(mut file: RawFile) -> Result<QcowFile> {
|
||||
///
|
||||
/// Additionally, max nesting depth of this qcow2 image will be set to default value 10.
|
||||
pub fn from(file: RawFile) -> Result<QcowFile> {
|
||||
Self::from_with_nesting_depth(file, MAX_NESTING_DEPTH)
|
||||
}
|
||||
|
||||
/// Creates a QcowFile from `file` and with a max nesting depth. File must be a valid qcow2
|
||||
/// image.
|
||||
pub fn from_with_nesting_depth(mut file: RawFile, max_nesting_depth: u32) -> Result<QcowFile> {
|
||||
let header = QcowHeader::new(&mut file)?;
|
||||
|
||||
// Only v2 and v3 files are supported.
|
||||
@@ -479,14 +479,20 @@ impl QcowFile {
|
||||
let direct_io = file.is_direct();
|
||||
|
||||
let backing_file = if let Some(backing_file_path) = header.backing_file_path.as_ref() {
|
||||
if max_nesting_depth == 0 {
|
||||
return Err(Error::MaxNestingDepthExceeded);
|
||||
}
|
||||
let path = backing_file_path.clone();
|
||||
let backing_raw_file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(path)
|
||||
.map_err(Error::BackingFileIo)?;
|
||||
let backing_file = crate::create_disk_file(backing_raw_file, direct_io)
|
||||
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||
Some(backing_file)
|
||||
let backing_file = Self::from_with_nesting_depth(
|
||||
RawFile::new(backing_raw_file, direct_io),
|
||||
max_nesting_depth - 1,
|
||||
)
|
||||
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||
Some(Box::new(backing_file))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -634,20 +640,22 @@ impl QcowFile {
|
||||
file: RawFile,
|
||||
version: u32,
|
||||
backing_file_name: &str,
|
||||
backing_file_max_nesting_depth: u32,
|
||||
) -> Result<QcowFile> {
|
||||
let direct_io = file.is_direct();
|
||||
let backing_raw_file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(backing_file_name)
|
||||
.map_err(Error::BackingFileIo)?;
|
||||
let backing_file = crate::create_disk_file(backing_raw_file, direct_io)
|
||||
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||
let size = backing_file
|
||||
.size()
|
||||
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||
let backing_file = Self::from_with_nesting_depth(
|
||||
RawFile::new(backing_raw_file, direct_io),
|
||||
backing_file_max_nesting_depth,
|
||||
)
|
||||
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||
let size = backing_file.virtual_size();
|
||||
let header = QcowHeader::create_for_size_and_path(version, size, Some(backing_file_name))?;
|
||||
let mut result = QcowFile::new_from_header(file, header)?;
|
||||
result.backing_file = Some(backing_file);
|
||||
result.backing_file = Some(Box::new(backing_file));
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
@@ -675,7 +683,7 @@ impl QcowFile {
|
||||
Ok(qcow)
|
||||
}
|
||||
|
||||
pub fn set_backing_file(&mut self, backing: Option<Box<dyn BlockBackend>>) {
|
||||
pub fn set_backing_file(&mut self, backing: Option<Box<Self>>) {
|
||||
self.backing_file = backing;
|
||||
}
|
||||
|
||||
@@ -1213,7 +1221,7 @@ impl QcowFile {
|
||||
}
|
||||
|
||||
// Allocate and initialize a new data cluster. Returns the offset of the
|
||||
// cluster in to the file on success.
|
||||
// cluster into the file on success.
|
||||
fn append_data_cluster(&mut self, initial_data: Option<Vec<u8>>) -> std::io::Result<u64> {
|
||||
let new_addr: u64 = self.get_new_cluster(initial_data)?;
|
||||
// The cluster refcount starts at one indicating it is used but doesn't need COW.
|
||||
@@ -1631,7 +1639,7 @@ impl PunchHole for QcowFile {
|
||||
let mut remaining = length;
|
||||
let mut offset = offset;
|
||||
while remaining > 0 {
|
||||
let chunk_length = min(remaining, std::usize::MAX as u64) as usize;
|
||||
let chunk_length = min(remaining, usize::MAX as u64) as usize;
|
||||
self.deallocate_bytes(offset, chunk_length)?;
|
||||
remaining -= chunk_length as u64;
|
||||
offset += chunk_length as u64;
|
||||
@@ -1791,11 +1799,17 @@ where
|
||||
/// Copy the contents of a disk image in `src_file` into `dst_file`.
|
||||
/// The type of `src_file` is automatically detected, and the output file type is
|
||||
/// determined by `dst_type`.
|
||||
pub fn convert(mut src_file: RawFile, dst_file: RawFile, dst_type: ImageType) -> Result<()> {
|
||||
pub fn convert(
|
||||
mut src_file: RawFile,
|
||||
dst_file: RawFile,
|
||||
dst_type: ImageType,
|
||||
src_max_nesting_depth: u32,
|
||||
) -> Result<()> {
|
||||
let src_type = detect_image_type(&mut src_file)?;
|
||||
match src_type {
|
||||
ImageType::Qcow2 => {
|
||||
let mut src_reader = QcowFile::from(src_file)?;
|
||||
let mut src_reader =
|
||||
QcowFile::from_with_nesting_depth(src_file, src_max_nesting_depth)?;
|
||||
convert_reader(&mut src_reader, dst_file, dst_type)
|
||||
}
|
||||
ImageType::Raw => {
|
||||
@@ -1824,6 +1838,9 @@ pub fn detect_image_type(file: &mut RawFile) -> Result<ImageType> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use vmm_sys_util::tempdir::TempDir;
|
||||
use vmm_sys_util::tempfile::TempFile;
|
||||
use vmm_sys_util::write_zeroes::WriteZeroes;
|
||||
|
||||
@@ -2023,6 +2040,82 @@ mod tests {
|
||||
assert_eq!(read_header.backing_file_path, header.backing_file_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_backing_file() {
|
||||
// `backing_file` is `None`
|
||||
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, None)
|
||||
.expect("Failed to create header.");
|
||||
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
||||
header
|
||||
.write_to(&mut disk_file)
|
||||
.expect("Failed to write header to shm.");
|
||||
disk_file.rewind().unwrap();
|
||||
// The maximum nesting depth is 0, which means backing file is not allowed.
|
||||
let res = QcowFile::from_with_nesting_depth(disk_file, 0);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disable_backing_file() {
|
||||
// `backing_file` is `Some`
|
||||
let header =
|
||||
QcowHeader::create_for_size_and_path(3, 0x10_0000, Some("/path/to/backing/file"))
|
||||
.expect("Failed to create header.");
|
||||
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
||||
header
|
||||
.write_to(&mut disk_file)
|
||||
.expect("Failed to write header to shm.");
|
||||
disk_file.rewind().unwrap();
|
||||
// The maximum nesting depth is 0, which means backing file is not allowed.
|
||||
let res = QcowFile::from_with_nesting_depth(disk_file, 0);
|
||||
assert!(res.is_err());
|
||||
assert!(matches!(res.unwrap_err(), Error::MaxNestingDepthExceeded));
|
||||
}
|
||||
|
||||
/// Create a qcow2 file with itself as its backing file.
|
||||
///
|
||||
/// Without configuration `max_nesting_depth`, this will cause infinite recursion when loading
|
||||
/// the file until stack overflow.
|
||||
fn new_self_referential_qcow(path: &Path) -> Result<()> {
|
||||
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, path.to_str())?;
|
||||
let mut disk_file = RawFile::new(
|
||||
File::create(path).expect("Failed to create image file."),
|
||||
false,
|
||||
);
|
||||
header.write_to(&mut disk_file)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_nesting_backing() {
|
||||
let test_dir = TempDir::new_with_prefix("/tmp/ch").unwrap();
|
||||
let img_path = test_dir.as_path().join("test.img");
|
||||
|
||||
new_self_referential_qcow(img_path.as_path()).unwrap();
|
||||
|
||||
let err = QcowFile::from_with_nesting_depth(
|
||||
RawFile::new(
|
||||
File::open(img_path.as_path()).expect("Failed to open qcow image file"),
|
||||
false,
|
||||
),
|
||||
MAX_NESTING_DEPTH,
|
||||
)
|
||||
.expect_err("Opening qcow file with itself as backing file should fail.");
|
||||
|
||||
// This type of error is complex. For comparing easily, we can check if it contains the
|
||||
// type name after formatting.
|
||||
assert!(format!("{err:?}").contains(&format!("{:?}", Error::MaxNestingDepthExceeded)));
|
||||
// This should recursively call the function ten times before throwing an error, and the
|
||||
// error `BackingFileOpen` should also be repeated ten times.
|
||||
assert_eq!(
|
||||
format!("{err:?}")
|
||||
.matches("BackingFileOpen")
|
||||
.collect::<Vec<_>>()
|
||||
.len() as u32,
|
||||
MAX_NESTING_DEPTH,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_magic() {
|
||||
let invalid_header = vec![0x51u8, 0x46, 0x4a, 0xfb];
|
||||
|
||||
@@ -4,50 +4,37 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::fmt::{self, Display};
|
||||
use std::io;
|
||||
|
||||
use libc::EINVAL;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::qcow::{
|
||||
qcow_raw_file::QcowRawFile,
|
||||
vec_cache::{CacheMap, Cacheable, VecCache},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// `EvictingCache` - Error writing a refblock from the cache to disk.
|
||||
#[error("Failed to write a refblock from the cache to disk: {0}")]
|
||||
EvictingRefCounts(io::Error),
|
||||
/// `InvalidIndex` - Address requested isn't within the range of the disk.
|
||||
#[error("Address requested is not within the range of the disk")]
|
||||
InvalidIndex,
|
||||
/// `NeedCluster` - Handle this error by reading the cluster and calling the function again.
|
||||
#[error("Cluster with addr={0} needs to be read")]
|
||||
NeedCluster(u64),
|
||||
/// `NeedNewCluster` - Handle this error by allocating a cluster and calling the function again.
|
||||
#[error("New cluster needs to be allocated for refcounts")]
|
||||
NeedNewCluster,
|
||||
/// `ReadingRefCounts` - Error reading the file in to the refcount cache.
|
||||
/// `ReadingRefCounts` - Error reading the file into the refcount cache.
|
||||
#[error("Failed to read the file into the refcount cache: {0}")]
|
||||
ReadingRefCounts(io::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
match self {
|
||||
EvictingRefCounts(e) => {
|
||||
write!(f, "failed to write a refblock from the cache to disk: {e}")
|
||||
}
|
||||
InvalidIndex => write!(f, "address requested is not within the range of the disk"),
|
||||
NeedCluster(addr) => write!(f, "cluster with addr={addr} needs to be read"),
|
||||
NeedNewCluster => write!(f, "new cluster needs to be allocated for refcounts"),
|
||||
ReadingRefCounts(e) => {
|
||||
write!(f, "failed to read the file into the refcount cache: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the refcount entries for an open qcow file.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RefCount {
|
||||
|
||||
@@ -127,7 +127,7 @@ pub struct Header {
|
||||
impl Header {
|
||||
/// Reads the Header structure from a reference VHDx file
|
||||
pub fn new(f: &mut File, start: u64) -> Result<Header> {
|
||||
// Read the whole header in to a buffer. We will need it for
|
||||
// Read the whole header into a buffer. We will need it for
|
||||
// calculating checksum.
|
||||
let mut buffer = [0; HEADER_SIZE as usize];
|
||||
f.seek(SeekFrom::Start(start))
|
||||
@@ -470,7 +470,7 @@ impl VhdxHeader {
|
||||
pub fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> Result<u32> {
|
||||
// Read the checksum into a mutable slice
|
||||
let csum_buf = &mut buffer[csum_offset..csum_offset + 4];
|
||||
// Convert the checksum chunk in to a u32 integer
|
||||
// Convert the checksum chunk into a u32 integer
|
||||
let orig_csum = LittleEndian::read_u32(csum_buf);
|
||||
// Zero the checksum in the buffer
|
||||
LittleEndian::write_u32(csum_buf, 0);
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
[package]
|
||||
name = "devices"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
name = "devices"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||
anyhow = "1.0.81"
|
||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||
anyhow = "1.0.86"
|
||||
arch = { path = "../arch" }
|
||||
bitflags = "2.5.0"
|
||||
bitflags = "2.6.0"
|
||||
byteorder = "1.5.0"
|
||||
event_monitor = { path = "../event_monitor" }
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
num_enum = "0.7.2"
|
||||
pci = { path = "../pci" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
thiserror = "1.0.58"
|
||||
thiserror = "1.0.62"
|
||||
tpm = { path = "../tpm" }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = "0.14.1"
|
||||
vm-memory = { version = "0.14.1", features = [
|
||||
"backend-atomic",
|
||||
"backend-bitmap",
|
||||
"backend-mmap",
|
||||
] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = "0.12.1"
|
||||
|
||||
@@ -29,3 +34,4 @@ arch = { path = "../arch" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
pvmemcontrol = []
|
||||
|
||||
@@ -4,31 +4,42 @@
|
||||
|
||||
use std::io;
|
||||
use std::result;
|
||||
use thiserror::Error;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Invalid trigger mode.
|
||||
#[error("Invalid trigger mode")]
|
||||
InvalidTriggerMode,
|
||||
/// Invalid delivery mode.
|
||||
#[error("Invalid delivery mode")]
|
||||
InvalidDeliveryMode,
|
||||
/// Failed creating the interrupt source group.
|
||||
#[error("Failed creating the interrupt source group: {0}")]
|
||||
CreateInterruptSourceGroup(io::Error),
|
||||
/// Failed triggering the interrupt.
|
||||
#[error("Failed triggering the interrupt: {0}")]
|
||||
TriggerInterrupt(io::Error),
|
||||
/// Failed masking the interrupt.
|
||||
#[error("Failed masking the interrupt: {0}")]
|
||||
MaskInterrupt(io::Error),
|
||||
/// Failed unmasking the interrupt.
|
||||
#[error("Failed unmasking the interrupt: {0}")]
|
||||
UnmaskInterrupt(io::Error),
|
||||
/// Failed updating the interrupt.
|
||||
#[error("Failed updating the interrupt: {0}")]
|
||||
UpdateInterrupt(io::Error),
|
||||
/// Failed enabling the interrupt.
|
||||
#[error("Failed enabling the interrupt: {0}")]
|
||||
EnableInterrupt(io::Error),
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
/// Failed creating GIC device.
|
||||
#[error("Failed creating GIC device: {0}")]
|
||||
CreateGic(hypervisor::HypervisorVmError),
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
/// Failed restoring GIC device.
|
||||
#[error("Failed restoring GIC device: {0}")]
|
||||
RestoreGic(hypervisor::arch::aarch64::gic::Error),
|
||||
}
|
||||
|
||||
|
||||
@@ -416,7 +416,7 @@ impl InterruptController for Ioapic {
|
||||
self.interrupt_source_group
|
||||
.trigger(irq as InterruptIndex)
|
||||
.map_err(Error::TriggerInterrupt)?;
|
||||
debug!("Interrupt successfully delivered");
|
||||
debug!("Interrupt {irq} successfully delivered");
|
||||
|
||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
||||
// It will be cleared when the EOI is received.
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::result;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::{fmt, io};
|
||||
use thiserror::Error;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
@@ -37,29 +38,18 @@ const GPIO_ID_HIGH: u64 = 0x1000;
|
||||
|
||||
const N_GPIOS: u32 = 8;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Bad Write Offset: {0}")]
|
||||
BadWriteOffset(u64),
|
||||
#[error("GPIO interrupt disabled by guest driver.")]
|
||||
GpioInterruptDisabled,
|
||||
#[error("Could not trigger GPIO interrupt: {0}.")]
|
||||
GpioInterruptFailure(io::Error),
|
||||
#[error("Invalid GPIO Input key triggered: {0}.")]
|
||||
GpioTriggerKeyFailure(u32),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {offset}"),
|
||||
Error::GpioInterruptDisabled => write!(f, "GPIO interrupt disabled by guest driver.",),
|
||||
Error::GpioInterruptFailure(ref e) => {
|
||||
write!(f, "Could not trigger GPIO interrupt: {e}.")
|
||||
}
|
||||
Error::GpioTriggerKeyFailure(key) => {
|
||||
write!(f, "Invalid GPIO Input key triggered: {key}.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// A GPIO device following the PL061 specification.
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
//! a real-time clock input.
|
||||
//!
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
use std::fmt;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::time::Instant;
|
||||
use std::{io, result};
|
||||
use thiserror::Error;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
|
||||
@@ -39,21 +39,14 @@ const AMBA_ID_HIGH: u64 = 0x1000;
|
||||
/// Constant to convert seconds to nanoseconds.
|
||||
pub const NANOS_PER_SECOND: u64 = 1_000_000_000;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Bad Write Offset: {0}")]
|
||||
BadWriteOffset(u64),
|
||||
#[error("Failed to trigger interrupt: {0}")]
|
||||
InterruptFailure(io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {offset}"),
|
||||
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Wrapper over `libc::clockid_t` to specify Linux Kernel clock source.
|
||||
@@ -79,98 +72,6 @@ impl From<ClockType> for libc::clockid_t {
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure representing the date in local time with nanosecond precision.
|
||||
pub struct LocalTime {
|
||||
/// Seconds in current minute.
|
||||
sec: i32,
|
||||
/// Minutes in current hour.
|
||||
min: i32,
|
||||
/// Hours in current day, 24H format.
|
||||
hour: i32,
|
||||
/// Days in current month.
|
||||
mday: i32,
|
||||
/// Months in current year.
|
||||
mon: i32,
|
||||
/// Years passed since 1900 BC.
|
||||
year: i32,
|
||||
/// Nanoseconds in current second.
|
||||
nsec: i64,
|
||||
}
|
||||
|
||||
impl LocalTime {
|
||||
/// Returns the [LocalTime](struct.LocalTime.html) structure for the calling moment.
|
||||
#[cfg(test)]
|
||||
pub fn now() -> LocalTime {
|
||||
let mut timespec = libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
};
|
||||
let mut tm: libc::tm = libc::tm {
|
||||
tm_sec: 0,
|
||||
tm_min: 0,
|
||||
tm_hour: 0,
|
||||
tm_mday: 0,
|
||||
tm_mon: 0,
|
||||
tm_year: 0,
|
||||
tm_wday: 0,
|
||||
tm_yday: 0,
|
||||
tm_isdst: 0,
|
||||
tm_gmtoff: 0,
|
||||
tm_zone: std::ptr::null(),
|
||||
};
|
||||
|
||||
// SAFETY: the parameters are valid.
|
||||
unsafe {
|
||||
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
|
||||
libc::localtime_r(×pec.tv_sec, &mut tm);
|
||||
}
|
||||
|
||||
LocalTime {
|
||||
sec: tm.tm_sec,
|
||||
min: tm.tm_min,
|
||||
hour: tm.tm_hour,
|
||||
mday: tm.tm_mday,
|
||||
mon: tm.tm_mon,
|
||||
year: tm.tm_year,
|
||||
nsec: timespec.tv_nsec,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LocalTime {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
|
||||
self.year + 1900,
|
||||
self.mon + 1,
|
||||
self.mday,
|
||||
self.hour,
|
||||
self.min,
|
||||
self.sec,
|
||||
self.nsec
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds a micro-second resolution timestamp with both the real time and cpu time.
|
||||
#[derive(Clone)]
|
||||
pub struct TimestampUs {
|
||||
/// Real time in microseconds.
|
||||
pub time_us: u64,
|
||||
/// Cpu time in microseconds.
|
||||
pub cputime_us: u64,
|
||||
}
|
||||
|
||||
impl Default for TimestampUs {
|
||||
fn default() -> TimestampUs {
|
||||
TimestampUs {
|
||||
time_us: get_time(ClockType::Monotonic) / 1000,
|
||||
cputime_us: get_time(ClockType::ProcessCpu) / 1000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a timestamp in nanoseconds based on the provided clock type.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -337,6 +238,70 @@ mod tests {
|
||||
|
||||
const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
|
||||
|
||||
struct LocalTime {
|
||||
sec: i32,
|
||||
min: i32,
|
||||
hour: i32,
|
||||
mday: i32,
|
||||
mon: i32,
|
||||
year: i32,
|
||||
nsec: i64,
|
||||
}
|
||||
|
||||
impl LocalTime {
|
||||
fn now() -> LocalTime {
|
||||
let mut timespec = libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
};
|
||||
let mut tm: libc::tm = libc::tm {
|
||||
tm_sec: 0,
|
||||
tm_min: 0,
|
||||
tm_hour: 0,
|
||||
tm_mday: 0,
|
||||
tm_mon: 0,
|
||||
tm_year: 0,
|
||||
tm_wday: 0,
|
||||
tm_yday: 0,
|
||||
tm_isdst: 0,
|
||||
tm_gmtoff: 0,
|
||||
tm_zone: std::ptr::null(),
|
||||
};
|
||||
|
||||
// SAFETY: the parameters are valid.
|
||||
unsafe {
|
||||
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
|
||||
libc::localtime_r(×pec.tv_sec, &mut tm);
|
||||
}
|
||||
|
||||
LocalTime {
|
||||
sec: tm.tm_sec,
|
||||
min: tm.tm_min,
|
||||
hour: tm.tm_hour,
|
||||
mday: tm.tm_mday,
|
||||
mon: tm.tm_mon,
|
||||
year: tm.tm_year,
|
||||
nsec: timespec.tv_nsec,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LocalTime {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
|
||||
self.year + 1900,
|
||||
self.mon + 1,
|
||||
self.mday,
|
||||
self.hour,
|
||||
self.min,
|
||||
self.sec,
|
||||
self.nsec
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_time() {
|
||||
for _ in 0..1000 {
|
||||
|
||||
@@ -411,14 +411,11 @@ mod tests {
|
||||
None,
|
||||
);
|
||||
|
||||
serial.write(0, DATA as u64, &[b'x', b'y']);
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
serial.write(0, DATA as u64, &[b'b']);
|
||||
serial.write(0, DATA as u64, &[b'c']);
|
||||
assert_eq!(
|
||||
serial_out.buf.lock().unwrap().as_slice(),
|
||||
&[b'a', b'b', b'c']
|
||||
);
|
||||
serial.write(0, DATA as u64, b"xy");
|
||||
serial.write(0, DATA as u64, b"a");
|
||||
serial.write(0, DATA as u64, b"b");
|
||||
serial.write(0, DATA as u64, b"c");
|
||||
assert_eq!(serial_out.buf.lock().unwrap().as_slice(), b"abc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -436,7 +433,7 @@ mod tests {
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
assert!(intr_evt.write(1).is_ok());
|
||||
serial.write(0, IER as u64, &[IER_RECV_BIT]);
|
||||
serial.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
|
||||
serial.queue_input_bytes(b"abc").unwrap();
|
||||
|
||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||
|
||||
@@ -473,7 +470,7 @@ mod tests {
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
assert!(intr_evt.write(1).is_ok());
|
||||
serial.write(0, IER as u64, &[IER_THR_BIT]);
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
serial.write(0, DATA as u64, b"a");
|
||||
|
||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||
let mut data = [0u8];
|
||||
@@ -515,9 +512,9 @@ mod tests {
|
||||
);
|
||||
|
||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT]);
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
serial.write(0, DATA as u64, &[b'b']);
|
||||
serial.write(0, DATA as u64, &[b'c']);
|
||||
serial.write(0, DATA as u64, b"a");
|
||||
serial.write(0, DATA as u64, b"b");
|
||||
serial.write(0, DATA as u64, b"c");
|
||||
|
||||
let mut data = [0u8];
|
||||
serial.read(0, MSR as u64, &mut data[..]);
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::time::Instant;
|
||||
use std::{io, result};
|
||||
use thiserror::Error;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
@@ -45,27 +45,20 @@ const PL011_ID: [u8; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
||||
const AMBA_ID_LOW: u64 = 0x3f8;
|
||||
const AMBA_ID_HIGH: u64 = 0x401;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("pl011_write: Bad Write Offset: {0}")]
|
||||
BadWriteOffset(u64),
|
||||
#[error("pl011: DMA not implemented.")]
|
||||
DmaNotImplemented,
|
||||
#[error("Failed to trigger interrupt: {0}")]
|
||||
InterruptFailure(io::Error),
|
||||
#[error("Failed to write: {0}")]
|
||||
WriteAllFailure(io::Error),
|
||||
#[error("Failed to flush: {0}")]
|
||||
FlushFailure(io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::BadWriteOffset(offset) => write!(f, "pl011_write: Bad Write Offset: {offset}"),
|
||||
Error::DmaNotImplemented => write!(f, "pl011: DMA not implemented."),
|
||||
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {e}"),
|
||||
Error::WriteAllFailure(e) => write!(f, "Failed to write: {e}"),
|
||||
Error::FlushFailure(e) => write!(f, "Failed to flush: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// A PL011 device following the PL011 specification.
|
||||
@@ -531,14 +524,11 @@ mod tests {
|
||||
None,
|
||||
);
|
||||
|
||||
pl011.write(0, UARTDR, &[b'x', b'y']);
|
||||
pl011.write(0, UARTDR, &[b'a']);
|
||||
pl011.write(0, UARTDR, &[b'b']);
|
||||
pl011.write(0, UARTDR, &[b'c']);
|
||||
assert_eq!(
|
||||
pl011_out.buf.lock().unwrap().as_slice(),
|
||||
&[b'x', b'a', b'b', b'c']
|
||||
);
|
||||
pl011.write(0, UARTDR, b"xy");
|
||||
pl011.write(0, UARTDR, b"a");
|
||||
pl011.write(0, UARTDR, b"b");
|
||||
pl011.write(0, UARTDR, b"c");
|
||||
assert_eq!(pl011_out.buf.lock().unwrap().as_slice(), b"xabc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -556,7 +546,7 @@ mod tests {
|
||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
assert!(intr_evt.write(1).is_ok());
|
||||
pl011.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
|
||||
pl011.queue_input_bytes(b"abc").unwrap();
|
||||
|
||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ pub mod interrupt_controller;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod ioapic;
|
||||
pub mod legacy;
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pub mod pvmemcontrol;
|
||||
pub mod pvpanic;
|
||||
pub mod tpm;
|
||||
|
||||
|
||||
819
devices/src/pvmemcontrol.rs
Normal file
819
devices/src/pvmemcontrol.rs
Normal file
@@ -0,0 +1,819 @@
|
||||
// Copyright © 2024 Google LLC
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use num_enum::TryFromPrimitive;
|
||||
use pci::{
|
||||
BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType,
|
||||
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::CString,
|
||||
io, result,
|
||||
sync::{Arc, Barrier, Mutex, RwLock},
|
||||
};
|
||||
use thiserror::Error;
|
||||
use vm_allocator::{page_size::get_page_size, AddressAllocator, SystemAllocator};
|
||||
use vm_device::{BusDeviceSync, Resource};
|
||||
use vm_memory::{
|
||||
bitmap::AtomicBitmap, Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory,
|
||||
GuestMemoryAtomic, GuestMemoryError, GuestMemoryMmap, Le32, Le64,
|
||||
};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
|
||||
const PVMEMCONTROL_VENDOR_ID: u16 = 0x1ae0;
|
||||
const PVMEMCONTROL_DEVICE_ID: u16 = 0x0087;
|
||||
|
||||
const PVMEMCONTROL_SUBSYSTEM_VENDOR_ID: u16 = 0x1ae0;
|
||||
const PVMEMCONTROL_SUBSYSTEM_ID: u16 = 0x011F;
|
||||
|
||||
const MAJOR_VERSION: u64 = 1;
|
||||
const MINOR_VERSION: u64 = 0;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
// device errors
|
||||
#[error("Guest gave us bad memory addresses: {0}")]
|
||||
GuestMemory(#[source] GuestMemoryError),
|
||||
#[error("Guest sent us invalid request")]
|
||||
InvalidRequest,
|
||||
|
||||
#[error("Guest sent us invalid command: {0}")]
|
||||
InvalidCommand(u32),
|
||||
#[error("Guest sent us invalid connection: {0}")]
|
||||
InvalidConnection(u32),
|
||||
|
||||
// pvmemcontrol errors
|
||||
#[error("Request contains invalid arguments: {0}")]
|
||||
InvalidArgument(u64),
|
||||
#[error("Unknown function code: {0}")]
|
||||
UnknownFunctionCode(u64),
|
||||
#[error("Libc call fail: {0}")]
|
||||
LibcFail(#[source] std::io::Error),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum PvmemcontrolSubclass {
|
||||
Other = 0x80,
|
||||
}
|
||||
|
||||
impl PciSubclass for PvmemcontrolSubclass {
|
||||
fn get_register_value(&self) -> u8 {
|
||||
*self as u8
|
||||
}
|
||||
}
|
||||
|
||||
/// commands have 0 as the most significant byte
|
||||
#[repr(u32)]
|
||||
#[derive(PartialEq, Eq, Copy, Clone, TryFromPrimitive)]
|
||||
enum PvmemcontrolTransportCommand {
|
||||
Reset = 0x060f_e6d2,
|
||||
Register = 0x0e35_9539,
|
||||
Ready = 0x0ca8_d227,
|
||||
Disconnect = 0x030f_5da0,
|
||||
Ack = 0x03cf_5196,
|
||||
Error = 0x01fb_a249,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct PvmemcontrolTransportRegister {
|
||||
buf_phys_addr: Le64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct PvmemcontrolTransportRegisterResponse {
|
||||
command: Le32,
|
||||
_padding: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
union PvmemcontrolTransportUnion {
|
||||
register: PvmemcontrolTransportRegister,
|
||||
register_response: PvmemcontrolTransportRegisterResponse,
|
||||
unit: (),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct PvmemcontrolTransport {
|
||||
payload: PvmemcontrolTransportUnion,
|
||||
command: PvmemcontrolTransportCommand,
|
||||
}
|
||||
|
||||
const PVMEMCONTROL_DEVICE_MMIO_SIZE: u64 = std::mem::size_of::<PvmemcontrolTransport>() as u64;
|
||||
const PVMEMCONTROL_DEVICE_MMIO_ALIGN: u64 = std::mem::align_of::<PvmemcontrolTransport>() as u64;
|
||||
|
||||
impl PvmemcontrolTransport {
|
||||
fn ack() -> Self {
|
||||
PvmemcontrolTransport {
|
||||
payload: PvmemcontrolTransportUnion { unit: () },
|
||||
command: PvmemcontrolTransportCommand::Ack,
|
||||
}
|
||||
}
|
||||
|
||||
fn error() -> Self {
|
||||
PvmemcontrolTransport {
|
||||
payload: PvmemcontrolTransportUnion { unit: () },
|
||||
command: PvmemcontrolTransportCommand::Error,
|
||||
}
|
||||
}
|
||||
|
||||
fn register_response(command: u32) -> Self {
|
||||
PvmemcontrolTransport {
|
||||
payload: PvmemcontrolTransportUnion {
|
||||
register_response: PvmemcontrolTransportRegisterResponse {
|
||||
command: command.into(),
|
||||
_padding: 0,
|
||||
},
|
||||
},
|
||||
command: PvmemcontrolTransportCommand::Ack,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn as_register(self) -> PvmemcontrolTransportRegister {
|
||||
self.payload.register
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Contains no references and does not have compiler-inserted padding
|
||||
unsafe impl ByteValued for PvmemcontrolTransportUnion {}
|
||||
// SAFETY: Contains no references and does not have compiler-inserted padding
|
||||
unsafe impl ByteValued for PvmemcontrolTransport {}
|
||||
|
||||
#[repr(u64)]
|
||||
#[derive(Copy, Clone, TryFromPrimitive, Debug)]
|
||||
enum FunctionCode {
|
||||
Info = 0,
|
||||
Dontneed = 1,
|
||||
Remove = 2,
|
||||
Free = 3,
|
||||
Pageout = 4,
|
||||
Dontdump = 5,
|
||||
SetVMAAnonName = 6,
|
||||
Mlock = 7,
|
||||
Munlock = 8,
|
||||
MprotectNone = 9,
|
||||
MprotectR = 10,
|
||||
MprotectW = 11,
|
||||
MprotectRW = 12,
|
||||
Mergeable = 13,
|
||||
Unmergeable = 14,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
struct PvmemcontrolReq {
|
||||
func_code: Le64,
|
||||
addr: Le64,
|
||||
length: Le64,
|
||||
arg: Le64,
|
||||
}
|
||||
|
||||
// SAFETY: it only has data and has no implicit padding.
|
||||
unsafe impl ByteValued for PvmemcontrolReq {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct PvmemcontrolResp {
|
||||
ret_errno: Le32,
|
||||
ret_code: Le32,
|
||||
ret_value: Le64,
|
||||
arg0: Le64,
|
||||
arg1: Le64,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PvmemcontrolResp {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let PvmemcontrolResp {
|
||||
ret_errno,
|
||||
ret_code,
|
||||
..
|
||||
} = self;
|
||||
write!(
|
||||
f,
|
||||
"PvmemcontrolResp {{ ret_errno: {}, ret_code: {}, .. }}",
|
||||
ret_errno.to_native(),
|
||||
ret_code.to_native()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: it only has data and has no implicit padding.
|
||||
unsafe impl ByteValued for PvmemcontrolResp {}
|
||||
|
||||
/// The guest connections start at 0x8000_0000, which has a leading 1 in
|
||||
/// the most significant byte, this ensures it does not conflict with
|
||||
/// any of the transport commands
|
||||
#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct GuestConnection {
|
||||
command: u32,
|
||||
}
|
||||
|
||||
impl Default for GuestConnection {
|
||||
fn default() -> Self {
|
||||
GuestConnection::new(0x8000_0000)
|
||||
}
|
||||
}
|
||||
|
||||
impl GuestConnection {
|
||||
fn new(command: u32) -> Self {
|
||||
Self { command }
|
||||
}
|
||||
|
||||
fn next(&self) -> Self {
|
||||
let GuestConnection { command } = *self;
|
||||
|
||||
if command == u32::MAX {
|
||||
GuestConnection::default()
|
||||
} else {
|
||||
GuestConnection::new(command + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for GuestConnection {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
if (value & 0x8000_0000) != 0 {
|
||||
Ok(GuestConnection::new(value))
|
||||
} else {
|
||||
Err(Error::InvalidConnection(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PercpuInitState {
|
||||
port_buf_map: HashMap<GuestConnection, GuestAddress>,
|
||||
next_conn: GuestConnection,
|
||||
}
|
||||
|
||||
impl PercpuInitState {
|
||||
fn new() -> Self {
|
||||
PercpuInitState {
|
||||
port_buf_map: HashMap::new(),
|
||||
next_conn: GuestConnection::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PvmemcontrolState {
|
||||
PercpuInit(PercpuInitState),
|
||||
Ready(HashMap<GuestConnection, GuestAddress>),
|
||||
Broken,
|
||||
}
|
||||
|
||||
pub struct PvmemcontrolDevice {
|
||||
transport: PvmemcontrolTransport,
|
||||
state: PvmemcontrolState,
|
||||
}
|
||||
|
||||
impl PvmemcontrolDevice {
|
||||
fn new(transport: PvmemcontrolTransport, state: PvmemcontrolState) -> Self {
|
||||
PvmemcontrolDevice { transport, state }
|
||||
}
|
||||
}
|
||||
|
||||
impl PvmemcontrolDevice {
|
||||
fn register_percpu_buf(
|
||||
guest_memory: &GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||
mut state: PercpuInitState,
|
||||
PvmemcontrolTransportRegister { buf_phys_addr }: PvmemcontrolTransportRegister,
|
||||
) -> Self {
|
||||
// access to this address is checked
|
||||
let buf_phys_addr = GuestAddress(buf_phys_addr.into());
|
||||
if !guest_memory.memory().check_range(
|
||||
buf_phys_addr,
|
||||
std::mem::size_of::<PvmemcontrolResp>().max(std::mem::size_of::<PvmemcontrolReq>()),
|
||||
) {
|
||||
warn!("guest sent invalid phys addr {:#x}", buf_phys_addr.0);
|
||||
return PvmemcontrolDevice::new(
|
||||
PvmemcontrolTransport::error(),
|
||||
PvmemcontrolState::Broken,
|
||||
);
|
||||
}
|
||||
|
||||
let conn = {
|
||||
// find an available port+byte combination, and fail if full
|
||||
let mut next_conn = state.next_conn;
|
||||
while state.port_buf_map.contains_key(&next_conn) {
|
||||
next_conn = next_conn.next();
|
||||
if next_conn == state.next_conn {
|
||||
warn!("connections exhausted");
|
||||
return PvmemcontrolDevice::new(
|
||||
PvmemcontrolTransport::error(),
|
||||
PvmemcontrolState::Broken,
|
||||
);
|
||||
}
|
||||
}
|
||||
next_conn
|
||||
};
|
||||
state.next_conn = conn.next();
|
||||
state.port_buf_map.insert(conn, buf_phys_addr);
|
||||
|
||||
// inform guest of the connection
|
||||
let response = PvmemcontrolTransport::register_response(conn.command);
|
||||
|
||||
PvmemcontrolDevice::new(response, PvmemcontrolState::PercpuInit(state))
|
||||
}
|
||||
|
||||
fn reset() -> Self {
|
||||
PvmemcontrolDevice::new(
|
||||
PvmemcontrolTransport::ack(),
|
||||
PvmemcontrolState::PercpuInit(PercpuInitState::new()),
|
||||
)
|
||||
}
|
||||
|
||||
fn error() -> Self {
|
||||
PvmemcontrolDevice::new(PvmemcontrolTransport::error(), PvmemcontrolState::Broken)
|
||||
}
|
||||
|
||||
fn ready(PercpuInitState { port_buf_map, .. }: PercpuInitState) -> Self {
|
||||
PvmemcontrolDevice::new(
|
||||
PvmemcontrolTransport::ack(),
|
||||
PvmemcontrolState::Ready(port_buf_map),
|
||||
)
|
||||
}
|
||||
|
||||
fn run_command(
|
||||
&mut self,
|
||||
guest_memory: &GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||
command: PvmemcontrolTransportCommand,
|
||||
) {
|
||||
let state = std::mem::replace(&mut self.state, PvmemcontrolState::Broken);
|
||||
|
||||
*self = match command {
|
||||
PvmemcontrolTransportCommand::Reset => Self::reset(),
|
||||
PvmemcontrolTransportCommand::Register => {
|
||||
if let PvmemcontrolState::PercpuInit(state) = state {
|
||||
// SAFETY: By device protocol. If driver is wrong the device
|
||||
// can enter a Broken state, but the behavior is still sound.
|
||||
Self::register_percpu_buf(guest_memory, state, unsafe {
|
||||
self.transport.as_register()
|
||||
})
|
||||
} else {
|
||||
debug!("received register without reset");
|
||||
Self::error()
|
||||
}
|
||||
}
|
||||
PvmemcontrolTransportCommand::Ready => {
|
||||
if let PvmemcontrolState::PercpuInit(state) = state {
|
||||
Self::ready(state)
|
||||
} else {
|
||||
debug!("received ready without reset");
|
||||
Self::error()
|
||||
}
|
||||
}
|
||||
PvmemcontrolTransportCommand::Disconnect => Self::error(),
|
||||
PvmemcontrolTransportCommand::Ack => {
|
||||
debug!("received ack as command");
|
||||
Self::error()
|
||||
}
|
||||
PvmemcontrolTransportCommand::Error => {
|
||||
debug!("received error as command");
|
||||
Self::error()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// read from the transport
|
||||
fn read_transport(&self, offset: u64, data: &mut [u8]) {
|
||||
self.transport
|
||||
.as_slice()
|
||||
.iter()
|
||||
.skip(offset as usize)
|
||||
.zip(data.iter_mut())
|
||||
.for_each(|(src, dest)| *dest = *src)
|
||||
}
|
||||
|
||||
/// can only write to transport payload
|
||||
/// command is a special register that needs separate dispatching
|
||||
fn write_transport(&mut self, offset: u64, data: &[u8]) {
|
||||
self.transport
|
||||
.payload
|
||||
.as_mut_slice()
|
||||
.iter_mut()
|
||||
.skip(offset as usize)
|
||||
.zip(data.iter())
|
||||
.for_each(|(dest, src)| *dest = *src)
|
||||
}
|
||||
|
||||
fn find_connection(&self, conn: GuestConnection) -> Option<GuestAddress> {
|
||||
match &self.state {
|
||||
PvmemcontrolState::Ready(map) => map.get(&conn).copied(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PvmemcontrolBusDevice {
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||
dev: RwLock<PvmemcontrolDevice>,
|
||||
}
|
||||
|
||||
pub struct PvmemcontrolPciDevice {
|
||||
id: String,
|
||||
configuration: PciConfiguration,
|
||||
bar_regions: Vec<PciBarConfiguration>,
|
||||
}
|
||||
|
||||
impl PvmemcontrolBusDevice {
|
||||
/// f is called with the host address of `range_base` and only when
|
||||
/// [`range_base`, `range_base` + `range_len`) is present in the guest
|
||||
fn operate_on_memory_range<F>(&self, addr: u64, length: u64, f: F) -> result::Result<(), Error>
|
||||
where
|
||||
F: FnOnce(*mut libc::c_void, libc::size_t) -> libc::c_int,
|
||||
{
|
||||
let memory = self.mem.memory();
|
||||
let range_base = GuestAddress(addr);
|
||||
let range_len = usize::try_from(length).map_err(|_| Error::InvalidRequest)?;
|
||||
|
||||
// assume guest memory is not interleaved with vmm memory on the host.
|
||||
if !memory.check_range(range_base, range_len) {
|
||||
return Err(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(
|
||||
range_base,
|
||||
)));
|
||||
}
|
||||
let hva = memory
|
||||
.get_host_address(range_base)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
let res = f(hva as *mut libc::c_void, range_len as libc::size_t);
|
||||
if res != 0 {
|
||||
return Err(Error::LibcFail(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn madvise(&self, addr: u64, length: u64, advice: libc::c_int) -> result::Result<(), Error> {
|
||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||
libc::madvise(base, len, advice)
|
||||
})
|
||||
}
|
||||
|
||||
fn mlock(&self, addr: u64, length: u64, on_default: bool) -> result::Result<(), Error> {
|
||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||
libc::mlock2(base, len, if on_default { libc::MLOCK_ONFAULT } else { 0 })
|
||||
})
|
||||
}
|
||||
|
||||
fn munlock(&self, addr: u64, length: u64) -> result::Result<(), Error> {
|
||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||
libc::munlock(base, len)
|
||||
})
|
||||
}
|
||||
|
||||
fn mprotect(
|
||||
&self,
|
||||
addr: u64,
|
||||
length: u64,
|
||||
protection: libc::c_int,
|
||||
) -> result::Result<(), Error> {
|
||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||
libc::mprotect(base, len, protection)
|
||||
})
|
||||
}
|
||||
|
||||
fn set_vma_anon_name(&self, addr: u64, length: u64, name: u64) -> result::Result<(), Error> {
|
||||
let name = (name != 0).then(|| CString::new(format!("pvmemcontrol-{}", name)).unwrap());
|
||||
let name_ptr = if let Some(name) = &name {
|
||||
name.as_ptr()
|
||||
} else {
|
||||
std::ptr::null()
|
||||
};
|
||||
debug!("addr {:X} length {} name {:?}", addr, length, name);
|
||||
|
||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||
libc::prctl(
|
||||
libc::PR_SET_VMA,
|
||||
libc::PR_SET_VMA_ANON_NAME,
|
||||
base,
|
||||
len,
|
||||
name_ptr,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn process_request(
|
||||
&self,
|
||||
func_code: FunctionCode,
|
||||
addr: u64,
|
||||
length: u64,
|
||||
arg: u64,
|
||||
) -> Result<PvmemcontrolResp, Error> {
|
||||
let result = match func_code {
|
||||
FunctionCode::Info => {
|
||||
return Ok(PvmemcontrolResp {
|
||||
ret_errno: 0.into(),
|
||||
ret_code: 0.into(),
|
||||
ret_value: get_page_size().into(),
|
||||
arg0: MAJOR_VERSION.into(),
|
||||
arg1: MINOR_VERSION.into(),
|
||||
})
|
||||
}
|
||||
FunctionCode::Dontneed => self.madvise(addr, length, libc::MADV_DONTNEED),
|
||||
FunctionCode::Remove => self.madvise(addr, length, libc::MADV_REMOVE),
|
||||
FunctionCode::Free => self.madvise(addr, length, libc::MADV_FREE),
|
||||
FunctionCode::Pageout => self.madvise(addr, length, libc::MADV_PAGEOUT),
|
||||
FunctionCode::Dontdump => self.madvise(addr, length, libc::MADV_DONTDUMP),
|
||||
FunctionCode::SetVMAAnonName => self.set_vma_anon_name(addr, length, arg),
|
||||
FunctionCode::Mlock => self.mlock(addr, length, false),
|
||||
FunctionCode::Munlock => self.munlock(addr, length),
|
||||
FunctionCode::MprotectNone => self.mprotect(addr, length, libc::PROT_NONE),
|
||||
FunctionCode::MprotectR => self.mprotect(addr, length, libc::PROT_READ),
|
||||
FunctionCode::MprotectW => self.mprotect(addr, length, libc::PROT_WRITE),
|
||||
FunctionCode::MprotectRW => {
|
||||
self.mprotect(addr, length, libc::PROT_READ | libc::PROT_WRITE)
|
||||
}
|
||||
FunctionCode::Mergeable => self.madvise(addr, length, libc::MADV_MERGEABLE),
|
||||
FunctionCode::Unmergeable => self.madvise(addr, length, libc::MADV_UNMERGEABLE),
|
||||
};
|
||||
result.map(|_| PvmemcontrolResp::default())
|
||||
}
|
||||
|
||||
fn handle_request(
|
||||
&self,
|
||||
PvmemcontrolReq {
|
||||
func_code,
|
||||
addr,
|
||||
length,
|
||||
arg,
|
||||
}: PvmemcontrolReq,
|
||||
) -> Result<PvmemcontrolResp, Error> {
|
||||
let (func_code, addr, length, arg) = (
|
||||
func_code.to_native(),
|
||||
addr.to_native(),
|
||||
length.to_native(),
|
||||
arg.to_native(),
|
||||
);
|
||||
|
||||
let resp_or_err = FunctionCode::try_from(func_code)
|
||||
.map_err(|_| Error::UnknownFunctionCode(func_code))
|
||||
.and_then(|func_code| self.process_request(func_code, addr, length, arg));
|
||||
|
||||
let resp = match resp_or_err {
|
||||
Ok(resp) => resp,
|
||||
Err(e) => match e {
|
||||
Error::InvalidArgument(arg) => PvmemcontrolResp {
|
||||
ret_errno: (libc::EINVAL as u32).into(),
|
||||
ret_code: (arg as u32).into(),
|
||||
..Default::default()
|
||||
},
|
||||
Error::LibcFail(err) => PvmemcontrolResp {
|
||||
ret_errno: (err.raw_os_error().unwrap_or(libc::EFAULT) as u32).into(),
|
||||
ret_code: 0u32.into(),
|
||||
..Default::default()
|
||||
},
|
||||
Error::UnknownFunctionCode(func_code) => PvmemcontrolResp {
|
||||
ret_errno: (libc::EOPNOTSUPP as u32).into(),
|
||||
ret_code: (func_code as u32).into(),
|
||||
..Default::default()
|
||||
},
|
||||
Error::GuestMemory(err) => {
|
||||
warn!("{}", err);
|
||||
PvmemcontrolResp {
|
||||
ret_errno: (libc::EINVAL as u32).into(),
|
||||
ret_code: (func_code as u32).into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
// device error, stop responding
|
||||
other => return Err(other),
|
||||
},
|
||||
};
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
fn handle_pvmemcontrol_request(&self, guest_addr: GuestAddress) {
|
||||
let request: PvmemcontrolReq = if let Ok(x) = self.mem.memory().read_obj(guest_addr) {
|
||||
x
|
||||
} else {
|
||||
warn!("cannot read from guest address {:#x}", guest_addr.0);
|
||||
return;
|
||||
};
|
||||
|
||||
let response: PvmemcontrolResp = match self.handle_request(request) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
warn!("cannot process request {:?} with error {}", request, e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if self.mem.memory().write_obj(response, guest_addr).is_err() {
|
||||
warn!("cannot write to guest address {:#x}", guest_addr.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_guest_write(&self, offset: u64, data: &[u8]) {
|
||||
if offset as usize != std::mem::offset_of!(PvmemcontrolTransport, command) {
|
||||
if data.len() != 4 && data.len() != 8 {
|
||||
warn!("guest write is not 4 or 8 bytes long");
|
||||
return;
|
||||
}
|
||||
self.dev.write().unwrap().write_transport(offset, data);
|
||||
return;
|
||||
}
|
||||
let data = if data.len() == 4 {
|
||||
let mut d = [0u8; 4];
|
||||
d.iter_mut()
|
||||
.zip(data.iter())
|
||||
.for_each(|(d, data)| *d = *data);
|
||||
d
|
||||
} else {
|
||||
warn!("guest write with non u32 at command register");
|
||||
return;
|
||||
};
|
||||
let data_cmd = u32::from_le_bytes(data);
|
||||
let command = PvmemcontrolTransportCommand::try_from(data_cmd);
|
||||
|
||||
match command {
|
||||
Ok(command) => self.dev.write().unwrap().run_command(&self.mem, command),
|
||||
Err(_) => {
|
||||
GuestConnection::try_from(data_cmd)
|
||||
.and_then(|conn| {
|
||||
self.dev
|
||||
.read()
|
||||
.unwrap()
|
||||
.find_connection(conn)
|
||||
.ok_or(Error::InvalidConnection(conn.command))
|
||||
})
|
||||
.map(|gpa| self.handle_pvmemcontrol_request(gpa))
|
||||
.unwrap_or_else(|err| warn!("{:?}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_guest_read(&self, offset: u64, data: &mut [u8]) {
|
||||
self.dev.read().unwrap().read_transport(offset, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl PvmemcontrolDevice {
|
||||
pub fn make_device(
|
||||
id: String,
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||
) -> (PvmemcontrolPciDevice, PvmemcontrolBusDevice) {
|
||||
let dev = RwLock::new(PvmemcontrolDevice::error());
|
||||
let mut configuration = PciConfiguration::new(
|
||||
PVMEMCONTROL_VENDOR_ID,
|
||||
PVMEMCONTROL_DEVICE_ID,
|
||||
0x1,
|
||||
PciClassCode::BaseSystemPeripheral,
|
||||
&PvmemcontrolSubclass::Other,
|
||||
None,
|
||||
PciHeaderType::Device,
|
||||
PVMEMCONTROL_SUBSYSTEM_VENDOR_ID,
|
||||
PVMEMCONTROL_SUBSYSTEM_ID,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let command: [u8; 2] = [0x03, 0x01]; // memory, io, SERR#
|
||||
|
||||
configuration.write_config_register(1, 0, &command);
|
||||
(
|
||||
PvmemcontrolPciDevice {
|
||||
id,
|
||||
configuration,
|
||||
bar_regions: Vec::new(),
|
||||
},
|
||||
PvmemcontrolBusDevice { mem, dev },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PciDevice for PvmemcontrolPciDevice {
|
||||
fn write_config_register(
|
||||
&mut self,
|
||||
reg_idx: usize,
|
||||
offset: u64,
|
||||
data: &[u8],
|
||||
) -> Option<Arc<Barrier>> {
|
||||
self.configuration
|
||||
.write_config_register(reg_idx, offset, data);
|
||||
None
|
||||
}
|
||||
|
||||
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
||||
self.configuration.read_config_register(reg_idx)
|
||||
}
|
||||
|
||||
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
fn detect_bar_reprogramming(
|
||||
&mut self,
|
||||
reg_idx: usize,
|
||||
data: &[u8],
|
||||
) -> Option<BarReprogrammingParams> {
|
||||
self.configuration.detect_bar_reprogramming(reg_idx, data)
|
||||
}
|
||||
|
||||
fn allocate_bars(
|
||||
&mut self,
|
||||
_allocator: &Arc<Mutex<SystemAllocator>>,
|
||||
mmio32_allocator: &mut AddressAllocator,
|
||||
_mmio64_allocator: &mut AddressAllocator,
|
||||
resources: Option<Vec<Resource>>,
|
||||
) -> Result<Vec<PciBarConfiguration>, PciDeviceError> {
|
||||
let mut bars = Vec::new();
|
||||
let region_type = PciBarRegionType::Memory32BitRegion;
|
||||
let bar_id = 0;
|
||||
let region_size = PVMEMCONTROL_DEVICE_MMIO_SIZE;
|
||||
let restoring = resources.is_some();
|
||||
let bar_addr = mmio32_allocator
|
||||
.allocate(None, region_size, Some(PVMEMCONTROL_DEVICE_MMIO_ALIGN))
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
|
||||
|
||||
let bar = PciBarConfiguration::default()
|
||||
.set_index(bar_id as usize)
|
||||
.set_address(bar_addr.raw_value())
|
||||
.set_size(region_size)
|
||||
.set_region_type(region_type)
|
||||
.set_prefetchable(PciBarPrefetchable::NotPrefetchable);
|
||||
|
||||
if !restoring {
|
||||
self.configuration
|
||||
.add_pci_bar(&bar)
|
||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
|
||||
}
|
||||
|
||||
bars.push(bar);
|
||||
self.bar_regions.clone_from(&bars);
|
||||
Ok(bars)
|
||||
}
|
||||
|
||||
fn free_bars(
|
||||
&mut self,
|
||||
_allocator: &mut SystemAllocator,
|
||||
mmio32_allocator: &mut AddressAllocator,
|
||||
_mmio64_allocator: &mut AddressAllocator,
|
||||
) -> Result<(), PciDeviceError> {
|
||||
for bar in self.bar_regions.drain(..) {
|
||||
mmio32_allocator.free(GuestAddress(bar.addr()), bar.size())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), io::Error> {
|
||||
for bar in self.bar_regions.iter_mut() {
|
||||
if bar.addr() == old_base {
|
||||
*bar = bar.set_address(new_base);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for PvmemcontrolPciDevice {
|
||||
fn pause(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resume(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for PvmemcontrolPciDevice {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let mut snapshot = Snapshot::new_from_state(&())?;
|
||||
|
||||
// Snapshot PciConfiguration
|
||||
snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
|
||||
|
||||
Ok(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
impl Transportable for PvmemcontrolPciDevice {}
|
||||
impl Migratable for PvmemcontrolPciDevice {}
|
||||
|
||||
impl BusDeviceSync for PvmemcontrolBusDevice {
|
||||
fn read(&self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
self.handle_guest_read(offset, data)
|
||||
}
|
||||
|
||||
fn write(&self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
self.handle_guest_write(offset, data);
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -450,7 +450,7 @@ impl BusDevice for Tpm {
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
error!("Invalid value passed to CRTL_REQ register");
|
||||
error!("Invalid value passed to CTRL_REQ register");
|
||||
return None;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -270,7 +270,7 @@ definition in XML format:
|
||||
### Command Line Interface
|
||||
|
||||
The Cloud Hypervisor Command Line Interface (CLI) can only be used for launching
|
||||
the Cloud Hypervisor binary, i.e. it can not be used for controlling the VMM or
|
||||
the Cloud Hypervisor binary, i.e. it cannot be used for controlling the VMM or
|
||||
the launched VM once they're up and running.
|
||||
|
||||
If you want to inspect the VMM, or control the VM after launching Cloud
|
||||
|
||||
@@ -159,10 +159,10 @@ as we might need to update the direct kernel boot command line, replacing
|
||||
|
||||
Update all references to the previous image name to the new one.
|
||||
|
||||
## NVIDIA image for VFIO baremetal CI
|
||||
## NVIDIA image for VFIO bare-metal CI
|
||||
|
||||
Here we are going to describe how to create a cloud image that contains the
|
||||
necessary NVIDIA drivers for our VFIO baremetal CI.
|
||||
necessary NVIDIA drivers for our VFIO bare-metal CI.
|
||||
|
||||
### Download base image
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Cloud Hypervisor uses [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) for fuzzing individual components.
|
||||
|
||||
The fuzzers are are in the `fuzz/fuzz_targets` directory
|
||||
The fuzzers are in the `fuzz/fuzz_targets` directory
|
||||
|
||||
## Preparation
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ On-line CPU(s) list: 0-7
|
||||
|
||||
After a reboot the added CPUs will remain.
|
||||
|
||||
Removing CPUs works similarly by reducing the number in the "desired_vcpus" field of the reisze API. The CPUs will be automatically offlined inside the guest so there is no need to run any commands inside the guest:
|
||||
Removing CPUs works similarly by reducing the number in the "desired_vcpus" field of the resize API. The CPUs will be automatically offlined inside the guest so there is no need to run any commands inside the guest:
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket resize --cpus 2
|
||||
|
||||
@@ -39,7 +39,7 @@ Another reason for having a virtual IOMMU is to allow passing physical devices
|
||||
from the host through multiple layers of virtualization. Let's take as example
|
||||
a system with a physical IOMMU running a VM with a virtual IOMMU. The
|
||||
implementation of the virtual IOMMU is responsible for updating the physical
|
||||
DMA Remapping table (DMAR) everytime the DMA mapping changes. This must happen
|
||||
DMA Remapping table (DMAR) every time the DMA mapping changes. This must happen
|
||||
through the VFIO framework on the host as this is the only userspace interface
|
||||
to interact with a physical IOMMU.
|
||||
|
||||
@@ -124,7 +124,7 @@ On AArch64 architecture, the virtual IOMMU can still be used even if ACPI is not
|
||||
enabled. But the effect is different with what the aforementioned test showed.
|
||||
|
||||
When ACPI is disabled, virtual IOMMU is supported through Flattened Device Tree
|
||||
(FDT). In this case, the guest kernel can not tell which device should be
|
||||
(FDT). In this case, the guest kernel cannot tell which device should be
|
||||
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
|
||||
|
||||
105
docs/landlock.md
Normal file
105
docs/landlock.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Sandboxing using Landlock
|
||||
|
||||
Landlock is a lightweight mechanism to allow unprivileged applications to
|
||||
sandbox themselves.
|
||||
|
||||
During initial stages of running, applications can define the set of resources
|
||||
(mostly files) they need to access during their lifetime. All such rules are
|
||||
used to create a ruleset. Once the ruleset is applied, the process cannot access
|
||||
any resources outside of the ruleset during its lifetime, even if it were
|
||||
compromised.
|
||||
|
||||
Under the scope of `read` and `write` access, Landlock currently allows some
|
||||
additional accesses (eg: for now, access to extended file attributes is always
|
||||
allowed). Eventually, Landlock will only allow accesses similar to Unix
|
||||
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 support can be checked with following command:
|
||||
```
|
||||
$ sudo dmesg | grep -w landlock
|
||||
[ 0.000000] landlock: Up and running.
|
||||
```
|
||||
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.
|
||||
|
||||
### Command Line
|
||||
Append `--landlock` to Cloud-Hypervisor's command line to enable Landlock
|
||||
support.
|
||||
|
||||
If you expect guest to access additional paths after it boots
|
||||
(ex: during hotplug), those paths can be passed using `--landlock-rules` command
|
||||
line parameter.
|
||||
|
||||
### API
|
||||
Landlock can also be enabled during `vm.create` request by passing a config like below:
|
||||
|
||||
```
|
||||
{
|
||||
...
|
||||
"landlock_enable": true,
|
||||
"landlock_rules": [
|
||||
{
|
||||
"path": "/tmp/disk1",
|
||||
"access": "rw"
|
||||
},
|
||||
{
|
||||
"path": "/tmp/disk2",
|
||||
"access": "rw"
|
||||
}
|
||||
]
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Usage Examples
|
||||
|
||||
To enable Landlock:
|
||||
|
||||
```
|
||||
./cloud-hypervisor \
|
||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
||||
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
--landlock
|
||||
```
|
||||
Hotplugging any new file-backed resources to above guest will result in
|
||||
**Permission Denied** error.
|
||||
|
||||
To enable Landlock with hotplug support:
|
||||
|
||||
```
|
||||
./cloud-hypervisor \
|
||||
--api-socket /tmpXXXX/ch.socket \
|
||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
||||
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
--landlock \
|
||||
--landlock-rules path="/path/to/hotplug1",access="rw" path="/path/to/hotplug2",access="rw"
|
||||
|
||||
./ch-remote --api-socket /tmpXXXX/ch.socket \
|
||||
add-disk "path=/path/to/hotplug/blk.raw"
|
||||
```
|
||||
|
||||
`--landlock-rules` accepts file or directory paths among its options.
|
||||
|
||||
# References
|
||||
|
||||
* https://landlock.io/
|
||||
@@ -466,7 +466,7 @@ List of virtual CPUs attached to the guest NUMA node identified by the
|
||||
`guest_numa_id` option. This allows for describing a list of CPUs which
|
||||
must be seen by the guest as belonging to the NUMA node `guest_numa_id`.
|
||||
|
||||
One can use this option for a fine grained description of the NUMA topology
|
||||
One can use this option for a fine-grained description of the NUMA topology
|
||||
regarding the CPUs associated with it, which might help the guest run more
|
||||
efficiently.
|
||||
|
||||
@@ -573,7 +573,7 @@ _Example_
|
||||
### 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 afffinity to NUMA node 0.
|
||||
has affinity to NUMA node 0. Be default, all other PCI segments have affinity to NUMA node 0.
|
||||
The user may configure the NUMA affinity for any additional PCI segments.
|
||||
|
||||
_Example_
|
||||
|
||||
@@ -8,6 +8,8 @@ e.g. `dev_cli.sh`. The only prerequisite is [Docker installation](https://docs.d
|
||||
Please note that upon its first invocation, this script will pull a
|
||||
fairly large container image.
|
||||
|
||||
## Run the Performance Tests
|
||||
|
||||
To generate metrics data for all available performance tests (including
|
||||
boot time, block I/O throughput, and network throughput & latency) and
|
||||
output the result into a json file:
|
||||
@@ -27,3 +29,92 @@ To generate metrics data for selected performance tests, e.g. boot time only:
|
||||
```
|
||||
$ ./scripts/dev_cli.sh tests --metrics -- -- --report-file /tmp/metrics.json --test-filter boot_time
|
||||
```
|
||||
|
||||
To set custom timeout or test iterations for all performance tests:
|
||||
|
||||
```
|
||||
$ ./scripts/dev_cli.sh tests --metrics -- -- --timeout 5 --iterations 10
|
||||
```
|
||||
|
||||
## Performance Tests Details
|
||||
|
||||
The following table lists the supported performance tests with default
|
||||
timeout and number of iterations. The `timeout` defines the maximum
|
||||
execution time of each test for each iteration. The `iteration` defines
|
||||
how many times a test needs to be executed to generate the final metrics
|
||||
data.
|
||||
|
||||
| **Type** | **Metric** | **Timeout(s)** | **Iterations** |
|
||||
|------------|--------------------------------------------|----------------|----------------|
|
||||
| Boot Time | boot_time_ms | 2 | 10 |
|
||||
| | boot_time_pmem_ms | 2 | 10 |
|
||||
| | boot_time_16_vcpus_ms | 2 | 10 |
|
||||
| | boot_time_16_vcpus_pmem_ms | 2 | 10 |
|
||||
| Virtio Net | virtio_net_latency_us | 10 | 5 |
|
||||
| | virtio_net_throughput_single_queue_rx_gbps | 10 | 5 |
|
||||
| | virtio_net_throughput_single_queue_tx_gbps | 10 | 5 |
|
||||
| | virtio_net_throughput_multi_queue_rx_gbps | 10 | 5 |
|
||||
| | virtio_net_throughput_multi_queue_tx_gbps | 10 | 5 |
|
||||
| Block | block_read_MiBps | 10 | 5 |
|
||||
| | block_write_MiBps | 10 | 5 |
|
||||
| | block_random_read_MiBps | 10 | 5 |
|
||||
| | block_random_write_MiBps | 10 | 5 |
|
||||
| | block_multi_queue_read_MiBps | 10 | 5 |
|
||||
| | block_multi_queue_write_MiBps | 10 | 5 |
|
||||
| | block_multi_queue_random_read_MiBps | 10 | 5 |
|
||||
| | block_multi_queue_random_write_MiBps | 10 | 5 |
|
||||
| | block_read_IOPS | 10 | 5 |
|
||||
| | block_write_IOPS | 10 | 5 |
|
||||
| | block_random_read_IOPS | 10 | 5 |
|
||||
| | block_random_write_IOPS | 10 | 5 |
|
||||
| | block_multi_queue_read_IOPS | 10 | 5 |
|
||||
| | block_multi_queue_write_IOPS | 10 | 5 |
|
||||
| | block_multi_queue_random_read_IOPS | 10 | 5 |
|
||||
| | block_multi_queue_random_write_IOPS | 10 | 5 |
|
||||
| Other | restore_latency_time_ms | 2 | 10 |
|
||||
|
||||
## Output Format
|
||||
|
||||
Performance-metrics output the result into a json file if `report-file`
|
||||
param is set. The fields included in JSON include:
|
||||
|
||||
| Field Name | Content |
|
||||
|--------------------|------------------------------------------|
|
||||
| git_human_readable | Recent tag information of git repository |
|
||||
| git_revision | Commit id of HEAD |
|
||||
| git_commit_date | Commit date of HEAD |
|
||||
| date | Date for executing the program |
|
||||
| results | A list of metrics |
|
||||
|
||||
## Example
|
||||
|
||||
Here is an example of generating metrics data for the boot time using
|
||||
`pmem`:
|
||||
|
||||
```bash
|
||||
$ ./scripts/dev_cli.sh tests --metrics -- -- --test-filter boot_time_pmem_ms
|
||||
```
|
||||
|
||||
Here is a sample output:
|
||||
|
||||
```json
|
||||
{
|
||||
"git_human_readable": "v40.0",
|
||||
"git_revision": "e9b263975786abbf895469b93dfc00f21ce39a88",
|
||||
"git_commit_date": "Fri Jun 21 08:40:44 2024 +0000",
|
||||
"date": "Tue Jul 16 16:35:29 UTC 2024",
|
||||
"results": [
|
||||
{
|
||||
"name": "boot_time_pmem_ms",
|
||||
"mean": 105.9461,
|
||||
"std_dev": 7.140993312558129,
|
||||
"max": 120.01499999999999,
|
||||
"min": 92.37600000000002
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note that the metrics data above is for illustration purpose only and
|
||||
does not represent the actual performance of Cloud Hypervisor on your
|
||||
system.
|
||||
|
||||
2
docs/releases.md
Executable file → Normal file
2
docs/releases.md
Executable file → Normal file
@@ -95,7 +95,7 @@ E - EOL
|
||||
|
||||
```
|
||||
|
||||
### LTS Stablity Considerations
|
||||
### LTS Stability Considerations
|
||||
|
||||
An LTS release is just a `MAJOR` release for which point releases are made for
|
||||
longer following the same rules for what can be backported to a `POINT` release.
|
||||
|
||||
@@ -63,7 +63,7 @@ component in the state it was left before the snapshot occurred.
|
||||
## Restore a Cloud Hypervisor VM
|
||||
|
||||
Given that one has access to an existing snapshot in `/home/foo/snapshot`,
|
||||
it is possible to create a new VM based on this snapshot with the following
|
||||
it is possible to create a new VM based on this snapshot with the following
|
||||
command:
|
||||
|
||||
```bash
|
||||
@@ -93,6 +93,21 @@ start using it.
|
||||
At this point, the VM is fully restored and is identical to the VM which was
|
||||
snapshot earlier.
|
||||
|
||||
## Restore a VM with new Net FDs
|
||||
For a VM created with FDs explicitly passed to NetConfig, a set of valid FDs
|
||||
need to be provided along with the VM restore command in the following syntax:
|
||||
|
||||
```bash
|
||||
# First terminal
|
||||
./cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock
|
||||
|
||||
# Second terminal
|
||||
./ch-remote --api-socket=/tmp/cloud-hypervisor.sock restore source_url=file:///home/foo/snapshot net_fds=[net1@[23,24],net2@[25,26]]
|
||||
```
|
||||
In the example above, the net device with id `net1` will be backed by FDs '23'
|
||||
and '24', and the net device with id `net2` will be backed by FDs '25' and '26'
|
||||
from the restored VM.
|
||||
|
||||
## Limitations
|
||||
|
||||
VFIO devices and Intel SGX are out of scope.
|
||||
|
||||
@@ -37,6 +37,6 @@ generating traces of the boot. These can be relocated for focus tracing on a
|
||||
narrow part of the code base.
|
||||
|
||||
A `tracer::trace_point!()` macro is also provided for an instantaneous trace
|
||||
point however this is not in use in the code base currently nor is handled by
|
||||
point however this is neither in use in the code base currently nor is handled by
|
||||
the visualisation script due to the difficulty in representation in the SVG.
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "event_monitor"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
name = "event_monitor"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
flume = "0.11.0"
|
||||
libc = "0.2.153"
|
||||
libc = "0.2.155"
|
||||
once_cell = "1.19.0"
|
||||
serde = { version = "1.0.197", features = ["rc", "derive"] }
|
||||
serde_json = "1.0.115"
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.120"
|
||||
|
||||
432
fuzz/Cargo.lock
generated
432
fuzz/Cargo.lock
generated
@@ -12,47 +12,48 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.13"
|
||||
version = "0.6.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
|
||||
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.6"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
|
||||
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.2"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
||||
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.2"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
@@ -60,16 +61,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.82"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
|
||||
|
||||
[[package]]
|
||||
name = "api_client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
@@ -105,9 +99,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
||||
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
@@ -117,9 +111,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
@@ -127,7 +121,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crc-any",
|
||||
"io-uring",
|
||||
"libc",
|
||||
"log",
|
||||
"remain",
|
||||
@@ -156,12 +149,13 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.92"
|
||||
version = "1.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41"
|
||||
checksum = "5fb8dd288a69fc53a1996d7ecfbf4a20d59065bff137ce7e56bbd620de191189"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -172,61 +166,36 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.4"
|
||||
version = "4.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
||||
checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.2"
|
||||
version = "4.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
||||
checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim 0.11.1",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.0"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||
|
||||
[[package]]
|
||||
name = "cloud-hypervisor"
|
||||
version = "38.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"api_client",
|
||||
"clap",
|
||||
"epoll",
|
||||
"event_monitor",
|
||||
"hypervisor",
|
||||
"libc",
|
||||
"log",
|
||||
"option_parser",
|
||||
"seccompiler",
|
||||
"serde_json",
|
||||
"signal-hook",
|
||||
"thiserror",
|
||||
"tpm",
|
||||
"tracer",
|
||||
"vm-memory",
|
||||
"vmm",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||
|
||||
[[package]]
|
||||
name = "cloud-hypervisor-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"block",
|
||||
"cloud-hypervisor",
|
||||
"devices",
|
||||
"epoll",
|
||||
"libc",
|
||||
@@ -248,24 +217,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
|
||||
|
||||
[[package]]
|
||||
name = "crc-any"
|
||||
version = "2.4.4"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c01a5e1f881f6fb6099a7bdf949e946719fd4f1fefa56264890574febf0eb6d0"
|
||||
checksum = "a62ec9ff5f7965e4d7280bd5482acd20aadb50d632cf6c1d74493856b011fa73"
|
||||
dependencies = [
|
||||
"debug-helper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.8"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
|
||||
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
@@ -273,23 +242,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.8"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
|
||||
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.8"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
@@ -309,12 +278,13 @@ dependencies = [
|
||||
"acpi_tables",
|
||||
"anyhow",
|
||||
"arch",
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"byteorder",
|
||||
"event_monitor",
|
||||
"hypervisor",
|
||||
"libc",
|
||||
"log",
|
||||
"num_enum",
|
||||
"pci",
|
||||
"serde",
|
||||
"thiserror",
|
||||
@@ -326,16 +296,42 @@ dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d"
|
||||
dependencies = [
|
||||
"enumflags2_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2_derive"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epoll"
|
||||
version = "4.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74351c3392ea1ff6cd2628e0042d268ac2371cb613252ff383b6dfa50d22fa79"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "event_monitor"
|
||||
version = "0.1.0"
|
||||
@@ -384,10 +380,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.14"
|
||||
name = "gdbstub"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
|
||||
checksum = "6341b3480afbb34eaefc7f92713bc92f2d83e338aaa1c44192f9c2956f4a4903"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if",
|
||||
"log",
|
||||
"managed",
|
||||
"num-traits",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gdbstub_arch"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e3b1357bd3203fc09a6601327ae0ab38865d14231d0b65d3143f5762cc7977d"
|
||||
dependencies = [
|
||||
"gdbstub",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -396,6 +416,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hypervisor"
|
||||
version = "0.1.0"
|
||||
@@ -421,15 +447,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.6.3"
|
||||
name = "indexmap"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9febecd4aebbe9c7c23c8e536e966805fdf09944c8a915e7991ee51acb67087"
|
||||
checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
@@ -438,9 +470,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.30"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "685a7d121ee3f65ae4fddd72b25a04bb36b6af81bc0828f7d5434c0fe60fa3a2"
|
||||
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -471,17 +503,28 @@ version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bedae2ca4a531bebe311abaf9691f5cc14eaa21475243caa2e39c43bb872947d"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"kvm-bindings",
|
||||
"libc",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
name = "landlock"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
checksum = "dafb8a4afee64f167eb2b52d32f0eea002e41a7a6450e68c799c8ec3a81a634c"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"libc",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
@@ -505,9 +548,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.11"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
|
||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
@@ -515,9 +558,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "managed"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||
|
||||
[[package]]
|
||||
name = "micro_http"
|
||||
@@ -563,6 +618,36 @@ dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
@@ -573,6 +658,12 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
name = "option_parser"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pci"
|
||||
version = "0.1.0"
|
||||
@@ -595,10 +686,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.81"
|
||||
name = "proc-macro-crate"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||
checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -625,9 +725,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "remain"
|
||||
version = "0.2.13"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad9f2390298a947ee0aa6073d440e221c0726188cfbcdf9604addb6ee393eb4a"
|
||||
checksum = "46aef80f842736de545ada6ec65b81ee91504efd6853f4b96de7414c42ae7443"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -636,9 +736,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.17"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
@@ -657,18 +757,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.198"
|
||||
version = "1.0.203"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
|
||||
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.198"
|
||||
version = "1.0.203"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
|
||||
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -677,9 +777,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.115"
|
||||
version = "1.0.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
|
||||
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -688,9 +788,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.8.0"
|
||||
version = "3.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c85f8e96d1d6857f13768fcbd895fcb06225510022a2774ed8b5150581847b0"
|
||||
checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -699,9 +799,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.8.0"
|
||||
version = "3.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8b3a576c4eb2924262d5951a3b737ccaf16c931e39a2810c36f9a7e25575557"
|
||||
checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
@@ -713,6 +813,12 @@ dependencies = [
|
||||
name = "serial_buffer"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.17"
|
||||
@@ -747,12 +853,6 @@ dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@@ -761,9 +861,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.58"
|
||||
version = "2.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
|
||||
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -772,24 +872,41 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.58"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.58"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tpm"
|
||||
version = "0.1.0"
|
||||
@@ -822,15 +939,15 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.8.0"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
|
||||
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
@@ -878,11 +995,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vhost"
|
||||
version = "0.10.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b64e816d0d49769fbfaa1494eb77cc2a3ddc526ead05c7f922cb7d64106286f"
|
||||
checksum = "6be08d1166d41a78861ad50212ab3f9eca0729c349ac3a7a8f557c62406b87cc"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"libc",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
@@ -929,9 +1046,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "virtio-queue"
|
||||
version = "0.11.0"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3f69a13d6610db9312acbb438b0390362af905d37634a2106be70c0f734986d"
|
||||
checksum = "07d8406e7250c934462de585d8f2d2781c31819bca1fbb7c5e964ca6bbaabfe8"
|
||||
dependencies = [
|
||||
"log",
|
||||
"virtio-bindings",
|
||||
@@ -1006,7 +1123,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"arch",
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 2.6.0",
|
||||
"block",
|
||||
"cfg-if",
|
||||
"clap",
|
||||
@@ -1014,7 +1131,10 @@ dependencies = [
|
||||
"epoll",
|
||||
"event_monitor",
|
||||
"flume",
|
||||
"gdbstub",
|
||||
"gdbstub_arch",
|
||||
"hypervisor",
|
||||
"landlock",
|
||||
"libc",
|
||||
"linux-loader",
|
||||
"log",
|
||||
@@ -1150,13 +1270,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.4"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
@@ -1165,51 +1286,66 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.4"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.4"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.32"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
@@ -1217,9 +1353,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.32"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -1,135 +1,133 @@
|
||||
[package]
|
||||
name = "cloud-hypervisor-fuzz"
|
||||
version = "0.0.0"
|
||||
authors = ["Automatically generated"]
|
||||
publish = false
|
||||
edition = "2021"
|
||||
name = "cloud-hypervisor-fuzz"
|
||||
publish = false
|
||||
version = "0.0.0"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[features]
|
||||
igvm = []
|
||||
pvmemcontrol = []
|
||||
|
||||
[dependencies]
|
||||
block = { path = "../block" }
|
||||
devices = { path = "../devices" }
|
||||
epoll = "4.3.3"
|
||||
libc = "0.2.153"
|
||||
libc = "0.2.155"
|
||||
libfuzzer-sys = "0.4.7"
|
||||
linux-loader = { version = "0.11.0", features = ["elf", "bzimage", "pe"] }
|
||||
linux-loader = { version = "0.11.0", features = ["bzimage", "elf", "pe"] }
|
||||
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" }
|
||||
net_util = { path = "../net_util" }
|
||||
once_cell = "1.19.0"
|
||||
seccompiler = "0.4.0"
|
||||
virtio-devices = { path = "../virtio-devices" }
|
||||
virtio-queue = "0.11.0"
|
||||
vmm = { path = "../vmm" }
|
||||
vmm-sys-util = "0.12.1"
|
||||
virtio-queue = "0.12.0"
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = "0.14.1"
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
|
||||
[dependencies.cloud-hypervisor]
|
||||
path = ".."
|
||||
vmm = { path = "../vmm", features = ["guest_debug"] }
|
||||
vmm-sys-util = "0.12.1"
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "balloon"
|
||||
path = "fuzz_targets/balloon.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "block"
|
||||
path = "fuzz_targets/block.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "cmos"
|
||||
path = "fuzz_targets/cmos.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "console"
|
||||
path = "fuzz_targets/console.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "http_api"
|
||||
path = "fuzz_targets/http_api.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "iommu"
|
||||
path = "fuzz_targets/iommu.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "linux_loader"
|
||||
path = "fuzz_targets/linux_loader.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "linux_loader_cmdline"
|
||||
path = "fuzz_targets/linux_loader_cmdline.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "mem"
|
||||
path = "fuzz_targets/mem.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "net"
|
||||
path = "fuzz_targets/net.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "pmem"
|
||||
path = "fuzz_targets/pmem.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "qcow"
|
||||
path = "fuzz_targets/qcow.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "rng"
|
||||
path = "fuzz_targets/rng.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "serial"
|
||||
path = "fuzz_targets/serial.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "vhdx"
|
||||
path = "fuzz_targets/vhdx.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "watchdog"
|
||||
path = "fuzz_targets/watchdog.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
@@ -105,7 +105,7 @@ impl RequestHandler for StubApiRequestHandler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn vm_coredump(&mut self, _: &str) -> Result<(), VmError> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -180,17 +180,20 @@ impl RequestHandler for StubApiRequestHandler {
|
||||
vdpa: None,
|
||||
vsock: None,
|
||||
pvpanic: false,
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pvmemcontrol: None,
|
||||
iommu: false,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
sgx_epc: None,
|
||||
numa: None,
|
||||
watchdog: false,
|
||||
#[cfg(feature = "guest_debug")]
|
||||
gdb: false,
|
||||
pci_segments: None,
|
||||
platform: None,
|
||||
tpm: None,
|
||||
preserved_fds: None,
|
||||
landlock_enable: false,
|
||||
landlock_rules: None,
|
||||
})),
|
||||
state: VmState::Running,
|
||||
memory_actual_size: 0,
|
||||
|
||||
@@ -1,39 +1,47 @@
|
||||
[package]
|
||||
name = "hypervisor"
|
||||
version = "0.1.0"
|
||||
authors = ["Microsoft Authors"]
|
||||
edition = "2021"
|
||||
license = "Apache-2.0 OR BSD-3-Clause"
|
||||
name = "hypervisor"
|
||||
version = "0.1.0"
|
||||
|
||||
[features]
|
||||
kvm = ["kvm-ioctls", "kvm-bindings", "vfio-ioctls/kvm"]
|
||||
mshv = ["mshv-ioctls", "mshv-bindings", "vfio-ioctls/mshv", "iced-x86"]
|
||||
sev_snp = ["igvm_parser", "igvm_defs"]
|
||||
kvm = ["kvm-bindings", "kvm-ioctls", "vfio-ioctls/kvm"]
|
||||
mshv = ["iced-x86", "mshv-bindings", "mshv-ioctls", "vfio-ioctls/mshv"]
|
||||
sev_snp = ["igvm", "igvm_defs"]
|
||||
tdx = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.81"
|
||||
anyhow = "1.0.86"
|
||||
byteorder = "1.5.0"
|
||||
igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main", package = "igvm_defs", optional = true }
|
||||
igvm_parser = { git = "https://github.com/microsoft/igvm", branch = "main", package = "igvm", optional = true }
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
igvm = { version = "0.3.3", optional = true }
|
||||
igvm_defs = { version = "0.3.1", optional = true }
|
||||
kvm-bindings = { version = "0.8.1", optional = true, features = ["serde"] }
|
||||
kvm-ioctls = { version = "0.17.0", optional = true }
|
||||
mshv-bindings = { git = "https://github.com/rust-vmm/mshv", branch = "main", features = ["with-serde", "fam-wrappers"], optional = true }
|
||||
mshv-ioctls = { git = "https://github.com/rust-vmm/mshv", branch = "main", optional = true}
|
||||
serde = { version = "1.0.197", features = ["rc", "derive"] }
|
||||
serde_with = { version = "3.7.0", default-features = false, features = ["macros"] }
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
mshv-bindings = { git = "https://github.com/rust-vmm/mshv", tag = "v0.2.0", features = [
|
||||
"fam-wrappers",
|
||||
"with-serde",
|
||||
], optional = true }
|
||||
mshv-ioctls = { git = "https://github.com/rust-vmm/mshv", tag = "v0.2.0", optional = true }
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
serde_with = { version = "3.9.0", default-features = false, features = [
|
||||
"macros",
|
||||
] }
|
||||
thiserror = "1.0.62"
|
||||
vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false }
|
||||
vm-memory = { version = "0.14.1", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-memory = { version = "0.14.1", features = [
|
||||
"backend-atomic",
|
||||
"backend-mmap",
|
||||
] }
|
||||
vmm-sys-util = { version = "0.12.1", features = ["with-serde"] }
|
||||
thiserror = "1.0.58"
|
||||
|
||||
[target.'cfg(target_arch = "x86_64")'.dependencies.iced-x86]
|
||||
default-features = false
|
||||
features = ["decoder", "fast_fmt", "instr_info", "op_code_info", "std"]
|
||||
optional = true
|
||||
version = "1.21.0"
|
||||
default-features = false
|
||||
features = ["std", "decoder", "op_code_info", "instr_info", "fast_fmt"]
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.11.3"
|
||||
|
||||
@@ -5,15 +5,19 @@
|
||||
use crate::{CpuState, GicState, HypervisorDeviceError, HypervisorVmError};
|
||||
use std::any::Any;
|
||||
use std::result;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors thrown while setting up the VGIC.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Error while calling KVM ioctl for setting up the global interrupt controller.
|
||||
#[error("Failed creating GIC device: {0}")]
|
||||
CreateGic(HypervisorVmError),
|
||||
/// Error while setting device attributes for the GIC.
|
||||
#[error("Failed setting device attributes for the GIC: {0}")]
|
||||
SetDeviceAttribute(HypervisorDeviceError),
|
||||
/// Error while getting device attributes for the GIC.
|
||||
#[error("Failed getting device attributes for the GIC: {0}")]
|
||||
GetDeviceAttribute(HypervisorDeviceError),
|
||||
}
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
@@ -137,14 +137,6 @@ pub trait PlatformEmulator {
|
||||
///
|
||||
fn set_cpu_state(&self, cpu_id: usize, state: Self::CpuState) -> Result<(), PlatformError>;
|
||||
|
||||
/// Translate a guest virtual address into a physical one
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `gva` - Guest virtual address to translate.
|
||||
///
|
||||
fn gva_to_gpa(&self, gva: u64) -> Result<u64, PlatformError>;
|
||||
|
||||
/// Fetch instruction bytes from memory.
|
||||
///
|
||||
/// # Arguments
|
||||
|
||||
@@ -13,6 +13,7 @@ pub mod cmp;
|
||||
pub mod mov;
|
||||
pub mod movs;
|
||||
pub mod or;
|
||||
pub mod stos;
|
||||
|
||||
fn get_op<T: CpuStateManager>(
|
||||
insn: &Instruction,
|
||||
|
||||
@@ -40,7 +40,7 @@ macro_rules! movs {
|
||||
let len = std::mem::size_of::<$bound>();
|
||||
|
||||
while count > 0 {
|
||||
let mut memory: [u8; 4] = [0; 4];
|
||||
let mut memory: [u8; 8] = [0; 8];
|
||||
|
||||
let src = state
|
||||
.linearize(Register::DS, rsi, false)
|
||||
@@ -83,6 +83,11 @@ macro_rules! movs {
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Movsq_m64_m64;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Movsq_m64_m64 {
|
||||
movs!(u64);
|
||||
}
|
||||
|
||||
pub struct Movsd_m32_m32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Movsd_m32_m32 {
|
||||
movs!(u32);
|
||||
@@ -103,6 +108,40 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::arch::x86::emulator::mock_vmm::*;
|
||||
|
||||
#[test]
|
||||
fn test_rep_movsq_m64_m64() {
|
||||
let ip: u64 = 0x1000;
|
||||
let memory: [u8; 32] = [
|
||||
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||
0xdd, 0xcc, 0xbb, 0xaa, // 0xaabbccdd
|
||||
0xa5, 0x5a, 0xa5, 0x5a, // 0x5aa55aa5
|
||||
0xcd, 0xcd, 0xcd, 0xcd, // 0xcdcdcdcd
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
];
|
||||
let insn = [0xf3, 0x48, 0xa5]; // rep movsq
|
||||
let regs = vec![
|
||||
(Register::ECX, 2),
|
||||
(Register::ESI, 0),
|
||||
(Register::EDI, 0x10),
|
||||
];
|
||||
let mut data = [0u8; 8];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
vmm.read_memory(0x10, &mut data).unwrap();
|
||||
assert_eq!(0xaabbccdd12345678, <u64>::from_le_bytes(data));
|
||||
vmm.read_memory(0x18, &mut data).unwrap();
|
||||
assert_eq!(0xcdcdcdcd5aa55aa5, <u64>::from_le_bytes(data));
|
||||
// The rest should be default value 0 from MockVmm
|
||||
vmm.read_memory(0x20, &mut data).unwrap();
|
||||
assert_eq!(0x0, <u64>::from_le_bytes(data));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rep_movsd_m32_m32() {
|
||||
let ip: u64 = 0x1000;
|
||||
|
||||
236
hypervisor/src/arch/x86/emulator/instructions/stos.rs
Normal file
236
hypervisor/src/arch/x86/emulator/instructions/stos.rs
Normal file
@@ -0,0 +1,236 @@
|
||||
//
|
||||
// Copyright © 2024 Microsoft
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
//
|
||||
// STOS - Store String
|
||||
//
|
||||
|
||||
use crate::arch::x86::emulator::instructions::*;
|
||||
use crate::arch::x86::regs::DF;
|
||||
|
||||
macro_rules! stos {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let mut count: u64 = if insn.has_rep_prefix() {
|
||||
state
|
||||
.read_reg(Register::ECX)
|
||||
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
let rax = state
|
||||
.read_reg(Register::RAX)
|
||||
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||
|
||||
let mut rdi = state
|
||||
.read_reg(Register::RDI)
|
||||
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||
|
||||
let df = (state.flags() & DF) != 0;
|
||||
let len = std::mem::size_of::<$bound>();
|
||||
let rax_bytes = rax.to_le_bytes();
|
||||
|
||||
while count > 0 {
|
||||
let dst = state
|
||||
.linearize(Register::ES, rdi, true)
|
||||
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||
|
||||
platform
|
||||
.write_memory(dst, &rax_bytes[0..len])
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
if df {
|
||||
rdi = rdi.wrapping_sub(len as u64);
|
||||
} else {
|
||||
rdi = rdi.wrapping_add(len as u64);
|
||||
}
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
if insn.has_rep_prefix() {
|
||||
state
|
||||
.write_reg(Register::ECX, 0)
|
||||
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Stosq_m64_RAX;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Stosq_m64_RAX {
|
||||
stos!(u64);
|
||||
}
|
||||
|
||||
pub struct Stosd_m32_EAX;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Stosd_m32_EAX {
|
||||
stos!(u32);
|
||||
}
|
||||
|
||||
pub struct Stosw_m16_AX;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Stosw_m16_AX {
|
||||
stos!(u16);
|
||||
}
|
||||
|
||||
pub struct Stosb_m8_AL;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Stosb_m8_AL {
|
||||
stos!(u8);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::arch::x86::emulator::mock_vmm::*;
|
||||
|
||||
#[test]
|
||||
fn test_rep_stosb() {
|
||||
let ip: u64 = 0x1000;
|
||||
let memory: [u8; 12] = [
|
||||
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||
0xdd, 0xcc, 0xbb, 0xaa, // 0xaabbccdd
|
||||
0xa5, 0x5a, 0xa5, 0x5a, // 0x5aa55aa5
|
||||
];
|
||||
let insn = [0xf3, 0xaa]; // rep stosb
|
||||
let regs = vec![
|
||||
(Register::ECX, 3),
|
||||
(Register::EDI, 0x0),
|
||||
(Register::RAX, 0x123456ff),
|
||||
];
|
||||
let mut data = [0u8; 4];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
vmm.read_memory(0, &mut data).unwrap();
|
||||
assert_eq!(0x12ffffff, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(4, &mut data).unwrap();
|
||||
assert_eq!(0xaabbccdd, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(8, &mut data).unwrap();
|
||||
assert_eq!(0x5aa55aa5, <u32>::from_le_bytes(data));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stosw() {
|
||||
let ip: u64 = 0x1000;
|
||||
let memory: [u8; 4] = [
|
||||
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||
];
|
||||
let insn = [0x66, 0xab]; // stosw
|
||||
let regs = vec![(Register::EDI, 0x1), (Register::AX, 0xaabb)];
|
||||
let mut data = [0u8; 4];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
vmm.read_memory(0x0, &mut data).unwrap();
|
||||
assert_eq!(0x12aabb78, <u32>::from_le_bytes(data));
|
||||
// The rest should be default value 0 from MockVmm
|
||||
vmm.read_memory(0x4, &mut data).unwrap();
|
||||
assert_eq!(0x0, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(0x8 + 8, &mut data).unwrap();
|
||||
assert_eq!(0x0, <u32>::from_le_bytes(data));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rep_stosw() {
|
||||
let ip: u64 = 0x1000;
|
||||
let memory: [u8; 8] = [
|
||||
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
];
|
||||
let insn = [0x66, 0xf3, 0xab]; // rep stosw
|
||||
let regs = vec![
|
||||
(Register::ECX, 2),
|
||||
(Register::EDI, 0x2),
|
||||
(Register::AX, 0xaabb),
|
||||
];
|
||||
let mut data = [0u8; 4];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
vmm.read_memory(0x0, &mut data).unwrap();
|
||||
assert_eq!(0xaabb5678, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(0x4, &mut data).unwrap();
|
||||
assert_eq!(0x0000aabb, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(0x8, &mut data).unwrap();
|
||||
assert_eq!(0x0, <u32>::from_le_bytes(data));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rep_stosd() {
|
||||
let ip: u64 = 0x1000;
|
||||
let memory: [u8; 12] = [
|
||||
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
];
|
||||
let insn = [0xf3, 0xab]; // rep stosd
|
||||
let regs = vec![
|
||||
(Register::ECX, 2),
|
||||
(Register::EDI, 0x8),
|
||||
(Register::EAX, 0xaabbccdd),
|
||||
];
|
||||
let mut data = [0u8; 4];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||
|
||||
// Go backwards this time
|
||||
let mut state = vmm.cpu_state(0).unwrap();
|
||||
state.set_flags(state.flags() | DF);
|
||||
vmm.set_cpu_state(0, state).unwrap();
|
||||
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
vmm.read_memory(0x0, &mut data).unwrap();
|
||||
assert_eq!(0x12345678, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(0x4, &mut data).unwrap();
|
||||
assert_eq!(0xaabbccdd, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(0x8, &mut data).unwrap();
|
||||
assert_eq!(0xaabbccdd, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(0xc, &mut data).unwrap();
|
||||
assert_eq!(0x0, <u32>::from_le_bytes(data));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rep_stosq() {
|
||||
let ip: u64 = 0x1000;
|
||||
let memory: [u8; 8] = [
|
||||
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
];
|
||||
let insn = [0xf3, 0x48, 0xab]; // rep stosq
|
||||
let regs = vec![
|
||||
(Register::ECX, 2),
|
||||
(Register::RDI, 0x0),
|
||||
(Register::RAX, 0x11223344aabbccdd),
|
||||
];
|
||||
let mut data = [0u8; 8];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
vmm.read_memory(0x0, &mut data).unwrap();
|
||||
assert_eq!(0x11223344aabbccdd, <u64>::from_le_bytes(data));
|
||||
vmm.read_memory(0x8, &mut data).unwrap();
|
||||
assert_eq!(0x11223344aabbccdd, <u64>::from_le_bytes(data));
|
||||
vmm.read_memory(0x10, &mut data).unwrap();
|
||||
assert_eq!(0x0, <u64>::from_le_bytes(data));
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,7 @@ pub trait CpuStateManager: Clone {
|
||||
.checked_add(segment_register.base)
|
||||
.ok_or_else(|| {
|
||||
PlatformError::InvalidAddress(anyhow!(
|
||||
"Logical address {:#x} can not be linearized with segment {:#x?}",
|
||||
"Logical address {:#x} cannot be linearized with segment {:#x?}",
|
||||
logical_addr,
|
||||
segment_register
|
||||
))
|
||||
@@ -148,7 +148,7 @@ pub trait CpuStateManager: Clone {
|
||||
// Must not write to a read-only segment.
|
||||
if segment_type_ro(segment_type) && write {
|
||||
return Err(PlatformError::InvalidAddress(anyhow!(
|
||||
"Can not write to a read-only segment"
|
||||
"Cannot write to a read-only segment"
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -532,11 +532,17 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
||||
(mov, Mov_moffs64_RAX),
|
||||
(mov, Mov_RAX_moffs64),
|
||||
// MOVS
|
||||
(movs, Movsq_m64_m64),
|
||||
(movs, Movsd_m32_m32),
|
||||
(movs, Movsw_m16_m16),
|
||||
(movs, Movsb_m8_m8),
|
||||
// OR
|
||||
(or, Or_rm8_r8)
|
||||
(or, Or_rm8_r8),
|
||||
// STOS
|
||||
(stos, Stosb_m8_AL),
|
||||
(stos, Stosw_m16_AX),
|
||||
(stos, Stosd_m32_EAX),
|
||||
(stos, Stosq_m64_RAX)
|
||||
);
|
||||
|
||||
handler
|
||||
@@ -561,7 +567,7 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
||||
|
||||
decoder.set_ip(state.ip());
|
||||
|
||||
while decoder.can_decode() && !stop_emulation {
|
||||
while !stop_emulation {
|
||||
decoder.decode_out(&mut insn);
|
||||
|
||||
if decoder.last_error() == DecoderError::NoMoreBytes {
|
||||
@@ -588,6 +594,7 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
||||
// Once we have the new stream, we must create a new decoder
|
||||
// and emulate one last instruction from the last decoded IP.
|
||||
decoder = Decoder::new(64, &fetched_insn_stream, DecoderOptions::NONE);
|
||||
decoder.set_ip(last_decoded_ip);
|
||||
decoder.decode_out(&mut insn);
|
||||
if decoder.last_error() != DecoderError::None {
|
||||
return Err(EmulationError::InstructionFetchingError(anyhow!(
|
||||
@@ -595,8 +602,6 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
||||
insn_format!(insn)
|
||||
)));
|
||||
}
|
||||
|
||||
stop_emulation = true;
|
||||
}
|
||||
|
||||
// Emulate the decoded instruction
|
||||
@@ -751,10 +756,6 @@ mod mock_vmm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gva_to_gpa(&self, gva: u64) -> Result<u64, PlatformError> {
|
||||
Ok(gva)
|
||||
}
|
||||
|
||||
fn fetch(&self, ip: u64, instruction_bytes: &mut [u8]) -> Result<(), PlatformError> {
|
||||
let rip = self
|
||||
.state
|
||||
@@ -771,12 +772,77 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::arch::x86::emulator::mock_vmm::*;
|
||||
|
||||
#[test]
|
||||
// Emulate executing an empty stream. Instructions should be fetched from
|
||||
// memory.
|
||||
//
|
||||
// mov rax, 0x1000
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
fn test_empty_instruction_stream() {
|
||||
let target_rax: u64 = 0x1000;
|
||||
let target_rbx: u64 = 0x1234567812345678;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
// Code at IP
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
// Padding
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // Padding is all zeroes
|
||||
// Data at IP + 0x10 (0x1234567812345678 in LE)
|
||||
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
|
||||
];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, vec![], Some((ip, &memory)));
|
||||
assert!(vmm.emulate_insn(cpu_id, &[], Some(2)).is_ok());
|
||||
|
||||
let rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, target_rax);
|
||||
|
||||
let rbx: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RBX)
|
||||
.unwrap();
|
||||
assert_eq!(rbx, target_rbx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Emulate executing an empty stream. Instructions should be fetched from
|
||||
// memory. The emulation should abort.
|
||||
//
|
||||
// mov rax, 0x1000
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
// ... garbage ...
|
||||
fn test_empty_instruction_stream_bad() {
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
// Code at IP
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
// Padding
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, // Garbage
|
||||
// Data at IP + 0x10 (0x1234567812345678 in LE)
|
||||
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
|
||||
];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, vec![], Some((ip, &memory)));
|
||||
assert!(vmm.emulate_insn(cpu_id, &[], None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Emulate truncated instruction stream, which should cause a fetch.
|
||||
//
|
||||
// mov rax, 0x1000
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
// Test with a first instruction truncated.
|
||||
fn test_fetch_first_instruction() {
|
||||
let target_rax: u64 = 0x1000;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
@@ -801,7 +867,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, ip);
|
||||
assert_eq!(rax, target_rax);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -839,6 +905,52 @@ mod tests {
|
||||
assert_eq!(rbx, target_rax);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Emulate only one instruction.
|
||||
//
|
||||
// mov rax, 0x1000
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
// The emulation should stop after the first instruction.
|
||||
fn test_emulate_one_instruction() {
|
||||
let target_rax: u64 = 0x1000;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
// Code at IP
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
// Padding
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // Padding is all zeroes
|
||||
// Data at IP + 0x10 (0x1234567812345678 in LE)
|
||||
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
|
||||
];
|
||||
let insn = [
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
];
|
||||
|
||||
let mut vmm = MockVmm::new(ip, vec![], Some((ip, &memory)));
|
||||
assert!(vmm.emulate_insn(cpu_id, &insn, Some(1)).is_ok());
|
||||
|
||||
let new_ip: u64 = vmm.cpu_state(cpu_id).unwrap().ip();
|
||||
assert_eq!(new_ip, ip + 0x7 /* length of mov rax,0x1000 */);
|
||||
|
||||
let rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, target_rax);
|
||||
|
||||
// The second instruction is not executed so RBX should be zero.
|
||||
let rbx: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RBX)
|
||||
.unwrap();
|
||||
assert_eq!(rbx, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Emulate truncated instruction stream, which should cause a fetch.
|
||||
//
|
||||
|
||||
@@ -53,7 +53,6 @@ pub enum Exception {
|
||||
pub mod regs;
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "with-serde", derive(Deserialize, Serialize))]
|
||||
pub struct SegmentRegister {
|
||||
pub base: u64,
|
||||
pub limit: u32,
|
||||
@@ -174,7 +173,6 @@ macro_rules! msr_data {
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "with-serde", derive(Deserialize, Serialize))]
|
||||
pub struct StandardRegisters {
|
||||
pub rax: u64,
|
||||
pub rbx: u64,
|
||||
@@ -197,14 +195,12 @@ pub struct StandardRegisters {
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "with-serde", derive(Deserialize, Serialize))]
|
||||
pub struct DescriptorTable {
|
||||
pub base: u64,
|
||||
pub limit: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "with-serde", derive(Deserialize, Serialize))]
|
||||
pub struct SpecialRegisters {
|
||||
pub cs: SegmentRegister,
|
||||
pub ds: SegmentRegister,
|
||||
|
||||
@@ -166,7 +166,7 @@ pub trait Hypervisor: Send + Sync {
|
||||
CpuVendor::AMD
|
||||
} else {
|
||||
// Not known yet, the corresponding manufacturer manual should contain the
|
||||
// necesssary info. See also https://wiki.osdev.org/CPUID#CPU_Vendor_ID_String
|
||||
// necessary info. See also https://wiki.osdev.org/CPUID#CPU_Vendor_ID_String
|
||||
CpuVendor::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,46 +77,61 @@ static VGIC_DIST_REGS: &[DistReg] = &[
|
||||
VGIC_DIST_REG!(GICD_IPRIORITYR, 8, 0),
|
||||
];
|
||||
|
||||
fn dist_attr_access(gic: &DeviceFd, offset: u32, val: &u32, set: bool) -> Result<()> {
|
||||
fn dist_attr_set(gic: &DeviceFd, offset: u32, val: u32) -> Result<()> {
|
||||
let gic_dist_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
|
||||
attr: offset as u64,
|
||||
addr: &val as *const u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
gic.set_device_attr(&gic_dist_attr).map_err(|e| {
|
||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dist_attr_get(gic: &DeviceFd, offset: u32) -> Result<u32> {
|
||||
let mut val = 0;
|
||||
|
||||
let mut gic_dist_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
|
||||
attr: offset as u64,
|
||||
addr: val as *const u32 as u64,
|
||||
addr: &mut val as *mut u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
if set {
|
||||
gic.set_device_attr(&gic_dist_attr).map_err(|e| {
|
||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
} else {
|
||||
gic.get_device_attr(&mut gic_dist_attr).map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
// get_device_attr should be marked as unsafe, and will be in future.
|
||||
// SAFETY: gic_dist_attr.addr is safe to write to.
|
||||
gic.get_device_attr(&mut gic_dist_attr).map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Get the distributor control register.
|
||||
pub fn read_ctlr(gic: &DeviceFd) -> Result<u32> {
|
||||
let val: u32 = 0;
|
||||
dist_attr_access(gic, GICD_CTLR, &val, false)?;
|
||||
Ok(val)
|
||||
dist_attr_get(gic, GICD_CTLR)
|
||||
}
|
||||
|
||||
/// Set the distributor control register.
|
||||
pub fn write_ctlr(gic: &DeviceFd, val: u32) -> Result<()> {
|
||||
dist_attr_access(gic, GICD_CTLR, &val, true)
|
||||
dist_attr_set(gic, GICD_CTLR, val)
|
||||
}
|
||||
|
||||
fn get_interrupts_num(gic: &DeviceFd) -> Result<u32> {
|
||||
let num_irq = 0;
|
||||
let mut num_irq = 0;
|
||||
|
||||
let mut nr_irqs_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
|
||||
attr: 0,
|
||||
addr: &num_irq as *const u32 as u64,
|
||||
addr: &mut num_irq as *mut u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
// get_device_attr should be marked as unsafe, and will be in future.
|
||||
// SAFETY: nr_irqs_attr.addr is safe to write to.
|
||||
gic.get_device_attr(&mut nr_irqs_attr).map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
@@ -158,8 +173,7 @@ pub fn set_dist_regs(gic: &DeviceFd, state: &[u32]) -> Result<()> {
|
||||
let end = compute_reg_len(gic, dreg, base)?;
|
||||
|
||||
while base < end {
|
||||
let val = state[idx];
|
||||
dist_attr_access(gic, base, &val, true)?;
|
||||
dist_attr_set(gic, base, state[idx])?;
|
||||
idx += 1;
|
||||
base += REG_SIZE as u32;
|
||||
}
|
||||
@@ -175,9 +189,7 @@ pub fn get_dist_regs(gic: &DeviceFd) -> Result<Vec<u32>> {
|
||||
let end = compute_reg_len(gic, dreg, base)?;
|
||||
|
||||
while base < end {
|
||||
let val: u32 = 0;
|
||||
dist_attr_access(gic, base, &val, false)?;
|
||||
state.push(val);
|
||||
state.push(dist_attr_get(gic, base)?);
|
||||
base += REG_SIZE as u32;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,23 +79,38 @@ static VGIC_ICC_REGS: &[u64] = &[
|
||||
SYS_ICC_AP1R3_EL1,
|
||||
];
|
||||
|
||||
fn icc_attr_access(gic: &DeviceFd, offset: u64, typer: u64, val: &u32, set: bool) -> Result<()> {
|
||||
fn icc_attr_set(gic: &DeviceFd, offset: u64, typer: u64, val: u32) -> Result<()> {
|
||||
let gic_icc_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
|
||||
attr: ((typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | offset), // this needs the mpidr
|
||||
addr: &val as *const u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
gic.set_device_attr(&gic_icc_attr).map_err(|e| {
|
||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn icc_attr_get(gic: &DeviceFd, offset: u64, typer: u64) -> Result<u32> {
|
||||
let mut val = 0;
|
||||
|
||||
let mut gic_icc_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
|
||||
attr: ((typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | offset), // this needs the mpidr
|
||||
addr: val as *const u32 as u64,
|
||||
addr: &mut val as *mut u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
if set {
|
||||
gic.set_device_attr(&gic_icc_attr).map_err(|e| {
|
||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
} else {
|
||||
gic.get_device_attr(&mut gic_icc_attr).map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
// get_device_attr should be marked as unsafe, and will be in future.
|
||||
// SAFETY: gic_icc_attr.addr is safe to write to.
|
||||
gic.get_device_attr(&mut gic_icc_attr).map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Get ICC registers.
|
||||
@@ -107,10 +122,9 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
||||
for ix in gicr_typer {
|
||||
let i = *ix;
|
||||
for icc_offset in VGIC_ICC_REGS {
|
||||
let val = 0;
|
||||
if *icc_offset == SYS_ICC_CTLR_EL1 {
|
||||
// calculate priority bits by reading the ctrl_el1 register.
|
||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
||||
let val = icc_attr_get(gic, *icc_offset, i)?;
|
||||
// The priority bits are found in the ICC_CTLR_EL1 register (bits from 10:8).
|
||||
// See page 194 from https://static.docs.arm.com/ihi0069/c/IHI0069C_gic_
|
||||
// architecture_specification.pdf.
|
||||
@@ -130,8 +144,7 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
||||
// 7 bits of priority.
|
||||
else if *icc_offset == SYS_ICC_AP0R1_EL1 || *icc_offset == SYS_ICC_AP1R1_EL1 {
|
||||
if num_priority_bits >= 6 {
|
||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
||||
state.push(val);
|
||||
state.push(icc_attr_get(gic, *icc_offset, i)?);
|
||||
}
|
||||
} else if *icc_offset == SYS_ICC_AP0R2_EL1
|
||||
|| *icc_offset == SYS_ICC_AP0R3_EL1
|
||||
@@ -139,12 +152,10 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
||||
|| *icc_offset == SYS_ICC_AP1R3_EL1
|
||||
{
|
||||
if num_priority_bits == 7 {
|
||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
||||
state.push(val);
|
||||
state.push(icc_attr_get(gic, *icc_offset, i)?);
|
||||
}
|
||||
} else {
|
||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
||||
state.push(val);
|
||||
state.push(icc_attr_get(gic, *icc_offset, i)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,7 +176,7 @@ pub fn set_icc_regs(gic: &DeviceFd, gicr_typer: &[u64], state: &[u32]) -> Result
|
||||
}
|
||||
if *icc_offset == SYS_ICC_AP0R1_EL1 || *icc_offset == SYS_ICC_AP1R1_EL1 {
|
||||
if num_priority_bits >= 6 {
|
||||
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
|
||||
icc_attr_set(gic, *icc_offset, i, state[idx])?;
|
||||
idx += 1;
|
||||
}
|
||||
continue;
|
||||
@@ -176,12 +187,12 @@ pub fn set_icc_regs(gic: &DeviceFd, gicr_typer: &[u64], state: &[u32]) -> Result
|
||||
|| *icc_offset == SYS_ICC_AP1R3_EL1
|
||||
{
|
||||
if num_priority_bits == 7 {
|
||||
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
|
||||
icc_attr_set(gic, *icc_offset, i, state[idx])?;
|
||||
idx += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
|
||||
icc_attr_set(gic, *icc_offset, i, state[idx])?;
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,34 +24,38 @@ const GITS_CWRITER: u32 = 0x0088;
|
||||
const GITS_CREADR: u32 = 0x0090;
|
||||
const GITS_BASER: u32 = 0x0100;
|
||||
|
||||
/// Access an ITS device attribute.
|
||||
///
|
||||
/// This is a helper function to get/set the ITS device attribute depending
|
||||
/// the bool parameter `set` provided.
|
||||
pub fn gicv3_its_attr_access(
|
||||
its_device: &DeviceFd,
|
||||
group: u32,
|
||||
attr: u32,
|
||||
val: &u64,
|
||||
set: bool,
|
||||
) -> Result<()> {
|
||||
fn gicv3_its_attr_set(its_device: &DeviceFd, group: u32, attr: u32, val: u64) -> Result<()> {
|
||||
let gicv3_its_attr = kvm_bindings::kvm_device_attr {
|
||||
group,
|
||||
attr: attr as u64,
|
||||
addr: &val as *const u64 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
its_device
|
||||
.set_device_attr(&gicv3_its_attr)
|
||||
.map_err(|e| Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into())))
|
||||
}
|
||||
|
||||
fn gicv3_its_attr_get(its_device: &DeviceFd, group: u32, attr: u32) -> Result<u64> {
|
||||
let mut val = 0;
|
||||
|
||||
let mut gicv3_its_attr = kvm_bindings::kvm_device_attr {
|
||||
group,
|
||||
attr: attr as u64,
|
||||
addr: val as *const u64 as u64,
|
||||
addr: &mut val as *mut u64 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
if set {
|
||||
its_device.set_device_attr(&gicv3_its_attr).map_err(|e| {
|
||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
})
|
||||
} else {
|
||||
its_device
|
||||
.get_device_attr(&mut gicv3_its_attr)
|
||||
.map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})
|
||||
}
|
||||
|
||||
// get_device_attr should be marked as unsafe, and will be in future.
|
||||
// SAFETY: gicv3_its_attr.addr is safe to write to.
|
||||
its_device
|
||||
.get_device_attr(&mut gicv3_its_attr)
|
||||
.map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Function that saves/restores ITS tables into guest RAM.
|
||||
@@ -324,60 +328,43 @@ impl Vgic for KvmGicV3Its {
|
||||
|
||||
let icc_state = get_icc_regs(&self.device, &gicr_typers)?;
|
||||
|
||||
let its_baser_state: [u64; 8] = [0; 8];
|
||||
let mut its_baser_state: [u64; 8] = [0; 8];
|
||||
for i in 0..8 {
|
||||
gicv3_its_attr_access(
|
||||
its_baser_state[i as usize] = gicv3_its_attr_get(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_BASER + i * 8,
|
||||
&its_baser_state[i as usize],
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
|
||||
let its_ctlr_state: u64 = 0;
|
||||
gicv3_its_attr_access(
|
||||
let its_ctlr_state = gicv3_its_attr_get(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CTLR,
|
||||
&its_ctlr_state,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let its_cbaser_state: u64 = 0;
|
||||
gicv3_its_attr_access(
|
||||
let its_cbaser_state = gicv3_its_attr_get(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CBASER,
|
||||
&its_cbaser_state,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let its_creadr_state: u64 = 0;
|
||||
gicv3_its_attr_access(
|
||||
let its_creadr_state = gicv3_its_attr_get(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CREADR,
|
||||
&its_creadr_state,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let its_cwriter_state: u64 = 0;
|
||||
gicv3_its_attr_access(
|
||||
let its_cwriter_state = gicv3_its_attr_get(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CWRITER,
|
||||
&its_cwriter_state,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let its_iidr_state: u64 = 0;
|
||||
gicv3_its_attr_access(
|
||||
let its_iidr_state = gicv3_its_attr_get(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_IIDR,
|
||||
&its_iidr_state,
|
||||
false,
|
||||
)?;
|
||||
|
||||
Ok(Gicv3ItsState {
|
||||
@@ -407,57 +394,51 @@ impl Vgic for KvmGicV3Its {
|
||||
set_icc_regs(&self.device, &gicr_typers, &state.icc)?;
|
||||
|
||||
//Restore GICv3ITS registers
|
||||
gicv3_its_attr_access(
|
||||
gicv3_its_attr_set(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_IIDR,
|
||||
&state.its_iidr,
|
||||
true,
|
||||
state.its_iidr,
|
||||
)?;
|
||||
|
||||
gicv3_its_attr_access(
|
||||
gicv3_its_attr_set(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CBASER,
|
||||
&state.its_cbaser,
|
||||
true,
|
||||
state.its_cbaser,
|
||||
)?;
|
||||
|
||||
gicv3_its_attr_access(
|
||||
gicv3_its_attr_set(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CREADR,
|
||||
&state.its_creadr,
|
||||
true,
|
||||
state.its_creadr,
|
||||
)?;
|
||||
|
||||
gicv3_its_attr_access(
|
||||
gicv3_its_attr_set(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CWRITER,
|
||||
&state.its_cwriter,
|
||||
true,
|
||||
state.its_cwriter,
|
||||
)?;
|
||||
|
||||
for i in 0..8 {
|
||||
gicv3_its_attr_access(
|
||||
gicv3_its_attr_set(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_BASER + i * 8,
|
||||
&state.its_baser[i as usize],
|
||||
true,
|
||||
state.its_baser[i as usize],
|
||||
)?;
|
||||
}
|
||||
|
||||
// Restore ITS tables
|
||||
gicv3_its_tables_access(self.its_device.as_ref().unwrap(), false)?;
|
||||
|
||||
gicv3_its_attr_access(
|
||||
gicv3_its_attr_set(
|
||||
self.its_device.as_ref().unwrap(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||
GITS_CTLR,
|
||||
&state.its_ctlr,
|
||||
true,
|
||||
state.its_ctlr,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ const KVM_ARM64_SYSREG_MPIDR_EL1: u64 = KVM_REG_ARM64
|
||||
| (((5_u64) << KVM_REG_ARM64_SYSREG_OP2_SHIFT) & KVM_REG_ARM64_SYSREG_OP2_MASK as u64);
|
||||
|
||||
/// This is how we represent the registers of a distributor.
|
||||
/// It is relrvant their offset from the base address of the
|
||||
/// It is relevant their offset from the base address of the
|
||||
/// distributor.
|
||||
/// Each register has a different number
|
||||
/// of bits_per_irq and is therefore variable length.
|
||||
@@ -96,23 +96,38 @@ static VGIC_SGI_REGS: &[RdistReg] = &[
|
||||
VGIC_RDIST_REG!(GICR_IPRIORITYR0, 32),
|
||||
];
|
||||
|
||||
fn redist_attr_access(gic: &DeviceFd, offset: u32, typer: u64, val: &u32, set: bool) -> Result<()> {
|
||||
fn redist_attr_set(gic: &DeviceFd, offset: u32, typer: u64, val: u32) -> Result<()> {
|
||||
let gic_redist_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_REDIST_REGS,
|
||||
attr: (typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | (offset as u64), // this needs the mpidr
|
||||
addr: &val as *const u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
gic.set_device_attr(&gic_redist_attr).map_err(|e| {
|
||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn redist_attr_get(gic: &DeviceFd, offset: u32, typer: u64) -> Result<u32> {
|
||||
let mut val = 0;
|
||||
|
||||
let mut gic_redist_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_REDIST_REGS,
|
||||
attr: (typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | (offset as u64), // this needs the mpidr
|
||||
addr: val as *const u32 as u64,
|
||||
addr: &mut val as *mut u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
if set {
|
||||
gic.set_device_attr(&gic_redist_attr).map_err(|e| {
|
||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
} else {
|
||||
gic.get_device_attr(&mut gic_redist_attr).map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
// get_device_attr should be marked as unsafe, and will be in future.
|
||||
// SAFETY: gic_redist_attr.addr is safe to write to.
|
||||
gic.get_device_attr(&mut gic_redist_attr).map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
fn access_redists_aux(
|
||||
@@ -129,14 +144,11 @@ fn access_redists_aux(
|
||||
let end = base + rdreg.length as u32;
|
||||
|
||||
while base < end {
|
||||
let mut val = 0;
|
||||
if set {
|
||||
val = state[*idx];
|
||||
redist_attr_access(gic, base, *i, &val, true)?;
|
||||
redist_attr_set(gic, base, *i, state[*idx])?;
|
||||
*idx += 1;
|
||||
} else {
|
||||
redist_attr_access(gic, base, *i, &val, false)?;
|
||||
state.push(val);
|
||||
state.push(redist_attr_get(gic, base, *i)?);
|
||||
}
|
||||
base += REG_SIZE as u32;
|
||||
}
|
||||
|
||||
@@ -106,12 +106,23 @@ pub fn is_system_register(regid: u64) -> bool {
|
||||
}
|
||||
|
||||
pub fn check_required_kvm_extensions(kvm: &Kvm) -> KvmResult<()> {
|
||||
if !kvm.check_extension(Cap::SignalMsi) {
|
||||
return Err(KvmError::CapabilityMissing(Cap::SignalMsi));
|
||||
}
|
||||
if !kvm.check_extension(Cap::OneReg) {
|
||||
return Err(KvmError::CapabilityMissing(Cap::OneReg));
|
||||
macro_rules! check_extension {
|
||||
($cap:expr) => {
|
||||
if !kvm.check_extension($cap) {
|
||||
return Err(KvmError::CapabilityMissing($cap));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// SetGuestDebug is required but some kernels have it implemented without the capability flag.
|
||||
check_extension!(Cap::ImmediateExit);
|
||||
check_extension!(Cap::Ioeventfd);
|
||||
check_extension!(Cap::Irqchip);
|
||||
check_extension!(Cap::Irqfd);
|
||||
check_extension!(Cap::IrqRouting);
|
||||
check_extension!(Cap::MpState);
|
||||
check_extension!(Cap::OneReg);
|
||||
check_extension!(Cap::UserMemory);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -535,9 +535,9 @@ impl vm::Vm for KvmVm {
|
||||
|
||||
if self.check_extension(crate::kvm::Cap::MsiDevid) {
|
||||
// On AArch64, there is limitation on the range of the 'devid',
|
||||
// it can not be greater than 65536 (the max of u16).
|
||||
// it cannot be greater than 65536 (the max of u16).
|
||||
//
|
||||
// BDF can not be used directly, because 'segment' is in high
|
||||
// BDF cannot be used directly, because 'segment' is in high
|
||||
// 16 bits. The layout of the u32 BDF is:
|
||||
// |---- 16 bits ----|-- 8 bits --|-- 5 bits --|-- 3 bits --|
|
||||
// | segment | bus | device | function |
|
||||
@@ -1505,7 +1505,7 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Set the floating point state (FPU) of a vCPU using the `KVM_SET_FPU` ioct.
|
||||
/// Set the floating point state (FPU) of a vCPU using the `KVM_SET_FPU` ioctl.
|
||||
///
|
||||
fn set_fpu(&self, fpu: &FpuState) -> cpu::Result<()> {
|
||||
let fpu: kvm_bindings::kvm_fpu = (*fpu).clone().into();
|
||||
@@ -1986,7 +1986,7 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
/// SREGS saves/restores a pending interrupt, similar to what
|
||||
/// VCPU_EVENTS also does.
|
||||
///
|
||||
/// GET_MSRS requires a pre-populated data structure to do something
|
||||
/// GET_MSRS requires a prepopulated data structure to do something
|
||||
/// meaningful. For SET_MSRS it will then contain good data.
|
||||
///
|
||||
/// # Example
|
||||
|
||||
@@ -32,29 +32,37 @@ pub use {
|
||||
/// Check KVM extension for Linux
|
||||
///
|
||||
pub fn check_required_kvm_extensions(kvm: &Kvm) -> KvmResult<()> {
|
||||
if !kvm.check_extension(Cap::SignalMsi) {
|
||||
return Err(KvmError::CapabilityMissing(Cap::SignalMsi));
|
||||
}
|
||||
if !kvm.check_extension(Cap::TscDeadlineTimer) {
|
||||
return Err(KvmError::CapabilityMissing(Cap::TscDeadlineTimer));
|
||||
}
|
||||
if !kvm.check_extension(Cap::SplitIrqchip) {
|
||||
return Err(KvmError::CapabilityMissing(Cap::SplitIrqchip));
|
||||
}
|
||||
if !kvm.check_extension(Cap::SetIdentityMapAddr) {
|
||||
return Err(KvmError::CapabilityMissing(Cap::SetIdentityMapAddr));
|
||||
}
|
||||
if !kvm.check_extension(Cap::SetTssAddr) {
|
||||
return Err(KvmError::CapabilityMissing(Cap::SetTssAddr));
|
||||
}
|
||||
if !kvm.check_extension(Cap::ImmediateExit) {
|
||||
return Err(KvmError::CapabilityMissing(Cap::ImmediateExit));
|
||||
}
|
||||
if !kvm.check_extension(Cap::GetTscKhz) {
|
||||
return Err(KvmError::CapabilityMissing(Cap::GetTscKhz));
|
||||
macro_rules! check_extension {
|
||||
($cap:expr) => {
|
||||
if !kvm.check_extension($cap) {
|
||||
return Err(KvmError::CapabilityMissing($cap));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// DeviceCtrl, EnableCap, and SetGuestDebug are also required, but some kernels have
|
||||
// the features implemented without the capability flags.
|
||||
check_extension!(Cap::AdjustClock);
|
||||
check_extension!(Cap::ExtCpuid);
|
||||
check_extension!(Cap::GetTscKhz);
|
||||
check_extension!(Cap::ImmediateExit);
|
||||
check_extension!(Cap::Ioeventfd);
|
||||
check_extension!(Cap::Irqchip);
|
||||
check_extension!(Cap::Irqfd);
|
||||
check_extension!(Cap::IrqRouting);
|
||||
check_extension!(Cap::MpState);
|
||||
check_extension!(Cap::SetIdentityMapAddr);
|
||||
check_extension!(Cap::SetTssAddr);
|
||||
check_extension!(Cap::SplitIrqchip);
|
||||
check_extension!(Cap::TscDeadlineTimer);
|
||||
check_extension!(Cap::UserMemory);
|
||||
check_extension!(Cap::UserNmi);
|
||||
check_extension!(Cap::VcpuEvents);
|
||||
check_extension!(Cap::Xcrs);
|
||||
check_extension!(Cap::Xsave);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct VcpuKvmState {
|
||||
pub cpuid: Vec<CpuIdEntry>,
|
||||
|
||||
@@ -165,7 +165,7 @@ pub enum ClockData {
|
||||
#[cfg(feature = "kvm")]
|
||||
Kvm(kvm_bindings::kvm_clock_data),
|
||||
#[cfg(feature = "mshv")]
|
||||
Mshv, /* MSHV does not support ClockData yet */
|
||||
Mshv(mshv::MshvClockData),
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
|
||||
use crate::arch::emulator::{PlatformEmulator, PlatformError};
|
||||
|
||||
use crate::arch::emulator::PlatformEmulator;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::x86::emulator::{Emulator, EmulatorCpuState};
|
||||
use crate::arch::x86::emulator::Emulator;
|
||||
use crate::cpu;
|
||||
use crate::cpu::Vcpu;
|
||||
use crate::hypervisor;
|
||||
use crate::mshv::emulator::MshvEmulatorContext;
|
||||
use crate::vec_with_array_field;
|
||||
use crate::vm::{self, InterruptSourceConfig, VmOps};
|
||||
use crate::HypervisorType;
|
||||
@@ -20,18 +19,15 @@ use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use vfio_ioctls::VfioDeviceFd;
|
||||
use vm::DataMatch;
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
mod snp_constants;
|
||||
// x86_64 dependencies
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod x86_64;
|
||||
#[cfg(feature = "sev_snp")]
|
||||
use snp_constants::*;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::x86::{CpuIdEntry, FpuState, MsrEntry};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::ClockData;
|
||||
|
||||
use crate::{
|
||||
CpuState, IoEventAddress, IrqRoutingEntry, MpState, UserMemoryRegion,
|
||||
USER_MEMORY_REGION_ADJUSTABLE, USER_MEMORY_REGION_EXECUTE, USER_MEMORY_REGION_READ,
|
||||
@@ -39,18 +35,16 @@ use crate::{
|
||||
};
|
||||
#[cfg(feature = "sev_snp")]
|
||||
use igvm_defs::IGVM_VHS_SNP_ID_BLOCK;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::VcpuMshvState;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::*;
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
use snp_constants::*;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::x86::{CpuIdEntry, FpuState, MsrEntry};
|
||||
pub use x86_64::*;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::{emulator, VcpuMshvState};
|
||||
|
||||
const DIRTY_BITMAP_CLEAR_DIRTY: u64 = 0x4;
|
||||
const DIRTY_BITMAP_SET_DIRTY: u64 = 0x8;
|
||||
@@ -60,7 +54,7 @@ const DIRTY_BITMAP_SET_DIRTY: u64 = 0x8;
|
||||
///
|
||||
pub use {
|
||||
mshv_bindings::mshv_create_device as CreateDevice,
|
||||
mshv_bindings::mshv_device_attr as DeviceAttr, mshv_ioctls::DeviceFd,
|
||||
mshv_bindings::mshv_device_attr as DeviceAttr, mshv_ioctls, mshv_ioctls::DeviceFd,
|
||||
};
|
||||
|
||||
pub const PAGE_SHIFT: usize = 12;
|
||||
@@ -92,6 +86,25 @@ impl From<mshv_user_mem_region> for UserMemoryRegion {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
impl From<MshvClockData> for ClockData {
|
||||
fn from(d: MshvClockData) -> Self {
|
||||
ClockData::Mshv(d)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
impl From<ClockData> for MshvClockData {
|
||||
fn from(ms: ClockData) -> Self {
|
||||
match ms {
|
||||
ClockData::Mshv(s) => s,
|
||||
/* Needed in case other hypervisors are enabled */
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => unreachable!("MSHV clock data is not valid"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UserMemoryRegion> for mshv_user_mem_region {
|
||||
fn from(region: UserMemoryRegion) -> Self {
|
||||
let mut flags: u32 = 0;
|
||||
@@ -254,6 +267,7 @@ impl hypervisor::Hypervisor for MshvHypervisor {
|
||||
}
|
||||
|
||||
// Set additional partition property for SEV-SNP partition.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
if mshv_vm_type == VmType::Snp {
|
||||
let snp_policy = snp::get_default_snp_guest_policy();
|
||||
let vmgexit_offloads = snp::get_default_vmgexit_offload_features();
|
||||
@@ -285,12 +299,20 @@ impl hypervisor::Hypervisor for MshvHypervisor {
|
||||
// to override this behavior with a more suitable option i.e., ignore
|
||||
// writes from the guest and return zero in attempt to read unimplemented
|
||||
// MSR.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fd.set_partition_property(
|
||||
hv_partition_property_code_HV_PARTITION_PROPERTY_UNIMPLEMENTED_MSR_ACTION,
|
||||
hv_unimplemented_msr_action_HV_UNIMPLEMENTED_MSR_ACTION_IGNORE_WRITE_READ_ZERO as u64,
|
||||
)
|
||||
.map_err(|e| hypervisor::HypervisorError::SetPartitionProperty(e.into()))?;
|
||||
|
||||
// Always create a frozen partition
|
||||
fd.set_partition_property(
|
||||
hv_partition_property_code_HV_PARTITION_PROPERTY_TIME_FREEZE,
|
||||
1u64,
|
||||
)
|
||||
.map_err(|e| hypervisor::HypervisorError::SetPartitionProperty(e.into()))?;
|
||||
|
||||
let vm_fd = Arc::new(fd);
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -346,7 +368,16 @@ impl hypervisor::Hypervisor for MshvHypervisor {
|
||||
/// Get the supported CpuID
|
||||
///
|
||||
fn get_supported_cpuid(&self) -> hypervisor::Result<Vec<CpuIdEntry>> {
|
||||
Ok(Vec::new())
|
||||
let mut cpuid = Vec::new();
|
||||
let functions: [u32; 2] = [0x1, 0xb];
|
||||
|
||||
for function in functions {
|
||||
cpuid.push(CpuIdEntry {
|
||||
function,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
Ok(cpuid)
|
||||
}
|
||||
|
||||
/// Get maximum number of vCPUs
|
||||
@@ -600,14 +631,18 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
Ok(cpu::VmExit::Ignore)
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
hv_message_type_HVMSG_UNMAPPED_GPA => {
|
||||
msg_type @ (hv_message_type_HVMSG_UNMAPPED_GPA
|
||||
| hv_message_type_HVMSG_GPA_INTERCEPT) => {
|
||||
let info = x.to_memory_info().unwrap();
|
||||
let insn_len = info.instruction_byte_count as usize;
|
||||
assert!(insn_len > 0 && insn_len <= 16);
|
||||
let gva = info.guest_virtual_address;
|
||||
let gpa = info.guest_physical_address;
|
||||
|
||||
debug!("Exit ({:?}) GVA {:x} GPA {:x}", msg_type, gva, gpa);
|
||||
|
||||
let mut context = MshvEmulatorContext {
|
||||
vcpu: self,
|
||||
map: (info.guest_virtual_address, info.guest_physical_address),
|
||||
map: (gva, gpa),
|
||||
};
|
||||
|
||||
// Create a new emulator.
|
||||
@@ -615,7 +650,10 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
|
||||
// Emulate the trapped instruction, and only the first one.
|
||||
let new_state = emul
|
||||
.emulate_first_insn(self.vp_index as usize, &info.instruction_bytes)
|
||||
.emulate_first_insn(
|
||||
self.vp_index as usize,
|
||||
&info.instruction_bytes[..insn_len],
|
||||
)
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
|
||||
|
||||
// Set CPU state back.
|
||||
@@ -678,6 +716,7 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
)))?;
|
||||
Ok(cpu::VmExit::Ignore)
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
hv_message_type_HVMSG_UNACCEPTED_GPA => {
|
||||
let info = x.to_memory_info().unwrap();
|
||||
let gva = info.guest_virtual_address;
|
||||
@@ -689,11 +728,13 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
gva,
|
||||
)))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
hv_message_type_HVMSG_X64_CPUID_INTERCEPT => {
|
||||
let info = x.to_cpuid_info().unwrap();
|
||||
debug!("cpuid eax: {:x}", { info.rax });
|
||||
Ok(cpu::VmExit::Ignore)
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
hv_message_type_HVMSG_X64_MSR_INTERCEPT => {
|
||||
let info = x.to_msr_info().unwrap();
|
||||
if info.header.intercept_access_type == 0 {
|
||||
@@ -703,6 +744,7 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
}
|
||||
Ok(cpu::VmExit::Ignore)
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
hv_message_type_HVMSG_X64_EXCEPTION_INTERCEPT => {
|
||||
//TODO: Handler for VMCALL here.
|
||||
let info = x.to_exception_info().unwrap();
|
||||
@@ -757,6 +799,10 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
}
|
||||
GHCB_INFO_REGISTER_REQUEST => {
|
||||
let mut ghcb_gpa = hv_x64_register_sev_ghcb::default();
|
||||
|
||||
// Disable the previously used GHCB page.
|
||||
self.disable_prev_ghcb_page()?;
|
||||
|
||||
// SAFETY: Accessing a union element from bindgen generated bindings.
|
||||
unsafe {
|
||||
ghcb_gpa.__bindgen_anon_1.set_enabled(1);
|
||||
@@ -784,6 +830,7 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
resp_ghcb_msr.__bindgen_anon_2.set_gpa_page_number(
|
||||
ghcb_msr.__bindgen_anon_2.gpa_page_number(),
|
||||
);
|
||||
debug!("GHCB GPA is {:x}", ghcb_gpa.as_uint64);
|
||||
}
|
||||
// SAFETY: Accessing a union element from bindgen generated bindings.
|
||||
let reg_name_value = unsafe {
|
||||
@@ -884,15 +931,7 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
)?;
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
let mut swei1_rw_gpa_arg =
|
||||
mshv_bindings::mshv_read_write_gpa {
|
||||
base_gpa: ghcb_gpa + GHCB_SW_EXITINFO1_OFFSET,
|
||||
byte_count: std::mem::size_of::<u64>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
self.fd.gpa_write(&mut swei1_rw_gpa_arg).map_err(
|
||||
|e| cpu::HypervisorCpuError::GpaWrite(e.into()),
|
||||
)?;
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
}
|
||||
SVM_NAE_HV_DOORBELL_PAGE_QUERY => {
|
||||
let mut reg_assocs = [ hv_register_assoc {
|
||||
@@ -914,6 +953,9 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
self.fd.gpa_write(&mut swei2_rw_gpa_arg).map_err(
|
||||
|e| cpu::HypervisorCpuError::GpaWrite(e.into()),
|
||||
)?;
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
}
|
||||
SVM_NAE_HV_DOORBELL_PAGE_CLEAR => {
|
||||
let mut swei2_rw_gpa_arg =
|
||||
@@ -1014,14 +1056,7 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
}
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
let mut swei1_rw_gpa_arg = mshv_bindings::mshv_read_write_gpa {
|
||||
base_gpa: ghcb_gpa + GHCB_SW_EXITINFO1_OFFSET,
|
||||
byte_count: std::mem::size_of::<u64>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
self.fd
|
||||
.gpa_write(&mut swei1_rw_gpa_arg)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GpaWrite(e.into()))?;
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
}
|
||||
SVM_EXITCODE_MMIO_READ => {
|
||||
let src_gpa =
|
||||
@@ -1050,6 +1085,9 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
self.fd
|
||||
.gpa_write(&mut arg)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GpaWrite(e.into()))?;
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
}
|
||||
SVM_EXITCODE_MMIO_WRITE => {
|
||||
let dst_gpa =
|
||||
@@ -1078,6 +1116,9 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
cpu::HypervisorCpuError::RunVcpu(e.into())
|
||||
})?;
|
||||
}
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
}
|
||||
SVM_EXITCODE_SNP_GUEST_REQUEST => {
|
||||
let req_gpa =
|
||||
@@ -1123,15 +1164,8 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
.sev_snp_ap_create(&mshv_ap_create_req)
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
|
||||
|
||||
let mut swei1_rw_gpa_arg = mshv_bindings::mshv_read_write_gpa {
|
||||
base_gpa: ghcb_gpa + GHCB_SW_EXITINFO1_OFFSET,
|
||||
byte_count: std::mem::size_of::<u64>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.fd
|
||||
.gpa_write(&mut swei1_rw_gpa_arg)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GpaWrite(e.into()))?;
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
}
|
||||
_ => panic!(
|
||||
"GHCB_INFO_NORMAL: Unhandled exit code: {:0x}",
|
||||
@@ -1288,7 +1322,7 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
self.set_fpu(&state.fpu)?;
|
||||
self.set_xcrs(&state.xcrs)?;
|
||||
// These registers are global and needed to be set only for first VCPU
|
||||
// as Microsoft Hypervisor allows setting this regsier for only one VCPU
|
||||
// as Microsoft Hypervisor allows setting this register for only one VCPU
|
||||
if self.vp_index == 0 {
|
||||
self.fd
|
||||
.set_misc_regs(&state.misc)
|
||||
@@ -1427,6 +1461,42 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
}
|
||||
|
||||
impl MshvVcpu {
|
||||
///
|
||||
/// Deactivate previously used GHCB page.
|
||||
///
|
||||
#[cfg(feature = "sev_snp")]
|
||||
fn disable_prev_ghcb_page(&self) -> cpu::Result<()> {
|
||||
let mut reg_assocs = [hv_register_assoc {
|
||||
name: hv_register_name_HV_X64_REGISTER_SEV_GHCB_GPA,
|
||||
..Default::default()
|
||||
}];
|
||||
self.fd.get_reg(&mut reg_assocs).unwrap();
|
||||
// SAFETY: Accessing a union element from bindgen generated bindings.
|
||||
let prev_ghcb_gpa = unsafe { reg_assocs[0].value.reg64 };
|
||||
|
||||
debug!("Prev GHCB GPA is {:x}", prev_ghcb_gpa);
|
||||
|
||||
let mut ghcb_gpa = hv_x64_register_sev_ghcb::default();
|
||||
|
||||
// SAFETY: Accessing a union element from bindgen generated bindings.
|
||||
unsafe {
|
||||
ghcb_gpa.__bindgen_anon_1.set_enabled(0);
|
||||
ghcb_gpa.__bindgen_anon_1.set_page_number(prev_ghcb_gpa);
|
||||
}
|
||||
|
||||
// SAFETY: Accessing a union element from bindgen generated bindings.
|
||||
let reg_name_value = unsafe {
|
||||
[(
|
||||
hv_register_name_HV_X64_REGISTER_SEV_GHCB_GPA,
|
||||
ghcb_gpa.as_uint64,
|
||||
)]
|
||||
};
|
||||
|
||||
set_registers_64!(self.fd, reg_name_value)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetRegister(e.into()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call that returns the vcpu's current "xcrs".
|
||||
@@ -1468,133 +1538,26 @@ impl MshvVcpu {
|
||||
.set_vcpu_events(events)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetVcpuEvents(e.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
struct MshvEmulatorContext<'a> {
|
||||
vcpu: &'a MshvVcpu,
|
||||
map: (u64, u64), // Initial GVA to GPA mapping provided by the hypervisor
|
||||
}
|
||||
///
|
||||
/// Clear SW_EXIT_INFO1 register for SEV-SNP guests.
|
||||
///
|
||||
#[cfg(feature = "sev_snp")]
|
||||
fn clear_swexit_info1(
|
||||
&self,
|
||||
ghcb_gpa: u64,
|
||||
) -> std::result::Result<cpu::VmExit, cpu::HypervisorCpuError> {
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
let mut swei1_rw_gpa_arg = mshv_bindings::mshv_read_write_gpa {
|
||||
base_gpa: ghcb_gpa + GHCB_SW_EXITINFO1_OFFSET,
|
||||
byte_count: std::mem::size_of::<u64>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
self.fd
|
||||
.gpa_write(&mut swei1_rw_gpa_arg)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GpaWrite(e.into()))?;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
impl<'a> MshvEmulatorContext<'a> {
|
||||
// Do the actual gva -> gpa translation
|
||||
#[allow(non_upper_case_globals)]
|
||||
fn translate(&self, gva: u64) -> Result<u64, PlatformError> {
|
||||
if self.map.0 == gva {
|
||||
return Ok(self.map.1);
|
||||
}
|
||||
|
||||
// TODO: More fine-grained control for the flags
|
||||
let flags = HV_TRANSLATE_GVA_VALIDATE_READ | HV_TRANSLATE_GVA_VALIDATE_WRITE;
|
||||
|
||||
let (gpa, result_code) = self
|
||||
.vcpu
|
||||
.translate_gva(gva, flags.into())
|
||||
.map_err(|e| PlatformError::TranslateVirtualAddress(anyhow!(e)))?;
|
||||
|
||||
match result_code {
|
||||
hv_translate_gva_result_code_HV_TRANSLATE_GVA_SUCCESS => Ok(gpa),
|
||||
_ => Err(PlatformError::TranslateVirtualAddress(anyhow!(result_code))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
/// Platform emulation for Hyper-V
|
||||
impl<'a> PlatformEmulator for MshvEmulatorContext<'a> {
|
||||
type CpuState = EmulatorCpuState;
|
||||
|
||||
fn read_memory(&self, gva: u64, data: &mut [u8]) -> Result<(), PlatformError> {
|
||||
let gpa = self.translate(gva)?;
|
||||
debug!(
|
||||
"mshv emulator: memory read {} bytes from [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gpa
|
||||
);
|
||||
|
||||
if let Some(vm_ops) = &self.vcpu.vm_ops {
|
||||
if vm_ops.guest_mem_read(gpa, data).is_err() {
|
||||
vm_ops
|
||||
.mmio_read(gpa, data)
|
||||
.map_err(|e| PlatformError::MemoryReadFailure(e.into()))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, gva: u64, data: &[u8]) -> Result<(), PlatformError> {
|
||||
let gpa = self.translate(gva)?;
|
||||
debug!(
|
||||
"mshv emulator: memory write {} bytes at [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gpa
|
||||
);
|
||||
|
||||
if let Some(vm_ops) = &self.vcpu.vm_ops {
|
||||
if vm_ops.guest_mem_write(gpa, data).is_err() {
|
||||
vm_ops
|
||||
.mmio_write(gpa, data)
|
||||
.map_err(|e| PlatformError::MemoryWriteFailure(e.into()))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cpu_state(&self, cpu_id: usize) -> Result<Self::CpuState, PlatformError> {
|
||||
if cpu_id != self.vcpu.vp_index as usize {
|
||||
return Err(PlatformError::GetCpuStateFailure(anyhow!(
|
||||
"CPU id mismatch {:?} {:?}",
|
||||
cpu_id,
|
||||
self.vcpu.vp_index
|
||||
)));
|
||||
}
|
||||
|
||||
let regs = self
|
||||
.vcpu
|
||||
.get_regs()
|
||||
.map_err(|e| PlatformError::GetCpuStateFailure(e.into()))?;
|
||||
let sregs = self
|
||||
.vcpu
|
||||
.get_sregs()
|
||||
.map_err(|e| PlatformError::GetCpuStateFailure(e.into()))?;
|
||||
|
||||
debug!("mshv emulator: Getting new CPU state");
|
||||
debug!("mshv emulator: {:#x?}", regs);
|
||||
|
||||
Ok(EmulatorCpuState { regs, sregs })
|
||||
}
|
||||
|
||||
fn set_cpu_state(&self, cpu_id: usize, state: Self::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: Setting new CPU state");
|
||||
debug!("mshv emulator: {:#x?}", state.regs);
|
||||
|
||||
self.vcpu
|
||||
.set_regs(&state.regs)
|
||||
.map_err(|e| PlatformError::SetCpuStateFailure(e.into()))?;
|
||||
self.vcpu
|
||||
.set_sregs(&state.sregs)
|
||||
.map_err(|e| PlatformError::SetCpuStateFailure(e.into()))
|
||||
}
|
||||
|
||||
fn gva_to_gpa(&self, gva: u64) -> Result<u64, PlatformError> {
|
||||
self.translate(gva)
|
||||
}
|
||||
|
||||
fn fetch(&self, _ip: u64, _instruction_bytes: &mut [u8]) -> Result<(), PlatformError> {
|
||||
Err(PlatformError::MemoryReadFailure(anyhow!("unimplemented")))
|
||||
Ok(cpu::VmExit::Ignore)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1930,13 +1893,23 @@ impl vm::Vm for MshvVm {
|
||||
/// Retrieve guest clock.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_clock(&self) -> vm::Result<ClockData> {
|
||||
Ok(ClockData::Mshv)
|
||||
let val = self
|
||||
.fd
|
||||
.get_partition_property(hv_partition_property_code_HV_PARTITION_PROPERTY_REFERENCE_TIME)
|
||||
.map_err(|e| vm::HypervisorVmError::GetClock(e.into()))?;
|
||||
Ok(MshvClockData { ref_time: val }.into())
|
||||
}
|
||||
|
||||
/// Set guest clock.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn set_clock(&self, _data: &ClockData) -> vm::Result<()> {
|
||||
Ok(())
|
||||
fn set_clock(&self, data: &ClockData) -> vm::Result<()> {
|
||||
let data: MshvClockData = (*data).into();
|
||||
self.fd
|
||||
.set_partition_property(
|
||||
hv_partition_property_code_HV_PARTITION_PROPERTY_REFERENCE_TIME,
|
||||
data.ref_time,
|
||||
)
|
||||
.map_err(|e| vm::HypervisorVmError::SetClock(e.into()))
|
||||
}
|
||||
|
||||
/// Downcast to the underlying MshvVm type
|
||||
@@ -2047,4 +2020,70 @@ impl vm::Vm for MshvVm {
|
||||
fn get_preferred_target(&self, kvi: &mut VcpuInit) -> vm::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Pause the VM
|
||||
fn pause(&self) -> vm::Result<()> {
|
||||
// Freeze the partition
|
||||
self.fd
|
||||
.set_partition_property(
|
||||
hv_partition_property_code_HV_PARTITION_PROPERTY_TIME_FREEZE,
|
||||
1u64,
|
||||
)
|
||||
.map_err(|e| {
|
||||
vm::HypervisorVmError::SetVmProperty(anyhow!(
|
||||
"Failed to set partition property: {}",
|
||||
e
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Resume the VM
|
||||
fn resume(&self) -> vm::Result<()> {
|
||||
// Resuming the partition using TIME_FREEZE property
|
||||
self.fd
|
||||
.set_partition_property(
|
||||
hv_partition_property_code_HV_PARTITION_PROPERTY_TIME_FREEZE,
|
||||
0u64,
|
||||
)
|
||||
.map_err(|e| {
|
||||
vm::HypervisorVmError::SetVmProperty(anyhow!(
|
||||
"Failed to set partition property: {}",
|
||||
e
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
fn gain_page_access(&self, gpa: u64, size: u32) -> vm::Result<()> {
|
||||
if !self.sev_snp_enabled {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let start_gpfn: u64 = gpa >> PAGE_SHIFT;
|
||||
let end_gpfn: u64 = (gpa + size as u64 - 1) >> PAGE_SHIFT;
|
||||
|
||||
let gpas: Vec<u64> = (start_gpfn..=end_gpfn).map(|x| x << PAGE_SHIFT).collect();
|
||||
|
||||
if !gpas.is_empty() {
|
||||
let mut gpa_list = vec_with_array_field::<mshv_modify_gpa_host_access, u64>(gpas.len());
|
||||
gpa_list[0].gpa_list_size = gpas.len() as u64;
|
||||
gpa_list[0].host_access = HV_MAP_GPA_READABLE | HV_MAP_GPA_WRITABLE;
|
||||
gpa_list[0].acquire = 1;
|
||||
gpa_list[0].flags = 0;
|
||||
|
||||
// SAFETY: gpa_list initialized with gpas.len() and now it is being turned into
|
||||
// gpas_slice with gpas.len() again. It is guaranteed to be large enough to hold
|
||||
// everything from gpas.
|
||||
unsafe {
|
||||
let gpas_slice: &mut [u64] = gpa_list[0].gpa_list.as_mut_slice(gpas.len());
|
||||
gpas_slice.copy_from_slice(gpas.as_slice());
|
||||
}
|
||||
|
||||
self.fd
|
||||
.modify_gpa_host_access(&gpa_list[0])
|
||||
.map_err(|e| vm::HypervisorVmError::ModifyGpaHostAccess(e.into()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
145
hypervisor/src/mshv/x86_64/emulator.rs
Normal file
145
hypervisor/src/mshv/x86_64/emulator.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2024, Microsoft Corporation
|
||||
//
|
||||
|
||||
use crate::arch::emulator::{PlatformEmulator, PlatformError};
|
||||
use crate::arch::x86::emulator::{CpuStateManager, EmulatorCpuState};
|
||||
use crate::cpu::Vcpu;
|
||||
use crate::mshv::MshvVcpu;
|
||||
use iced_x86::Register;
|
||||
use mshv_bindings::*;
|
||||
|
||||
pub struct MshvEmulatorContext<'a> {
|
||||
pub vcpu: &'a MshvVcpu,
|
||||
pub map: (u64, u64), // Initial GVA to GPA mapping provided by the hypervisor
|
||||
}
|
||||
|
||||
impl<'a> MshvEmulatorContext<'a> {
|
||||
// Do the actual gva -> gpa translation
|
||||
#[allow(non_upper_case_globals)]
|
||||
fn translate(&self, gva: u64, flags: u32) -> Result<u64, PlatformError> {
|
||||
if self.map.0 == gva {
|
||||
return Ok(self.map.1);
|
||||
}
|
||||
|
||||
let (gpa, result_code) = self
|
||||
.vcpu
|
||||
.translate_gva(gva, flags.into())
|
||||
.map_err(|e| PlatformError::TranslateVirtualAddress(anyhow!(e)))?;
|
||||
|
||||
match result_code {
|
||||
hv_translate_gva_result_code_HV_TRANSLATE_GVA_SUCCESS => Ok(gpa),
|
||||
_ => Err(PlatformError::TranslateVirtualAddress(anyhow!(result_code))),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_memory_flags(
|
||||
&self,
|
||||
gva: u64,
|
||||
data: &mut [u8],
|
||||
flags: u32,
|
||||
) -> Result<(), PlatformError> {
|
||||
let gpa = self.translate(gva, flags)?;
|
||||
debug!(
|
||||
"mshv emulator: memory read {} bytes from [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gpa
|
||||
);
|
||||
|
||||
if let Some(vm_ops) = &self.vcpu.vm_ops {
|
||||
if vm_ops.guest_mem_read(gpa, data).is_err() {
|
||||
vm_ops
|
||||
.mmio_read(gpa, data)
|
||||
.map_err(|e| PlatformError::MemoryReadFailure(e.into()))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform emulation for Hyper-V
|
||||
impl<'a> PlatformEmulator for MshvEmulatorContext<'a> {
|
||||
type CpuState = EmulatorCpuState;
|
||||
|
||||
fn read_memory(&self, gva: u64, data: &mut [u8]) -> Result<(), PlatformError> {
|
||||
self.read_memory_flags(gva, data, HV_TRANSLATE_GVA_VALIDATE_READ)
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, gva: u64, data: &[u8]) -> Result<(), PlatformError> {
|
||||
let gpa = self.translate(gva, HV_TRANSLATE_GVA_VALIDATE_WRITE)?;
|
||||
debug!(
|
||||
"mshv emulator: memory write {} bytes at [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gpa
|
||||
);
|
||||
|
||||
if let Some(vm_ops) = &self.vcpu.vm_ops {
|
||||
if vm_ops.guest_mem_write(gpa, data).is_err() {
|
||||
vm_ops
|
||||
.mmio_write(gpa, data)
|
||||
.map_err(|e| PlatformError::MemoryWriteFailure(e.into()))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cpu_state(&self, cpu_id: usize) -> Result<Self::CpuState, PlatformError> {
|
||||
if cpu_id != self.vcpu.vp_index as usize {
|
||||
return Err(PlatformError::GetCpuStateFailure(anyhow!(
|
||||
"CPU id mismatch {:?} {:?}",
|
||||
cpu_id,
|
||||
self.vcpu.vp_index
|
||||
)));
|
||||
}
|
||||
|
||||
let regs = self
|
||||
.vcpu
|
||||
.get_regs()
|
||||
.map_err(|e| PlatformError::GetCpuStateFailure(e.into()))?;
|
||||
let sregs = self
|
||||
.vcpu
|
||||
.get_sregs()
|
||||
.map_err(|e| PlatformError::GetCpuStateFailure(e.into()))?;
|
||||
|
||||
debug!("mshv emulator: Getting new CPU state");
|
||||
debug!("mshv emulator: {:#x?}", regs);
|
||||
|
||||
Ok(EmulatorCpuState { regs, sregs })
|
||||
}
|
||||
|
||||
fn set_cpu_state(&self, cpu_id: usize, state: Self::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: Setting new CPU state");
|
||||
debug!("mshv emulator: {:#x?}", state.regs);
|
||||
|
||||
self.vcpu
|
||||
.set_regs(&state.regs)
|
||||
.map_err(|e| PlatformError::SetCpuStateFailure(e.into()))?;
|
||||
self.vcpu
|
||||
.set_sregs(&state.sregs)
|
||||
.map_err(|e| PlatformError::SetCpuStateFailure(e.into()))
|
||||
}
|
||||
|
||||
fn fetch(&self, ip: u64, instruction_bytes: &mut [u8]) -> Result<(), PlatformError> {
|
||||
let rip =
|
||||
self.cpu_state(self.vcpu.vp_index as usize)?
|
||||
.linearize(Register::CS, ip, false)?;
|
||||
self.read_memory_flags(
|
||||
rip,
|
||||
instruction_bytes,
|
||||
HV_TRANSLATE_GVA_VALIDATE_READ | HV_TRANSLATE_GVA_VALIDATE_EXECUTE,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ use crate::arch::x86::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
pub mod emulator;
|
||||
|
||||
///
|
||||
/// Export generically-named wrappers of mshv_bindings for Unix-based platforms
|
||||
///
|
||||
@@ -43,6 +45,11 @@ pub struct VcpuMshvState {
|
||||
pub vp_states: AllVpStateComponents,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct MshvClockData {
|
||||
pub ref_time: u64,
|
||||
}
|
||||
|
||||
impl fmt::Display for VcpuMshvState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let expected_num_msrs = self.msrs.len();
|
||||
|
||||
@@ -190,7 +190,7 @@ pub enum HypervisorVmError {
|
||||
/// Assert virtual interrupt error
|
||||
///
|
||||
#[error("Failed to assert virtual Interrupt: {0}")]
|
||||
AsserttVirtualInterrupt(#[source] anyhow::Error),
|
||||
AssertVirtualInterrupt(#[source] anyhow::Error),
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
///
|
||||
@@ -231,6 +231,16 @@ pub enum HypervisorVmError {
|
||||
///
|
||||
#[error("Failed to complete isolated import: {0}")]
|
||||
CompleteIsolatedImport(#[source] anyhow::Error),
|
||||
/// Failed to set VM property
|
||||
///
|
||||
#[error("Failed to set VM property: {0}")]
|
||||
SetVmProperty(#[source] anyhow::Error),
|
||||
///
|
||||
/// Modify GPA host access error
|
||||
///
|
||||
#[cfg(feature = "sev_snp")]
|
||||
#[error("Failed to modify GPA host access: {0}")]
|
||||
ModifyGpaHostAccess(#[source] anyhow::Error),
|
||||
}
|
||||
///
|
||||
/// Result type for returning from a function
|
||||
@@ -390,6 +400,21 @@ pub trait Vm: Send + Sync + Any {
|
||||
) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Pause the VM
|
||||
fn pause(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resume the VM
|
||||
fn resume(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
fn gain_page_access(&self, _gpa: u64, _size: u32) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VmOps: Send + Sync {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "net_gen"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
name = "net_gen"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
vmm-sys-util = "0.12.1"
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
[package]
|
||||
name = "net_util"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
name = "net_util"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
epoll = "4.3.3"
|
||||
getrandom = "0.2.13"
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
getrandom = "0.2.14"
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
net_gen = { path = "../net_gen" }
|
||||
rate_limiter = { path = "../rate_limiter" }
|
||||
serde = "1.0.197"
|
||||
thiserror = "1.0.58"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
thiserror = "1.0.62"
|
||||
virtio-bindings = "0.2.2"
|
||||
virtio-queue = "0.11.0"
|
||||
vm-memory = { version = "0.14.1", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
|
||||
virtio-queue = "0.12.0"
|
||||
vm-memory = { version = "0.14.1", features = [
|
||||
"backend-atomic",
|
||||
"backend-bitmap",
|
||||
"backend-mmap",
|
||||
] }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = "0.12.1"
|
||||
|
||||
[dev-dependencies]
|
||||
once_cell = "1.19.0"
|
||||
pnet = "0.34.0"
|
||||
pnet_datalink = "0.34.0"
|
||||
serde_json = "1.0.115"
|
||||
pnet = "0.35.0"
|
||||
pnet_datalink = "0.35.0"
|
||||
serde_json = "1.0.120"
|
||||
|
||||
8
net_util/build.rs
Normal file
8
net_util/build.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright © 2024 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
fn main() {
|
||||
println!("cargo::rustc-check-cfg=cfg(fuzzing)");
|
||||
}
|
||||
@@ -67,7 +67,7 @@ fn create_sockaddr(ip_addr: net::Ipv4Addr) -> net_gen::sockaddr {
|
||||
sin_family: net_gen::AF_INET as u16,
|
||||
sin_port: 0,
|
||||
// SAFETY: ip_addr can be safely transmute to in_addr
|
||||
sin_addr: unsafe { mem::transmute(ip_addr.octets()) },
|
||||
sin_addr: unsafe { mem::transmute::<[u8; 4], net_gen::inn::in_addr>(ip_addr.octets()) },
|
||||
__pad: [0; 8usize],
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use super::{register_listener, unregister_listener, vnet_hdr_len, Tap};
|
||||
use crate::GuestMemoryMmap;
|
||||
use rate_limiter::{RateLimiter, TokenType};
|
||||
use std::io;
|
||||
use std::num::Wrapping;
|
||||
@@ -12,6 +11,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use virtio_queue::{Queue, QueueOwnedT, QueueT};
|
||||
use vm_memory::bitmap::Bitmap;
|
||||
use vm_memory::{Bytes, GuestMemory};
|
||||
use vm_virtio::{AccessPlatform, Translatable};
|
||||
|
||||
@@ -19,6 +19,7 @@ use vm_virtio::{AccessPlatform, Translatable};
|
||||
pub struct TxVirtio {
|
||||
pub counter_bytes: Wrapping<u64>,
|
||||
pub counter_frames: Wrapping<u64>,
|
||||
iovecs: IovecBuffer,
|
||||
}
|
||||
|
||||
impl Default for TxVirtio {
|
||||
@@ -32,12 +33,13 @@ impl TxVirtio {
|
||||
TxVirtio {
|
||||
counter_bytes: Wrapping(0),
|
||||
counter_frames: Wrapping(0),
|
||||
iovecs: IovecBuffer::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_desc_chain(
|
||||
pub fn process_desc_chain<B: Bitmap + 'static>(
|
||||
&mut self,
|
||||
mem: &GuestMemoryMmap,
|
||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||
tap: &Tap,
|
||||
queue: &mut Queue,
|
||||
rate_limiter: &mut Option<RateLimiter>,
|
||||
@@ -54,7 +56,7 @@ impl TxVirtio {
|
||||
|
||||
let mut next_desc = desc_chain.next();
|
||||
|
||||
let mut iovecs = Vec::new();
|
||||
let mut iovecs = self.iovecs.borrow();
|
||||
while let Some(desc) = next_desc {
|
||||
let desc_addr = desc
|
||||
.addr()
|
||||
@@ -145,6 +147,7 @@ impl TxVirtio {
|
||||
pub struct RxVirtio {
|
||||
pub counter_bytes: Wrapping<u64>,
|
||||
pub counter_frames: Wrapping<u64>,
|
||||
iovecs: IovecBuffer,
|
||||
}
|
||||
|
||||
impl Default for RxVirtio {
|
||||
@@ -158,12 +161,13 @@ impl RxVirtio {
|
||||
RxVirtio {
|
||||
counter_bytes: Wrapping(0),
|
||||
counter_frames: Wrapping(0),
|
||||
iovecs: IovecBuffer::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_desc_chain(
|
||||
pub fn process_desc_chain<B: Bitmap + 'static>(
|
||||
&mut self,
|
||||
mem: &GuestMemoryMmap,
|
||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||
tap: &Tap,
|
||||
queue: &mut Queue,
|
||||
rate_limiter: &mut Option<RateLimiter>,
|
||||
@@ -193,7 +197,7 @@ impl RxVirtio {
|
||||
.ok_or(NetQueuePairError::DescriptorInvalidHeader)?;
|
||||
let mut next_desc = Some(desc);
|
||||
|
||||
let mut iovecs = Vec::new();
|
||||
let mut iovecs = self.iovecs.borrow();
|
||||
while let Some(desc) = next_desc {
|
||||
let desc_addr = desc
|
||||
.addr()
|
||||
@@ -288,6 +292,53 @@ impl RxVirtio {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct IovecBuffer(Vec<libc::iovec>);
|
||||
|
||||
// SAFETY: Implementing Send for IovecBuffer is safe as the pointer inside is iovec.
|
||||
// The iovecs are usually constructed from virtio descriptors, which are safe to send across
|
||||
// threads.
|
||||
unsafe impl Send for IovecBuffer {}
|
||||
// SAFETY: Implementing Sync for IovecBuffer is safe as the pointer inside is iovec.
|
||||
// The iovecs are usually constructed from virtio descriptors, which are safe to access from
|
||||
// multiple threads.
|
||||
unsafe impl Sync for IovecBuffer {}
|
||||
|
||||
impl IovecBuffer {
|
||||
fn new() -> Self {
|
||||
// Here we use 4 as the default capacity because it is enough for most cases.
|
||||
const DEFAULT_CAPACITY: usize = 4;
|
||||
IovecBuffer(Vec::with_capacity(DEFAULT_CAPACITY))
|
||||
}
|
||||
|
||||
fn borrow(&mut self) -> IovecBufferBorrowed<'_> {
|
||||
IovecBufferBorrowed(&mut self.0)
|
||||
}
|
||||
}
|
||||
|
||||
struct IovecBufferBorrowed<'a>(&'a mut Vec<libc::iovec>);
|
||||
|
||||
impl<'a> std::ops::Deref for IovecBufferBorrowed<'a> {
|
||||
type Target = Vec<libc::iovec>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::ops::DerefMut for IovecBufferBorrowed<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IovecBufferBorrowed<'_> {
|
||||
fn drop(&mut self) {
|
||||
// Clear the buffer to make sure old values are not used after
|
||||
self.0.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct NetCounters {
|
||||
pub tx_bytes: Arc<AtomicU64>,
|
||||
@@ -350,9 +401,9 @@ pub struct NetQueuePair {
|
||||
}
|
||||
|
||||
impl NetQueuePair {
|
||||
pub fn process_tx(
|
||||
pub fn process_tx<B: Bitmap + 'static>(
|
||||
&mut self,
|
||||
mem: &GuestMemoryMmap,
|
||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||
queue: &mut Queue,
|
||||
) -> Result<bool, NetQueuePairError> {
|
||||
let tx_tap_retry = self.tx.process_desc_chain(
|
||||
@@ -400,9 +451,9 @@ impl NetQueuePair {
|
||||
.map_err(NetQueuePairError::QueueNeedsNotification)
|
||||
}
|
||||
|
||||
pub fn process_rx(
|
||||
pub fn process_rx<B: Bitmap + 'static>(
|
||||
&mut self,
|
||||
mem: &GuestMemoryMmap,
|
||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||
queue: &mut Queue,
|
||||
) -> Result<bool, NetQueuePairError> {
|
||||
self.rx_desc_avail = !self.rx.process_desc_chain(
|
||||
@@ -418,7 +469,7 @@ impl NetQueuePair {
|
||||
.map_or(false, |r| r.is_blocked());
|
||||
|
||||
// Stop listening on the `RX_TAP_EVENT` when:
|
||||
// 1) there is no available describles, or
|
||||
// 1) there is no available describes, or
|
||||
// 2) the RX rate limit is reached.
|
||||
if self.rx_tap_listening && (!self.rx_desc_avail || rate_limit_reached) {
|
||||
unregister_listener(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "option_parser"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
name = "option_parser"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -23,6 +23,7 @@ pub enum OptionParserError {
|
||||
UnknownOption(String),
|
||||
InvalidSyntax(String),
|
||||
Conversion(String, String),
|
||||
InvalidValue(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for OptionParserError {
|
||||
@@ -33,6 +34,7 @@ impl fmt::Display for OptionParserError {
|
||||
OptionParserError::Conversion(field, value) => {
|
||||
write!(f, "unable to convert {value} for {field}")
|
||||
}
|
||||
OptionParserError::InvalidValue(s) => write!(f, "invalid value: {s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,7 +205,7 @@ impl FromStr for ByteSized {
|
||||
0
|
||||
};
|
||||
|
||||
let s = s.trim_end_matches(|c| c == 'K' || c == 'M' || c == 'G');
|
||||
let s = s.trim_end_matches(['K', 'M', 'G']);
|
||||
s.parse::<u64>()
|
||||
.map_err(|_| ByteSizedParseError::InvalidValue(s.to_owned()))?
|
||||
<< shift
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "pci"
|
||||
version = "0.1.0"
|
||||
authors = ["Samuel Ortiz <sameo@linux.intel.com>"]
|
||||
edition = "2021"
|
||||
name = "pci"
|
||||
version = "0.1.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
@@ -10,19 +10,24 @@ kvm = ["vfio-ioctls/kvm"]
|
||||
mshv = ["vfio-ioctls/mshv"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.81"
|
||||
anyhow = "1.0.86"
|
||||
byteorder = "1.5.0"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
vfio-bindings = { git = "https://github.com/rust-vmm/vfio", branch = "main", features = ["fam-wrappers"] }
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
thiserror = "1.0.62"
|
||||
vfio-bindings = { git = "https://github.com/rust-vmm/vfio", branch = "main", features = [
|
||||
"fam-wrappers",
|
||||
] }
|
||||
vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false }
|
||||
vfio_user = { git = "https://github.com/rust-vmm/vfio-user", branch = "main" }
|
||||
|
||||
vmm-sys-util = "0.12.1"
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
thiserror = "1.0.58"
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = { version = "0.14.1", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
|
||||
vm-memory = { version = "0.14.1", features = [
|
||||
"backend-atomic",
|
||||
"backend-bitmap",
|
||||
"backend-mmap",
|
||||
] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = "0.12.1"
|
||||
|
||||
@@ -14,7 +14,7 @@ use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use vm_device::{Bus, BusDevice};
|
||||
use vm_device::{Bus, BusDevice, BusDeviceSync};
|
||||
|
||||
const VENDOR_ID_INTEL: u16 = 0x8086;
|
||||
const DEVICE_ID_INTEL_VIRT_PCIE_HOST: u16 = 0x0d57;
|
||||
@@ -122,7 +122,7 @@ impl PciBus {
|
||||
|
||||
pub fn register_mapping(
|
||||
&self,
|
||||
dev: Arc<Mutex<dyn BusDevice>>,
|
||||
dev: Arc<dyn BusDeviceSync>,
|
||||
#[cfg(target_arch = "x86_64")] io_bus: &Bus,
|
||||
mmio_bus: &Bus,
|
||||
bars: Vec<PciBarConfiguration>,
|
||||
@@ -409,7 +409,7 @@ impl BusDevice for PciConfigMmio {
|
||||
// Only allow reads to the register boundary.
|
||||
let start = offset as usize % 4;
|
||||
let end = start + data.len();
|
||||
if end > 4 || offset > u64::from(u32::max_value()) {
|
||||
if end > 4 || offset > u64::from(u32::MAX) {
|
||||
for d in data {
|
||||
*d = 0xff;
|
||||
}
|
||||
@@ -423,7 +423,7 @@ impl BusDevice for PciConfigMmio {
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if offset > u64::from(u32::max_value()) {
|
||||
if offset > u64::from(u32::MAX) {
|
||||
return None;
|
||||
}
|
||||
self.config_space_write(offset as u32, offset % 4, data);
|
||||
|
||||
@@ -291,7 +291,7 @@ pub enum PciExpressCapabilityId {
|
||||
VfResizeableBar = 0x0024,
|
||||
DataLinkFeature = 0x0025,
|
||||
PhysicalLayerSixteenGts = 0x0026,
|
||||
LaneMargeningAtTheReceiver = 0x0027,
|
||||
LaneMarginingAtTheReceiver = 0x0027,
|
||||
HierarchyId = 0x0028,
|
||||
NativePcieEnclosureManagement = 0x0029,
|
||||
PhysicalLayerThirtyTwoGts = 0x002a,
|
||||
@@ -345,7 +345,7 @@ impl From<u16> for PciExpressCapabilityId {
|
||||
0x0024 => PciExpressCapabilityId::VfResizeableBar,
|
||||
0x0025 => PciExpressCapabilityId::DataLinkFeature,
|
||||
0x0026 => PciExpressCapabilityId::PhysicalLayerSixteenGts,
|
||||
0x0027 => PciExpressCapabilityId::LaneMargeningAtTheReceiver,
|
||||
0x0027 => PciExpressCapabilityId::LaneMarginingAtTheReceiver,
|
||||
0x0028 => PciExpressCapabilityId::HierarchyId,
|
||||
0x0029 => PciExpressCapabilityId::NativePcieEnclosureManagement,
|
||||
0x002a => PciExpressCapabilityId::PhysicalLayerThirtyTwoGts,
|
||||
@@ -743,7 +743,7 @@ impl PciConfiguration {
|
||||
.ok_or(Error::BarAddressInvalid(config.addr, config.size))?;
|
||||
match config.region_type {
|
||||
PciBarRegionType::Memory32BitRegion | PciBarRegionType::IoRegion => {
|
||||
if end_addr > u64::from(u32::max_value()) {
|
||||
if end_addr > u64::from(u32::MAX) {
|
||||
return Err(Error::BarAddressInvalid(config.addr, config.size));
|
||||
}
|
||||
|
||||
@@ -757,10 +757,6 @@ impl PciConfiguration {
|
||||
return Err(Error::BarInvalid64(bar_idx));
|
||||
}
|
||||
|
||||
if end_addr > u64::max_value() {
|
||||
return Err(Error::BarAddressInvalid(config.addr, config.size));
|
||||
}
|
||||
|
||||
if self.bars[bar_idx + 1].used {
|
||||
return Err(Error::BarInUse64(bar_idx));
|
||||
}
|
||||
@@ -818,7 +814,7 @@ impl PciConfiguration {
|
||||
.checked_add(config.size - 1)
|
||||
.ok_or(Error::RomBarAddressInvalid(config.addr, config.size))?;
|
||||
|
||||
if end_addr > u64::from(u32::max_value()) {
|
||||
if end_addr > u64::from(u32::MAX) {
|
||||
return Err(Error::RomBarAddressInvalid(config.addr, config.size));
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::fmt::{self, Display};
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use std::{io, result};
|
||||
use vm_allocator::{AddressAllocator, SystemAllocator};
|
||||
use vm_device::{BusDevice, Resource};
|
||||
use vm_device::Resource;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -54,7 +54,7 @@ pub struct BarReprogrammingParams {
|
||||
pub region_type: PciBarRegionType,
|
||||
}
|
||||
|
||||
pub trait PciDevice: BusDevice {
|
||||
pub trait PciDevice: Send {
|
||||
/// Allocates the needed PCI BARs space using the `allocate` function which takes a size and
|
||||
/// returns an address. Returns a Vec of (GuestAddress, GuestUsize) tuples.
|
||||
fn allocate_bars(
|
||||
@@ -79,7 +79,7 @@ pub trait PciDevice: BusDevice {
|
||||
|
||||
/// Sets a register in the configuration space.
|
||||
/// * `reg_idx` - The index of the config register to modify.
|
||||
/// * `offset` - Offset in to the register.
|
||||
/// * `offset` - Offset into the register.
|
||||
fn write_config_register(
|
||||
&mut self,
|
||||
reg_idx: usize,
|
||||
@@ -97,11 +97,11 @@ pub trait PciDevice: BusDevice {
|
||||
) -> Option<BarReprogrammingParams> {
|
||||
None
|
||||
}
|
||||
/// Reads from a BAR region mapped in to the device.
|
||||
/// Reads from a BAR region mapped into the device.
|
||||
/// * `addr` - The guest address inside the BAR.
|
||||
/// * `data` - Filled with the data from `addr`.
|
||||
fn read_bar(&mut self, _base: u64, _offset: u64, _data: &mut [u8]) {}
|
||||
/// Writes to a BAR region mapped in to the device.
|
||||
/// Writes to a BAR region mapped into the device.
|
||||
/// * `addr` - The guest address inside the BAR.
|
||||
/// * `data` - The data to write.
|
||||
fn write_bar(&mut self, _base: u64, _offset: u64, _data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
|
||||
@@ -213,6 +213,12 @@ impl MsixConfig {
|
||||
let index: usize = (offset / MSIX_TABLE_ENTRIES_MODULO) as usize;
|
||||
let modulo_offset = offset % MSIX_TABLE_ENTRIES_MODULO;
|
||||
|
||||
if index >= self.table_entries.len() {
|
||||
debug!("Invalid MSI-X table entry index {index}");
|
||||
data.copy_from_slice(&[0xff; 8][..data.len()]);
|
||||
return;
|
||||
}
|
||||
|
||||
match data.len() {
|
||||
4 => {
|
||||
let value = match modulo_offset {
|
||||
@@ -260,6 +266,11 @@ impl MsixConfig {
|
||||
let index: usize = (offset / MSIX_TABLE_ENTRIES_MODULO) as usize;
|
||||
let modulo_offset = offset % MSIX_TABLE_ENTRIES_MODULO;
|
||||
|
||||
if index >= self.table_entries.len() {
|
||||
debug!("Invalid MSI-X table entry index {index}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the value of the entry before modification
|
||||
let old_entry = self.table_entries[index].clone();
|
||||
|
||||
@@ -351,6 +362,12 @@ impl MsixConfig {
|
||||
let index: usize = (offset / MSIX_PBA_ENTRIES_MODULO) as usize;
|
||||
let modulo_offset = offset % MSIX_PBA_ENTRIES_MODULO;
|
||||
|
||||
if index >= self.pba_entries.len() {
|
||||
debug!("Invalid MSI-X PBA entry index {index}");
|
||||
data.copy_from_slice(&[0xff; 8][..data.len()]);
|
||||
return;
|
||||
}
|
||||
|
||||
match data.len() {
|
||||
4 => {
|
||||
let value: u32 = match modulo_offset {
|
||||
|
||||
@@ -11,7 +11,6 @@ use std::any::Any;
|
||||
use std::os::unix::prelude::AsRawFd;
|
||||
use std::ptr::null_mut;
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use std::u32;
|
||||
use thiserror::Error;
|
||||
use vfio_bindings::bindings::vfio::*;
|
||||
use vfio_ioctls::VfioIrq;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
[package]
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
build = "../build.rs"
|
||||
edition = "2021"
|
||||
name = "performance-metrics"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
build = "../build.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.4", features = ["wrap_help"] }
|
||||
dirs = "5.0.1"
|
||||
serde = { version = "1.0.197", features = ["rc", "derive"] }
|
||||
serde_json = "1.0.115"
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.120"
|
||||
test_infra = { path = "../test_infra" }
|
||||
thiserror = "1.0.58"
|
||||
thiserror = "1.0.62"
|
||||
wait-timeout = "0.2.0"
|
||||
|
||||
@@ -274,7 +274,7 @@ mod adjuster {
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_LIST: [PerformanceTest; 29] = [
|
||||
const TEST_LIST: [PerformanceTest; 30] = [
|
||||
PerformanceTest {
|
||||
name: "boot_time_ms",
|
||||
func_ptr: performance_boot_time,
|
||||
@@ -306,6 +306,16 @@ const TEST_LIST: [PerformanceTest; 29] = [
|
||||
},
|
||||
unit_adjuster: adjuster::s_to_ms,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "restore_latency_time_ms",
|
||||
func_ptr: performance_restore_latency,
|
||||
control: PerformanceTestControl {
|
||||
test_timeout: 2,
|
||||
test_iterations: 10,
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
unit_adjuster: adjuster::identity,
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "boot_time_16_vcpus_pmem_ms",
|
||||
func_ptr: performance_boot_time_pmem,
|
||||
|
||||
@@ -23,6 +23,7 @@ pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-arm64-custom-20210929-
|
||||
enum Error {
|
||||
BootTimeParse,
|
||||
Infra(InfraError),
|
||||
RestoreTimeParse,
|
||||
}
|
||||
|
||||
impl From<InfraError> for Error {
|
||||
@@ -34,7 +35,7 @@ impl From<InfraError> for Error {
|
||||
const BLK_IO_TEST_IMG: &str = "/var/tmp/ch-blk-io-test.img";
|
||||
|
||||
pub fn init_tests() {
|
||||
// The test image can not be created on tmpfs (e.g. /tmp) filesystem,
|
||||
// The test image cannot be created on tmpfs (e.g. /tmp) filesystem,
|
||||
// as tmpfs does not support O_DIRECT
|
||||
assert!(exec_host_command_output(&format!(
|
||||
"dd if=/dev/zero of={BLK_IO_TEST_IMG} bs=1M count=4096"
|
||||
@@ -76,6 +77,26 @@ fn direct_kernel_boot_path() -> PathBuf {
|
||||
kernel_path
|
||||
}
|
||||
|
||||
fn remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool {
|
||||
let mut cmd = std::process::Command::new(clh_command("ch-remote"));
|
||||
cmd.args([&format!("--api-socket={}", api_socket), command]);
|
||||
|
||||
if let Some(arg) = arg {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
let output = cmd.output().unwrap();
|
||||
if output.status.success() {
|
||||
true
|
||||
} else {
|
||||
eprintln!(
|
||||
"Error running ch-remote command: {:?}\nstderr: {}",
|
||||
&cmd,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 {
|
||||
let test_timeout = control.test_timeout;
|
||||
let (rx, bandwidth) = control.net_control.unwrap();
|
||||
@@ -199,7 +220,7 @@ fn parse_boot_time_output(output: &[u8]) -> Result<f64, Error> {
|
||||
);
|
||||
assert!(
|
||||
t[7].eq("seconds"),
|
||||
"Expecting 'seconds' as the the last word of the 'Debug I/O port' output"
|
||||
"Expecting 'seconds' as the last word of the 'Debug I/O port' output"
|
||||
);
|
||||
|
||||
t[6].parse::<f64>().unwrap()
|
||||
@@ -226,7 +247,7 @@ fn parse_boot_time_output(output: &[u8]) -> Result<f64, Error> {
|
||||
);
|
||||
assert!(
|
||||
t[7].eq("seconds"),
|
||||
"Expecting 'seconds' as the the last word of the 'Debug I/O port' output"
|
||||
"Expecting 'seconds' as the last word of the 'Debug I/O port' output"
|
||||
);
|
||||
|
||||
t[6].parse::<f64>().unwrap()
|
||||
@@ -255,7 +276,7 @@ fn measure_boot_time(cmd: &mut GuestCommand, test_timeout: u32) -> Result<f64, E
|
||||
let _ = child.kill();
|
||||
let output = child.wait_with_output().unwrap();
|
||||
|
||||
parse_boot_time_output(&output.stderr).map_err(|e| {
|
||||
parse_boot_time_output(&output.stderr).inspect_err(|_| {
|
||||
eprintln!(
|
||||
"\n\n==== Start child stdout ====\n\n{}\n\n==== End child stdout ====",
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
@@ -264,8 +285,6 @@ fn measure_boot_time(cmd: &mut GuestCommand, test_timeout: u32) -> Result<f64, E
|
||||
"\n\n==== Start child stderr ====\n\n{}\n\n==== End child stderr ====",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
@@ -406,6 +425,120 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the event_monitor file based on the format that each event
|
||||
// is followed by a double newline
|
||||
fn parse_event_file(event_file: &str) -> Vec<serde_json::Value> {
|
||||
let content = fs::read(event_file).unwrap();
|
||||
let mut ret = Vec::new();
|
||||
for entry in String::from_utf8_lossy(&content)
|
||||
.trim()
|
||||
.split("\n\n")
|
||||
.collect::<Vec<&str>>()
|
||||
{
|
||||
ret.push(serde_json::from_str(entry).unwrap());
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn parse_restore_time_output(events: &[serde_json::Value]) -> Result<f64, Error> {
|
||||
for entry in events.iter() {
|
||||
if entry["event"].as_str().unwrap() == "restored" {
|
||||
let duration = entry["timestamp"]["secs"].as_u64().unwrap() as f64 * 1_000f64
|
||||
+ entry["timestamp"]["nanos"].as_u64().unwrap() as f64 / 1_000_000f64;
|
||||
return Ok(duration);
|
||||
}
|
||||
}
|
||||
Err(Error::RestoreTimeParse)
|
||||
}
|
||||
|
||||
fn measure_restore_time(
|
||||
cmd: &mut GuestCommand,
|
||||
event_file: &str,
|
||||
test_timeout: u32,
|
||||
) -> Result<f64, Error> {
|
||||
let mut child = cmd
|
||||
.capture_output()
|
||||
.verbosity(VerbosityLevel::Warn)
|
||||
.set_print_cmd(false)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
thread::sleep(Duration::new((test_timeout / 2) as u64, 0));
|
||||
let _ = child.kill();
|
||||
let output = child.wait_with_output().unwrap();
|
||||
|
||||
let json_events = parse_event_file(event_file);
|
||||
|
||||
parse_restore_time_output(&json_events).inspect_err(|_| {
|
||||
eprintln!(
|
||||
"\n\n==== Start child stdout ====\n\n{}\n\n==== End child stdout ====\
|
||||
\n\n==== Start child stderr ====\n\n{}\n\n==== End child stderr ====",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn performance_restore_latency(control: &PerformanceTestControl) -> f64 {
|
||||
let r = std::panic::catch_unwind(|| {
|
||||
let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
let guest = performance_test_new_guest(Box::new(focal));
|
||||
let api_socket_source = String::from(
|
||||
guest
|
||||
.tmp_dir
|
||||
.as_path()
|
||||
.join("cloud-hypervisor.sock")
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut child = GuestCommand::new(&guest)
|
||||
.args(["--api-socket", &api_socket_source])
|
||||
.args([
|
||||
"--cpus",
|
||||
&format!("boot={}", control.num_boot_vcpus.unwrap_or(1)),
|
||||
])
|
||||
.args(["--memory", "size=256M"])
|
||||
.args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
|
||||
.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
|
||||
.args(["--console", "off"])
|
||||
.default_disks()
|
||||
.set_print_cmd(false)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
thread::sleep(Duration::new((control.test_timeout / 2) as u64, 0));
|
||||
let snapshot_dir = String::from(guest.tmp_dir.as_path().join("snapshot").to_str().unwrap());
|
||||
std::fs::create_dir(&snapshot_dir).unwrap();
|
||||
assert!(remote_command(&api_socket_source, "pause", None));
|
||||
assert!(remote_command(
|
||||
&api_socket_source,
|
||||
"snapshot",
|
||||
Some(format!("file://{}", snapshot_dir).as_str()),
|
||||
));
|
||||
|
||||
let _ = child.kill();
|
||||
|
||||
let event_path = String::from(guest.tmp_dir.as_path().join("event.json").to_str().unwrap());
|
||||
let mut cmd = GuestCommand::new(&guest);
|
||||
let c = cmd
|
||||
.args([
|
||||
"--restore",
|
||||
format!("source_url=file://{}", snapshot_dir).as_str(),
|
||||
])
|
||||
.args(["--event-monitor", format!("path={}", event_path).as_str()]);
|
||||
|
||||
measure_restore_time(c, event_path.as_str(), control.test_timeout).unwrap()
|
||||
});
|
||||
|
||||
match r {
|
||||
Ok(r) => r,
|
||||
Err(_) => {
|
||||
panic!("test failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -496,7 +629,42 @@ cloud-hypervisor: 613.57361ms: <vcpu0> INFO:vmm/src/vm.rs:392 -- [Debug I/O port
|
||||
|
||||
assert_eq!(parse_boot_time_output(output.as_bytes()).unwrap(), 0.4525);
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_restore_time_output() {
|
||||
let output = r#"
|
||||
{
|
||||
"timestamp": {
|
||||
"secs": 0,
|
||||
"nanos": 4664404
|
||||
},
|
||||
"source": "virtio-device",
|
||||
"event": "activated",
|
||||
"properties": {
|
||||
"id": "__rng"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"timestamp": {
|
||||
"secs": 0,
|
||||
"nanos": 5505133
|
||||
},
|
||||
"source": "vm",
|
||||
"event": "restored",
|
||||
"properties": null
|
||||
}
|
||||
"#;
|
||||
let mut ret = Vec::new();
|
||||
for entry in String::from(output)
|
||||
.trim()
|
||||
.split("\n\n")
|
||||
.collect::<Vec<&str>>()
|
||||
{
|
||||
ret.push(serde_json::from_str(entry).unwrap());
|
||||
}
|
||||
|
||||
assert_eq!(parse_restore_time_output(&ret).unwrap(), 5.505133_f64);
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_fio_output() {
|
||||
let output = r#"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "rate_limiter"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
epoll = "4.3.3"
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
thiserror = "1.0.58"
|
||||
libc = "0.2.155"
|
||||
log = "0.4.22"
|
||||
thiserror = "1.0.62"
|
||||
vmm-sys-util = "0.12.1"
|
||||
|
||||
@@ -367,7 +367,7 @@ pub(crate) mod tests {
|
||||
assert!(h.as_raw_fd() > 0);
|
||||
|
||||
// ops/s limiter should be disabled so consume(whatever) should work
|
||||
assert!(h.consume(u64::max_value(), TokenType::Ops));
|
||||
assert!(h.consume(u64::MAX, TokenType::Ops));
|
||||
|
||||
// do full 1000 bytes
|
||||
assert!(h.consume(1000, TokenType::Bytes));
|
||||
@@ -403,7 +403,7 @@ pub(crate) mod tests {
|
||||
assert!(h.as_raw_fd() > 0);
|
||||
|
||||
// bytes/s limiter should be disabled so consume(whatever) should work
|
||||
assert!(h.consume(u64::max_value(), TokenType::Bytes));
|
||||
assert!(h.consume(u64::MAX, TokenType::Bytes));
|
||||
|
||||
// do full 1000 ops
|
||||
assert!(h.consume(1000, TokenType::Ops));
|
||||
|
||||
@@ -307,14 +307,14 @@ impl RateLimiter {
|
||||
///
|
||||
/// * `bytes_total_capacity` - the total capacity of the `TokenType::Bytes` token bucket.
|
||||
/// * `bytes_one_time_burst` - initial extra credit on top of `bytes_total_capacity`,
|
||||
/// that does not replenish and which can be used for an initial burst of data.
|
||||
/// that does not replenish and which can be used for an initial burst of data.
|
||||
/// * `bytes_complete_refill_time_ms` - number of milliseconds for the `TokenType::Bytes`
|
||||
/// token bucket to go from zero Bytes to `bytes_total_capacity` Bytes.
|
||||
/// token bucket to go from zero Bytes to `bytes_total_capacity` Bytes.
|
||||
/// * `ops_total_capacity` - the total capacity of the `TokenType::Ops` token bucket.
|
||||
/// * `ops_one_time_burst` - initial extra credit on top of `ops_total_capacity`,
|
||||
/// that does not replenish and which can be used for an initial burst of data.
|
||||
/// that does not replenish and which can be used for an initial burst of data.
|
||||
/// * `ops_complete_refill_time_ms` - number of milliseconds for the `TokenType::Ops` token
|
||||
/// bucket to go from zero Ops to `ops_total_capacity` Ops.
|
||||
/// bucket to go from zero Ops to `ops_total_capacity` Ops.
|
||||
///
|
||||
/// If either bytes/ops *size* or *refill_time* are **zero**, the limiter
|
||||
/// is **disabled** for that respective token type.
|
||||
@@ -662,8 +662,8 @@ pub(crate) mod tests {
|
||||
// limiter should not be blocked
|
||||
assert!(!l.is_blocked());
|
||||
// limiter should be disabled so consume(whatever) should work
|
||||
assert!(l.consume(u64::max_value(), TokenType::Ops));
|
||||
assert!(l.consume(u64::max_value(), TokenType::Bytes));
|
||||
assert!(l.consume(u64::MAX, TokenType::Ops));
|
||||
assert!(l.consume(u64::MAX, TokenType::Bytes));
|
||||
// calling the handler without there having been an event should error
|
||||
assert!(l.event_handler().is_err());
|
||||
assert_eq!(
|
||||
@@ -721,7 +721,7 @@ pub(crate) mod tests {
|
||||
assert!(l.as_raw_fd() > 0);
|
||||
|
||||
// ops/s limiter should be disabled so consume(whatever) should work
|
||||
assert!(l.consume(u64::max_value(), TokenType::Ops));
|
||||
assert!(l.consume(u64::MAX, TokenType::Ops));
|
||||
|
||||
// do full 1000 bytes
|
||||
assert!(l.consume(1000, TokenType::Bytes));
|
||||
@@ -754,7 +754,7 @@ pub(crate) mod tests {
|
||||
assert!(l.as_raw_fd() > 0);
|
||||
|
||||
// bytes/s limiter should be disabled so consume(whatever) should work
|
||||
assert!(l.consume(u64::max_value(), TokenType::Bytes));
|
||||
assert!(l.consume(u64::MAX, TokenType::Bytes));
|
||||
|
||||
// do full 1000 ops
|
||||
assert!(l.consume(1000, TokenType::Ops));
|
||||
|
||||
282
release-notes.md
282
release-notes.md
@@ -1,10 +1,20 @@
|
||||
- [v41.0](#v410)
|
||||
- [Experimental "Pvmemcontrol" Support](#experimental-pvmemcontrol-support)
|
||||
- [Sandboxing With Landlock Support](#sandboxing-with-landlock-support)
|
||||
- [Notable Performance Improvements](#notable-performance-improvements)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes)
|
||||
- [Contributors](#contributors)
|
||||
- [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-1)
|
||||
- [Contributors](#contributors-1)
|
||||
- [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)
|
||||
- [Contributors](#contributors)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-2)
|
||||
- [Contributors](#contributors-2)
|
||||
- [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)
|
||||
@@ -12,16 +22,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-1)
|
||||
- [Contributors](#contributors-1)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-3)
|
||||
- [Contributors](#contributors-3)
|
||||
- [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-2)
|
||||
- [Contributors](#contributors-2)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-4)
|
||||
- [Contributors](#contributors-4)
|
||||
- [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)
|
||||
@@ -30,31 +40,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-3)
|
||||
- [Contributors](#contributors-3)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-5)
|
||||
- [Contributors](#contributors-5)
|
||||
- [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-4)
|
||||
- [Contributors](#contributors-4)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-6)
|
||||
- [Contributors](#contributors-6)
|
||||
- [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-5)
|
||||
- [Contributors](#contributors-5)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-7)
|
||||
- [Contributors](#contributors-7)
|
||||
- [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-6)
|
||||
- [Contributors](#contributors-6)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-8)
|
||||
- [Contributors](#contributors-8)
|
||||
- [v32.0](#v320)
|
||||
- [Increased PCI Segment Limit](#increased-pci-segment-limit)
|
||||
- [API Changes](#api-changes)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-7)
|
||||
- [Contributors](#contributors-7)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-9)
|
||||
- [Contributors](#contributors-9)
|
||||
- [v31.1](#v311)
|
||||
- [v31.0](#v310)
|
||||
- [Update to Latest `acpi_tables`](#update-to-latest-acpi_tables)
|
||||
@@ -62,15 +72,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-8)
|
||||
- [Contributors](#contributors-8)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-10)
|
||||
- [Contributors](#contributors-10)
|
||||
- [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-9)
|
||||
- [Contributors](#contributors-9)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-11)
|
||||
- [Contributors](#contributors-11)
|
||||
- [v28.2](#v282)
|
||||
- [v29.0](#v290)
|
||||
- [Release Binary Supports Both MSHV and KVM](#release-binary-supports-both-mshv-and-kvm)
|
||||
@@ -80,10 +90,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-10)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-12)
|
||||
- [Removals](#removals)
|
||||
- [Deprecations](#deprecations)
|
||||
- [Contributors](#contributors-10)
|
||||
- [Contributors](#contributors-12)
|
||||
- [v28.1](#v281)
|
||||
- [v28.0](#v280)
|
||||
- [Community Engagement (Reminder)](#community-engagement-reminder)
|
||||
@@ -91,9 +101,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-11)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-13)
|
||||
- [Removals](#removals-1)
|
||||
- [Contributors](#contributors-11)
|
||||
- [Contributors](#contributors-13)
|
||||
- [v27.0](#v270)
|
||||
- [Community Engagement](#community-engagement)
|
||||
- [Prebuilt Packages](#prebuilt-packages)
|
||||
@@ -102,41 +112,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-12)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-14)
|
||||
- [Deprecations](#deprecations-1)
|
||||
- [Contributors](#contributors-12)
|
||||
- [Contributors](#contributors-14)
|
||||
- [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-13)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-15)
|
||||
- [Deprecations](#deprecations-2)
|
||||
- [Removals](#removals-2)
|
||||
- [Contributors](#contributors-13)
|
||||
- [Contributors](#contributors-15)
|
||||
- [v25.0](#v250)
|
||||
- [`ch-remote` Improvements](#ch-remote-improvements-1)
|
||||
- [VM "Coredump" Support](#vm-coredump-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-14)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-16)
|
||||
- [Removals](#removals-3)
|
||||
- [Contributors](#contributors-14)
|
||||
- [Contributors](#contributors-16)
|
||||
- [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-15)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-17)
|
||||
- [Notable Improvements](#notable-improvements)
|
||||
- [Deprecations](#deprecations-3)
|
||||
- [New on the Website](#new-on-the-website)
|
||||
- [Contributors](#contributors-15)
|
||||
- [Contributors](#contributors-17)
|
||||
- [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-16)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-18)
|
||||
- [Deprecations](#deprecations-4)
|
||||
- [Contributors](#contributors-16)
|
||||
- [Contributors](#contributors-18)
|
||||
- [v22.1](#v221)
|
||||
- [v22.0](#v220)
|
||||
- [GDB Debug Stub Support](#gdb-debug-stub-support)
|
||||
@@ -147,13 +157,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-17)
|
||||
- [Contributors](#contributors-17)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-19)
|
||||
- [Contributors](#contributors-19)
|
||||
- [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-18)
|
||||
- [Contributors](#contributors-18)
|
||||
- [Notable Bug fixes](#notable-bug-fixes-20)
|
||||
- [Contributors](#contributors-20)
|
||||
- [v20.2](#v202)
|
||||
- [v20.1](#v201)
|
||||
- [v20.0](#v200)
|
||||
@@ -162,8 +172,8 @@
|
||||
- [Improved VFIO support](#improved-vfio-support)
|
||||
- [Safer code](#safer-code)
|
||||
- [Extended documentation](#extended-documentation)
|
||||
- [Notable bug fixes](#notable-bug-fixes-19)
|
||||
- [Contributors](#contributors-19)
|
||||
- [Notable bug fixes](#notable-bug-fixes-21)
|
||||
- [Contributors](#contributors-21)
|
||||
- [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)
|
||||
@@ -171,8 +181,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-20)
|
||||
- [Contributors](#contributors-20)
|
||||
- [Notable bug fixes](#notable-bug-fixes-22)
|
||||
- [Contributors](#contributors-22)
|
||||
- [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)
|
||||
@@ -182,23 +192,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-21)
|
||||
- [Contributors](#contributors-21)
|
||||
- [Notable bug fixes](#notable-bug-fixes-23)
|
||||
- [Contributors](#contributors-23)
|
||||
- [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-22)
|
||||
- [Contributors](#contributors-22)
|
||||
- [Notable bug fixes](#notable-bug-fixes-24)
|
||||
- [Contributors](#contributors-24)
|
||||
- [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-23)
|
||||
- [Notable bug fixes](#notable-bug-fixes-25)
|
||||
- [Removed functionality](#removed-functionality)
|
||||
- [Contributors](#contributors-23)
|
||||
- [Contributors](#contributors-25)
|
||||
- [v15.0](#v150)
|
||||
- [Version numbering and stability guarantees](#version-numbering-and-stability-guarantees)
|
||||
- [Network device rate limiting](#network-device-rate-limiting)
|
||||
@@ -206,7 +216,7 @@
|
||||
- [`--api-socket` supports file descriptor parameter](#--api-socket-supports-file-descriptor-parameter)
|
||||
- [Bug fixes](#bug-fixes)
|
||||
- [Deprecations](#deprecations-5)
|
||||
- [Contributors](#contributors-24)
|
||||
- [Contributors](#contributors-26)
|
||||
- [v0.14.1](#v0141)
|
||||
- [v0.14.0](#v0140)
|
||||
- [Structured event monitoring](#structured-event-monitoring)
|
||||
@@ -216,7 +226,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-6)
|
||||
- [Contributors](#contributors-25)
|
||||
- [Contributors](#contributors-27)
|
||||
- [v0.13.0](#v0130)
|
||||
- [Wider VFIO device support](#wider-vfio-device-support)
|
||||
- [Improved huge page support](#improved-huge-page-support)
|
||||
@@ -224,13 +234,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-26)
|
||||
- [Contributors](#contributors-28)
|
||||
- [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-27)
|
||||
- [Contributors](#contributors-29)
|
||||
- [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)
|
||||
@@ -242,15 +252,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-24)
|
||||
- [Contributors](#contributors-28)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-26)
|
||||
- [Contributors](#contributors-30)
|
||||
- [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-25)
|
||||
- [Contributors](#contributors-29)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-27)
|
||||
- [Contributors](#contributors-31)
|
||||
- [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)
|
||||
@@ -263,17 +273,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-26)
|
||||
- [Contributors](#contributors-30)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-28)
|
||||
- [Contributors](#contributors-32)
|
||||
- [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-27)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-29)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes)
|
||||
- [Contributors](#contributors-31)
|
||||
- [Contributors](#contributors-33)
|
||||
- [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)
|
||||
@@ -283,14 +293,14 @@
|
||||
- [`Seccomp` Sandboxing](#seccomp-sandboxing)
|
||||
- [Updated Distribution Support](#updated-distribution-support)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes-1)
|
||||
- [Contributors](#contributors-32)
|
||||
- [Contributors](#contributors-34)
|
||||
- [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-33)
|
||||
- [Contributors](#contributors-35)
|
||||
- [v0.5.1](#v051)
|
||||
- [v0.5.0](#v050)
|
||||
- [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing)
|
||||
@@ -298,7 +308,7 @@
|
||||
- [New Interrupt Management Framework](#new-interrupt-management-framework)
|
||||
- [Development Tools](#development-tools)
|
||||
- [Kata Containers Integration](#kata-containers-integration)
|
||||
- [Contributors](#contributors-34)
|
||||
- [Contributors](#contributors-36)
|
||||
- [v0.4.0](#v040)
|
||||
- [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition)
|
||||
- [Programmatic firmware tables generation](#programmatic-firmware-tables-generation)
|
||||
@@ -307,7 +317,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-35)
|
||||
- [Contributors](#contributors-37)
|
||||
- [v0.3.0](#v030)
|
||||
- [Block device offloading](#block-device-offloading)
|
||||
- [Network device backend](#network-device-backend)
|
||||
@@ -334,6 +344,116 @@
|
||||
- [Unit testing](#unit-testing)
|
||||
- [Integration tests parallelization](#integration-tests-parallelization)
|
||||
|
||||
# v41.0
|
||||
|
||||
This release has been tracked in our [roadmap
|
||||
project](https://github.com/orgs/cloud-hypervisor/projects/6) as iteration
|
||||
v41.0. The following user visible changes have been made:
|
||||
|
||||
### Experimental "Pvmemcontrol" Support
|
||||
|
||||
VMM support has been added for this experimental functionality (requires
|
||||
currently out of tree Linux kernel patches) to allow guests to control its
|
||||
physical memory properties to allow optimisations and security features.
|
||||
(#6318, #6467)
|
||||
|
||||
### Sandboxing With Landlock Support
|
||||
|
||||
Support for restricting the VMM process using the Linux kernel "Landlock" API
|
||||
has been added - this can be used to restrict the files (and the read/write
|
||||
permissions) that the VMM process can access. This adds another layer of
|
||||
security alongside the existing sycall filters (`seccomp`) - this can be
|
||||
enabled with `--landlock` and [fully documentated](docs/landlock.md). (#5170)
|
||||
|
||||
### Notable Performance Improvements
|
||||
|
||||
* Reduced heap allocations in `virtio-net` via the use of a cache of `Iovec`
|
||||
structures (#6636)
|
||||
* Notification suppression ("`EVENT_IDX`") support has been added to
|
||||
`virtio-block` giving a 60% improvement in single queue block throughput and
|
||||
IOPs performance (#6580)
|
||||
* Correct size used for `status` field in `virtio-block` state (#6586)
|
||||
|
||||
### Notable Bug Fixes
|
||||
|
||||
* Avoid panic on out-of-bounds PCI MSI-X access (#6657)
|
||||
* Fix undefined behaviour on AArch64 leading to wrong optimisation on KVM API
|
||||
access (#6647)
|
||||
* Rust v1.80.0 added use of `fcntl` syscall on debug assertions so this is now
|
||||
included in the virtio-device seccomp filters for tests that use this (#6648)
|
||||
* Short reads are now handled correctly in the `virtio-vsock` device (#6621)
|
||||
* Fix undefined behaviour on TTY ioctl leading to wrong optimisation (#6568)
|
||||
|
||||
### Contributors
|
||||
|
||||
Many thanks to everyone who has contributed to our release:
|
||||
|
||||
* Alyssa Ross <hi@alyssa.is>
|
||||
* Bo Chen <chen.bo@intel.com>
|
||||
* Changyuan Lyu <changyuanl@google.com>
|
||||
* Jinank Jain <jinankjain@microsoft.com>
|
||||
* Julian Stecklina <julian.stecklina@cyberus-technology.de>
|
||||
* Muminul Islam <muislam@microsoft.com>
|
||||
* Nuno Das Neves <nudasnev@microsoft.com>
|
||||
* Praveen K Paladugu <prapal@linux.microsoft.com>
|
||||
* Rob Bradford <rbradford@rivosinc.com>
|
||||
* Songqian Li <sionli@tencent.com>
|
||||
* Wei Liu <liuwe@microsoft.com>
|
||||
* Yuanchu Xie <yuanchu@google.com>
|
||||
* ihciah <ihciah@gmail.com>
|
||||
* wuxinyue <wuxinyue.wxy@antgroup.com>
|
||||
|
||||
# v40.0
|
||||
|
||||
This release has been tracked in our [roadmap
|
||||
project](https://github.com/orgs/cloud-hypervisor/projects/6) as iteration
|
||||
v40.0. The following user visible changes have been made:
|
||||
|
||||
### Support for Restoring File Descriptor Backed Network Devices
|
||||
|
||||
It is now possible to pass file descriptors over the HTTP API (and using
|
||||
`ch-remote`) when restoring to update the file descriptors for network devices.
|
||||
This enables snapshot & restore functionality for guests using `macvtap` or
|
||||
other file descriptor backed network devices. (#6286)
|
||||
|
||||
### Notable Bug Fixes
|
||||
|
||||
* Default values have been removed from required fields in the OpenAPI metadata
|
||||
(#6495)
|
||||
* The help syntax of `ch-remote remove-device` has been improved (#6456)
|
||||
* A double close of file descriptors has been fixed when using `--serial`
|
||||
(#6486)
|
||||
* To prevent loops a limit on the nesting level for QCOW2 backing files has
|
||||
been introduced (#6482)
|
||||
* Boot time performance has been improved with multiple cores by avoiding
|
||||
`cpuid` instructions and by seeding the in kernel file descriptor table
|
||||
(#6498, #6478)
|
||||
* L1 cache details are more likely to be propagated into the guest (#6523)
|
||||
* The default topology for guests now uses multiple cores rather than sockets
|
||||
(#6504)
|
||||
|
||||
### Contributors
|
||||
|
||||
Many thanks to everyone who has contributed to our release:
|
||||
|
||||
* Alexandru Matei <alexandru.matei@uipath.com>
|
||||
* Bo Chen <chen.bo@intel.com>
|
||||
* Jinank Jain <jinankjain@microsoft.com>
|
||||
* Josh Soref <2119212+jsoref@users.noreply.github.com>
|
||||
* Muminul Islam <muislam@microsoft.com>
|
||||
* Nuno Das Neves <nudasnev@microsoft.com>
|
||||
* Omer Faruk Bayram <omer.faruk@sartura.hr>
|
||||
* Praveen K Paladugu <prapal@linux.microsoft.com>
|
||||
* Purna Pavan Chandra <paekkaladevi@linux.microsoft.com>
|
||||
* Rob Bradford <rbradford@rivosinc.com>
|
||||
* SamrutGadde <samrut.gadde@gmail.com>
|
||||
* Sean Banko <sbanko@crusoe.ai>
|
||||
* Songqian Li <sionli@tencent.com>
|
||||
* Wei Liu <liuwe@microsoft.com>
|
||||
* Yi Wang <foxywang@tencent.com>
|
||||
* Yu Li <liyu.yukiteru@bytedance.com>
|
||||
*
|
||||
|
||||
# v39.0
|
||||
|
||||
This release has been tracked in our [roadmap
|
||||
@@ -1081,7 +1201,7 @@ releases of the LTS.
|
||||
|
||||
### Virtualised TPM Support
|
||||
|
||||
Support for adding an emulated CRB TPM has been added. This has it's own [TPM
|
||||
Support for adding an emulated CRB TPM has been added. This has its own [TPM
|
||||
documentation](docs/tpm.md).
|
||||
|
||||
### Transparent Huge Page Support
|
||||
@@ -1272,7 +1392,7 @@ plan to use alternatives.
|
||||
The following functionality has been removed:
|
||||
|
||||
* The unused `poll_queue` parameter has been removed from `--disk` and
|
||||
equivalent. This was residual from the the removal of the `vhost-user-block`
|
||||
equivalent. This was residual from the removal of the `vhost-user-block`
|
||||
spawning feature (#4402.)
|
||||
|
||||
### Contributors
|
||||
@@ -2094,19 +2214,19 @@ Deprecated features will be removed in a subsequent release and users should pla
|
||||
Many thanks to everyone who has contributed to our 0.14.0 release including
|
||||
some new faces.
|
||||
|
||||
Bo Chen <chen.bo@intel.com>
|
||||
Henry Wang <Henry.Wang@arm.com>
|
||||
Iggy Jackson <iggy@theiggy.com>
|
||||
Jiachen Zhang <zhangjiachen.jaycee@bytedance.com>
|
||||
Michael Zhao <michael.zhao@arm.com>
|
||||
Muminul Islam <muislam@microsoft.com>
|
||||
Penny Zheng <Penny.Zheng@arm.com>
|
||||
Rob Bradford <robert.bradford@intel.com>
|
||||
Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
Vineeth Pillai <viremana@linux.microsoft.com>
|
||||
Wei Liu <liuwe@microsoft.com>
|
||||
William Douglas <william.r.douglas@gmail.com>
|
||||
Zide Chen <zide.chen@intel.com>
|
||||
* Bo Chen <chen.bo@intel.com>
|
||||
* Henry Wang <Henry.Wang@arm.com>
|
||||
* Iggy Jackson <iggy@theiggy.com>
|
||||
* Jiachen Zhang <zhangjiachen.jaycee@bytedance.com>
|
||||
* Michael Zhao <michael.zhao@arm.com>
|
||||
* Muminul Islam <muislam@microsoft.com>
|
||||
* Penny Zheng <Penny.Zheng@arm.com>
|
||||
* Rob Bradford <robert.bradford@intel.com>
|
||||
* Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
* Vineeth Pillai <viremana@linux.microsoft.com>
|
||||
* Wei Liu <liuwe@microsoft.com>
|
||||
* William Douglas <william.r.douglas@gmail.com>
|
||||
* Zide Chen <zide.chen@intel.com>
|
||||
|
||||
# v0.13.0
|
||||
|
||||
@@ -2191,7 +2311,7 @@ removed.
|
||||
### Migration of `vhost-user-fs` backend
|
||||
|
||||
The `vhost-user-fs` backend is no longer included in Cloud Hypervisor and it is
|
||||
instead hosted in [it's own
|
||||
instead hosted in [its own
|
||||
repository](https://gitlab.com/virtio-fs/virtiofsd-rs)
|
||||
|
||||
### Enhanced "info" API
|
||||
@@ -2325,7 +2445,7 @@ Some `virtio-block` device drivers may generate requests with multiple descripto
|
||||
|
||||
### Memory Zones
|
||||
|
||||
Support has been added for fine grained control of memory allocation for the guest. This includes controlling the backing of sections of guest memory, assigning to specific host NUMA nodes and assigning memory and vCPUs to specific memory nodes inside the guest. Full details of this can be found in the [memory documentation](docs/memory.md).
|
||||
Support has been added for fine-grained control of memory allocation for the guest. This includes controlling the backing of sections of guest memory, assigning to specific host NUMA nodes and assigning memory and vCPUs to specific memory nodes inside the guest. Full details of this can be found in the [memory documentation](docs/memory.md).
|
||||
|
||||
### `Seccomp` Sandbox Improvements
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
FROM ubuntu:22.04 as dev
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG RUST_TOOLCHAIN="1.74.1"
|
||||
ARG RUST_TOOLCHAIN="1.77.0"
|
||||
ARG CLH_SRC_DIR="/cloud-hypervisor"
|
||||
ARG CLH_BUILD_DIR="$CLH_SRC_DIR/build"
|
||||
ARG CARGO_REGISTRY_DIR="$CLH_BUILD_DIR/cargo_registry"
|
||||
|
||||
@@ -9,7 +9,7 @@ CLI_NAME="Cloud Hypervisor"
|
||||
CTR_IMAGE_TAG="ghcr.io/cloud-hypervisor/cloud-hypervisor"
|
||||
|
||||
# Needs to match explicit version in docker-image.yaml workflow
|
||||
CTR_IMAGE_VERSION="20240407-0"
|
||||
CTR_IMAGE_VERSION="20240507-0"
|
||||
: "${CTR_IMAGE:=${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}}"
|
||||
|
||||
DOCKER_RUNTIME="docker"
|
||||
|
||||
@@ -134,7 +134,7 @@ update_workloads() {
|
||||
popd || exit
|
||||
|
||||
# Download Cloud Hypervisor binary from its last stable release
|
||||
LAST_RELEASE_VERSION="v36.0"
|
||||
LAST_RELEASE_VERSION="v39.0"
|
||||
CH_RELEASE_URL="https://github.com/cloud-hypervisor/cloud-hypervisor/releases/download/$LAST_RELEASE_VERSION/cloud-hypervisor-static-aarch64"
|
||||
CH_RELEASE_NAME="cloud-hypervisor-static-aarch64"
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
|
||||
@@ -45,7 +45,7 @@ fi
|
||||
popd || exit
|
||||
|
||||
# Download Cloud Hypervisor binary from its last stable release
|
||||
LAST_RELEASE_VERSION="v36.0"
|
||||
LAST_RELEASE_VERSION="v39.0"
|
||||
CH_RELEASE_URL="https://github.com/cloud-hypervisor/cloud-hypervisor/releases/download/$LAST_RELEASE_VERSION/cloud-hypervisor-static"
|
||||
CH_RELEASE_NAME="cloud-hypervisor-static"
|
||||
pushd "$WORKLOADS_DIR" || exit
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
set -x
|
||||
|
||||
# This set of vfio tests require to be ran on a specific machine with
|
||||
# specific hardware (e.g. the "vfio" bera-metal worker equipped with a
|
||||
# specific hardware (e.g. the "vfio" bare-metal worker equipped with a
|
||||
# Nvidia Tesla T4 card). So the provisioning of the running machine is
|
||||
# out of the scope of this script, including the custom guest image with
|
||||
# Nvidia drivers installed, and properly configured Nvidia Tesla T4 card.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
name = "serial_buffer"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
@@ -15,7 +15,7 @@ use std::marker::PhantomData;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::process;
|
||||
#[cfg(feature = "dbus_api")]
|
||||
use zbus::{dbus_proxy, zvariant::Optional};
|
||||
use zbus::{proxy, zvariant::Optional};
|
||||
|
||||
type ApiResult = Result<(), Error>;
|
||||
|
||||
@@ -72,7 +72,7 @@ enum TargetApi<'a> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "dbus_api")]
|
||||
#[dbus_proxy(name = "org.cloudhypervisor.DBusApi1", assume_defaults = false)]
|
||||
#[proxy(name = "org.cloudhypervisor.DBusApi1", assume_defaults = false)]
|
||||
trait DBusApi1 {
|
||||
fn vmm_ping(&self) -> zbus::Result<String>;
|
||||
fn vmm_shutdown(&self) -> zbus::Result<()>;
|
||||
@@ -445,14 +445,14 @@ fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResu
|
||||
.map_err(Error::HttpApiClient)
|
||||
}
|
||||
Some("restore") => {
|
||||
let restore_config = restore_config(
|
||||
let (restore_config, fds) = restore_config(
|
||||
matches
|
||||
.subcommand_matches("restore")
|
||||
.unwrap()
|
||||
.get_one::<String>("restore_config")
|
||||
.unwrap(),
|
||||
)?;
|
||||
simple_api_command(socket, "PUT", "restore", Some(&restore_config))
|
||||
simple_api_command_with_fds(socket, "PUT", "restore", Some(&restore_config), fds)
|
||||
.map_err(Error::HttpApiClient)
|
||||
}
|
||||
Some("coredump") => {
|
||||
@@ -661,7 +661,7 @@ fn dbus_api_do_command(matches: &ArgMatches, proxy: &DBusApi1ProxyBlocking<'_>)
|
||||
proxy.api_vm_snapshot(&snapshot_config)
|
||||
}
|
||||
Some("restore") => {
|
||||
let restore_config = restore_config(
|
||||
let (restore_config, _fds) = restore_config(
|
||||
matches
|
||||
.subcommand_matches("restore")
|
||||
.unwrap()
|
||||
@@ -849,11 +849,20 @@ fn snapshot_config(url: &str) -> String {
|
||||
serde_json::to_string(&snapshot_config).unwrap()
|
||||
}
|
||||
|
||||
fn restore_config(config: &str) -> Result<String, Error> {
|
||||
let restore_config = vmm::config::RestoreConfig::parse(config).map_err(Error::Restore)?;
|
||||
fn restore_config(config: &str) -> Result<(String, Vec<i32>), Error> {
|
||||
let mut restore_config = vmm::config::RestoreConfig::parse(config).map_err(Error::Restore)?;
|
||||
// RestoreConfig is modified on purpose to take out the file descriptors.
|
||||
// These fds are passed to the server side process via SCM_RIGHTS
|
||||
let fds = match &mut restore_config.net_fds {
|
||||
Some(net_fds) => net_fds
|
||||
.iter_mut()
|
||||
.flat_map(|net| net.fds.take().unwrap_or_default())
|
||||
.collect(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
let restore_config = serde_json::to_string(&restore_config).unwrap();
|
||||
|
||||
Ok(restore_config)
|
||||
Ok((restore_config, fds))
|
||||
}
|
||||
|
||||
fn coredump_config(destination_url: &str) -> String {
|
||||
@@ -987,7 +996,7 @@ fn main() {
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("remove-device")
|
||||
.about("Remove VFIO device")
|
||||
.about("Remove VFIO and PCI device")
|
||||
.arg(Arg::new("id").index(1).help("<device_id>")),
|
||||
)
|
||||
.subcommand(Command::new("info").about("Info on the VM"))
|
||||
|
||||
138
src/main.rs
138
src/main.rs
@@ -12,17 +12,18 @@ use log::{warn, LevelFilter};
|
||||
use option_parser::OptionParser;
|
||||
use seccompiler::SeccompAction;
|
||||
use signal_hook::consts::SIGSYS;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::{FromRawFd, RawFd};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{env, io};
|
||||
use thiserror::Error;
|
||||
#[cfg(feature = "dbus_api")]
|
||||
use vmm::api::dbus::{dbus_api_graceful_shutdown, DBusApiOptions};
|
||||
use vmm::api::http::http_api_graceful_shutdown;
|
||||
use vmm::api::ApiAction;
|
||||
use vmm::config;
|
||||
use vmm::landlock::{Landlock, LandlockError};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::signal::block_signal;
|
||||
|
||||
@@ -85,6 +86,22 @@ enum Error {
|
||||
LoggerSetup(log::SetLoggerError),
|
||||
#[error("Failed to gracefully shutdown http api: {0}")]
|
||||
HttpApiShutdown(#[source] vmm::Error),
|
||||
#[error("Failed to create Landlock object: {0}")]
|
||||
CreateLandlock(#[source] LandlockError),
|
||||
#[error("Failed to apply Landlock: {0}")]
|
||||
ApplyLandlock(#[source] LandlockError),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum FdTableError {
|
||||
#[error("Failed to create event fd: {0}")]
|
||||
CreateEventFd(std::io::Error),
|
||||
#[error("Failed to obtain file limit: {0}")]
|
||||
GetRLimit(std::io::Error),
|
||||
#[error("Error calling fcntl with F_GETFD: {0}")]
|
||||
GetFd(std::io::Error),
|
||||
#[error("Failed to duplicate file handle: {0}")]
|
||||
Dup2(std::io::Error),
|
||||
}
|
||||
|
||||
struct Logger {
|
||||
@@ -259,6 +276,24 @@ fn create_app(default_vcpus: String, default_memory: String, default_rng: String
|
||||
.num_args(1..)
|
||||
.group("vm-config"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("landlock")
|
||||
.long("landlock")
|
||||
.num_args(0)
|
||||
.help(
|
||||
"enable/disable Landlock.",
|
||||
)
|
||||
.action(ArgAction::SetTrue)
|
||||
.default_value("false")
|
||||
.group("vm-config"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("landlock-rules")
|
||||
.long("landlock-rules")
|
||||
.help(config::LandlockConfig::SYNTAX)
|
||||
.num_args(1..)
|
||||
.group("vm-config"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("net")
|
||||
.long("net")
|
||||
@@ -487,6 +522,16 @@ fn create_app(default_vcpus: String, default_memory: String, default_rng: String
|
||||
.num_args(1)
|
||||
.group("vm-config"),
|
||||
);
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
let app = app.arg(
|
||||
Arg::new("pvmemcontrol")
|
||||
.long("pvmemcontrol")
|
||||
.help("Pvmemcontrol device")
|
||||
.num_args(0)
|
||||
.action(ArgAction::SetTrue)
|
||||
.group("vm-config"),
|
||||
);
|
||||
|
||||
app.arg(
|
||||
Arg::new("version")
|
||||
.short('V')
|
||||
@@ -622,6 +667,7 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
|
||||
let vm_debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?;
|
||||
|
||||
let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateExitEventFd)?;
|
||||
let landlock_enable = cmd_arguments.get_flag("landlock");
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut event_monitor = cmd_arguments
|
||||
@@ -691,6 +737,7 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
|
||||
vmm::start_event_monitor_thread(
|
||||
monitor,
|
||||
&seccomp_action,
|
||||
landlock_enable,
|
||||
hypervisor.hypervisor_type(),
|
||||
exit_evt.try_clone().unwrap(),
|
||||
)
|
||||
@@ -717,6 +764,7 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
|
||||
exit_evt.try_clone().unwrap(),
|
||||
&seccomp_action,
|
||||
hypervisor,
|
||||
landlock_enable,
|
||||
)
|
||||
.map_err(Error::StartVmmThread)?;
|
||||
|
||||
@@ -764,6 +812,13 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
|
||||
}
|
||||
}
|
||||
|
||||
if landlock_enable {
|
||||
Landlock::new()
|
||||
.map_err(Error::CreateLandlock)?
|
||||
.restrict_self()
|
||||
.map_err(Error::ApplyLandlock)?;
|
||||
}
|
||||
|
||||
vmm_thread_handle
|
||||
.thread_handle
|
||||
.join()
|
||||
@@ -782,6 +837,77 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
|
||||
r.map(|_| api_socket_path)
|
||||
}
|
||||
|
||||
// This is a best-effort solution to the latency induced by the RCU
|
||||
// synchronization that happens in the kernel whenever the file descriptor table
|
||||
// fills up.
|
||||
// The table has initially 64 entries on amd64 and every time it fills up, a new
|
||||
// table is created, double the size of the current one, and the entries are
|
||||
// copied to the new table. The filesystem code that does this uses
|
||||
// synchronize_rcu() to ensure all preexisting RCU read-side critical sections
|
||||
// have completed:
|
||||
//
|
||||
// https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/fs/file.c?h=v6.9.1#n162
|
||||
//
|
||||
// Rust programs that create lots of file handles or use
|
||||
// {File,EventFd}::try_clone() to share them are impacted by this issue. This
|
||||
// behavior is quite noticeable in the snapshot restore scenario, the latency is
|
||||
// a big chunk of the total time required to start cloud-hypervisor and restore
|
||||
// the snapshot.
|
||||
//
|
||||
// The kernel has an optimization in code, where it doesn't call
|
||||
// synchronize_rcu() if there is only one thread in the process. We can take
|
||||
// advantage of this optimization by expanding the descriptor table at
|
||||
// application start, when it has only one thread.
|
||||
//
|
||||
// The code tries to resize the table to an adequate size for most use cases,
|
||||
// 4096, this way we avoid any expansion that might take place later.
|
||||
fn expand_fdtable() -> Result<(), FdTableError> {
|
||||
let mut limits = libc::rlimit {
|
||||
rlim_cur: 0,
|
||||
rlim_max: 0,
|
||||
};
|
||||
|
||||
// SAFETY: FFI call with valid arguments
|
||||
if unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) } < 0 {
|
||||
return Err(FdTableError::GetRLimit(io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
let table_size = if limits.rlim_cur == libc::RLIM_INFINITY {
|
||||
4096
|
||||
} else {
|
||||
std::cmp::min(limits.rlim_cur, 4096) as libc::c_int
|
||||
};
|
||||
|
||||
// The first 3 handles are stdin, stdout, stderr. We don't want to touch
|
||||
// any of them.
|
||||
if table_size <= 3 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let dummy_evt = EventFd::new(0).map_err(FdTableError::CreateEventFd)?;
|
||||
|
||||
// Test if the file descriptor is empty
|
||||
// SAFETY: FFI call with valid arguments
|
||||
let flags: i32 = unsafe { libc::fcntl(table_size - 1, libc::F_GETFD) };
|
||||
if flags >= 0 {
|
||||
// Nothing to do, the table is already big enough
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let err = io::Error::last_os_error();
|
||||
if err.raw_os_error() != Some(libc::EBADF) {
|
||||
return Err(FdTableError::GetFd(err));
|
||||
}
|
||||
// SAFETY: FFI call with valid arguments
|
||||
if unsafe { libc::dup2(dummy_evt.as_raw_fd(), table_size - 1) } < 0 {
|
||||
return Err(FdTableError::Dup2(io::Error::last_os_error()));
|
||||
}
|
||||
// SAFETY: FFI call, trivially
|
||||
unsafe { libc::close(table_size - 1) };
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(all(feature = "tdx", feature = "sev_snp"))]
|
||||
compile_error!("Feature 'tdx' and 'sev_snp' are mutually exclusive.");
|
||||
@@ -808,6 +934,10 @@ fn main() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(e) = expand_fdtable() {
|
||||
warn!("Error expanding FD table: {e}");
|
||||
}
|
||||
|
||||
let exit_code = match start_vmm(cmd_arguments) {
|
||||
Ok(path) => {
|
||||
path.map(|s| std::fs::remove_file(s).ok());
|
||||
@@ -934,6 +1064,8 @@ mod unit_tests {
|
||||
vdpa: None,
|
||||
vsock: None,
|
||||
pvpanic: false,
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pvmemcontrol: None,
|
||||
iommu: false,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
sgx_epc: None,
|
||||
@@ -945,6 +1077,8 @@ mod unit_tests {
|
||||
platform: None,
|
||||
tpm: None,
|
||||
preserved_fds: None,
|
||||
landlock_enable: false,
|
||||
landlock_rules: None,
|
||||
};
|
||||
|
||||
assert_eq!(expected_vm_config, result_vm_config);
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
[package]
|
||||
name = "test_infra"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
name = "test_infra"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
dirs = "5.0.1"
|
||||
epoll = "4.3.3"
|
||||
libc = "0.2.153"
|
||||
libc = "0.2.155"
|
||||
once_cell = "1.19.0"
|
||||
serde = { version = "1.0.197", features = ["rc", "derive"] }
|
||||
serde_json = "1.0.115"
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.120"
|
||||
ssh2 = { version = "0.9.4", features = ["vendored-openssl"] }
|
||||
vmm-sys-util = "0.12.1"
|
||||
wait-timeout = "0.2.0"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user