mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
255 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e8a7b6d07 | ||
|
|
2d7bfdd920 | ||
|
|
5822969afa | ||
|
|
defc33927f | ||
|
|
efbafdf9ed | ||
|
|
2b60452195 | ||
|
|
e8a25c22f6 | ||
|
|
b8be1dc24a | ||
|
|
83631599a3 | ||
|
|
78e2f7a99a | ||
|
|
205b8c1cd5 | ||
|
|
3acf9dfcf3 | ||
|
|
63c30a6e79 | ||
|
|
c65ead5de8 | ||
|
|
837bcbc6ba | ||
|
|
3598e603d5 | ||
|
|
34bb31791b | ||
|
|
9085a39c7d | ||
|
|
5fc3f37c9b | ||
|
|
3bb51d4d5e | ||
|
|
cc72ed1cc9 | ||
|
|
8663b429b3 | ||
|
|
df336ade57 | ||
|
|
9e78c2e686 | ||
|
|
786e33931f | ||
|
|
90d1083bda | ||
|
|
5ca068a068 | ||
|
|
2a0ba7aef8 | ||
|
|
097b30669f | ||
|
|
ef090cf37d | ||
|
|
db3ece8ef2 | ||
|
|
af41d6fc88 | ||
|
|
d2d3abb13c | ||
|
|
dbbd04a4cf | ||
|
|
4ac0cb9cff | ||
|
|
80c3fd922a | ||
|
|
37a7000fdd | ||
|
|
6e9e24ec0f | ||
|
|
1fc8ee945a | ||
|
|
cb59f826ad | ||
|
|
688ec0eb2d | ||
|
|
8e018d6feb | ||
|
|
c446b9d510 | ||
|
|
6df7cd0e4b | ||
|
|
3a04db5936 | ||
|
|
ad7d02cd74 | ||
|
|
1298b508bf | ||
|
|
a95fa1c4e8 | ||
|
|
228adebc32 | ||
|
|
14eb071b29 | ||
|
|
5652cc7a05 | ||
|
|
2c50c963f5 | ||
|
|
46848fdc43 | ||
|
|
b918220b49 | ||
|
|
278ab05cbc | ||
|
|
32d07e40cc | ||
|
|
63869bde75 | ||
|
|
fb4769388b | ||
|
|
20c4ed829a | ||
|
|
4b8d7e718d | ||
|
|
6e0aa56f06 | ||
|
|
03352f45f9 | ||
|
|
f40adff2a1 | ||
|
|
0acb1e329d | ||
|
|
6566c739e1 | ||
|
|
9ab00dcb75 | ||
|
|
ee1899c6f6 | ||
|
|
392f1ec155 | ||
|
|
9fad680db1 | ||
|
|
9ebb1a55bc | ||
|
|
85e1865cb5 | ||
|
|
4b1328a29c | ||
|
|
8225d4cd6e | ||
|
|
dac7737919 | ||
|
|
3e750de43f | ||
|
|
eb91bc812b | ||
|
|
59b4aaba87 | ||
|
|
8288cb2ac8 | ||
|
|
2a466132a0 | ||
|
|
fc5c210498 | ||
|
|
8dbb16df4d | ||
|
|
7abbad0a62 | ||
|
|
7328ecdb3b | ||
|
|
f9daf2e247 | ||
|
|
43b3642955 | ||
|
|
42758244a0 | ||
|
|
27af983ec9 | ||
|
|
b70344158b | ||
|
|
7e0cb078ed | ||
|
|
9a93f4f0a6 | ||
|
|
c505cfae2b | ||
|
|
8a5e47f989 | ||
|
|
46cde1a38e | ||
|
|
ce0b475ef7 | ||
|
|
f674019ea1 | ||
|
|
f2de4d0315 | ||
|
|
6a722e5c0b | ||
|
|
aa31748781 | ||
|
|
b14fd37db9 | ||
|
|
f27aa21e3f | ||
|
|
fe5561df50 | ||
|
|
2371325f9c | ||
|
|
e50f4418a2 | ||
|
|
8916dad2da | ||
|
|
1b66a2fa60 | ||
|
|
8ea4145f98 | ||
|
|
47367eb61b | ||
|
|
df3e5c874f | ||
|
|
8ec6cda0c5 | ||
|
|
8c33eb3069 | ||
|
|
f6d1a9d9b8 | ||
|
|
d724511a91 | ||
|
|
9ff42060e0 | ||
|
|
db151819f1 | ||
|
|
9356af80c6 | ||
|
|
6a4a931b9e | ||
|
|
038f198129 | ||
|
|
833a3d456c | ||
|
|
c0ca3b6b8e | ||
|
|
a9eb352aea | ||
|
|
09392f0530 | ||
|
|
527dd68ce1 | ||
|
|
4a1af7f63c | ||
|
|
c4309515c9 | ||
|
|
347611b0c7 | ||
|
|
f14ab872ec | ||
|
|
cfc8c39446 | ||
|
|
1aab372a06 | ||
|
|
c1b26b1fab | ||
|
|
d80ac43ef1 | ||
|
|
5f076923d3 | ||
|
|
36de390caf | ||
|
|
d4f7f73bc8 | ||
|
|
4ed81894aa | ||
|
|
2e2cad91ae | ||
|
|
b5ee9212c1 | ||
|
|
162791b571 | ||
|
|
8207b2e97d | ||
|
|
a0455167d0 | ||
|
|
1bc47507b7 | ||
|
|
5ba61f6d5e | ||
|
|
6d6e290000 | ||
|
|
0e7a1fc923 | ||
|
|
f63cb85f93 | ||
|
|
ff1cb11946 | ||
|
|
df2516f229 | ||
|
|
2ae3919181 | ||
|
|
8188074300 | ||
|
|
e235c6de4f | ||
|
|
151f96e454 | ||
|
|
2f1ff23066 | ||
|
|
4671a5831f | ||
|
|
03ab6839c1 | ||
|
|
6710a39b5a | ||
|
|
feb1c33084 | ||
|
|
47167a658e | ||
|
|
ea7abc6c80 | ||
|
|
e6ef9ece2c | ||
|
|
2e9d815701 | ||
|
|
2e0f1c2afe | ||
|
|
e869283b4e | ||
|
|
bdfd1a3f38 | ||
|
|
9c5135da7a | ||
|
|
b79c1f7722 | ||
|
|
acc60b0ad5 | ||
|
|
3dc7aff00e | ||
|
|
5f8a62f3d0 | ||
|
|
36137232f0 | ||
|
|
79a02f9171 | ||
|
|
00674cd850 | ||
|
|
5323da031c | ||
|
|
0688bec298 | ||
|
|
675e46355c | ||
|
|
036890e5be | ||
|
|
9c5bfb8e13 | ||
|
|
dbff0e94b8 | ||
|
|
0c8f9d2768 | ||
|
|
f06b2aaaa7 | ||
|
|
a2f3da3488 | ||
|
|
2cd406ba50 | ||
|
|
29b3848ffb | ||
|
|
d723b7dae8 | ||
|
|
0a229ef4f5 | ||
|
|
6b06cec611 | ||
|
|
360980d93c | ||
|
|
39083d705b | ||
|
|
a949ab21f7 | ||
|
|
4164853ec6 | ||
|
|
c7559bb7a4 | ||
|
|
397d388710 | ||
|
|
b232de9963 | ||
|
|
927148dd3c | ||
|
|
c347f84d74 | ||
|
|
0a0c7358a2 | ||
|
|
5f0337c21d | ||
|
|
b488d4859b | ||
|
|
5b3ca78dac | ||
|
|
180e6d1e78 | ||
|
|
f0360c92d9 | ||
|
|
f9b0875a60 | ||
|
|
bf4f3db6c4 | ||
|
|
eb60106159 | ||
|
|
67ef4f0d74 | ||
|
|
f622a76597 | ||
|
|
3bc11a4a2e | ||
|
|
4df5ebea12 | ||
|
|
0739c2c7fd | ||
|
|
7358144f09 | ||
|
|
3567206059 | ||
|
|
ea7f4d7f0b | ||
|
|
c0fede0774 | ||
|
|
1097afbaff | ||
|
|
ccb83282e9 | ||
|
|
3ad4b8486f | ||
|
|
1099f0726b | ||
|
|
26974c7625 | ||
|
|
c042483953 | ||
|
|
6d27ac9dfc | ||
|
|
3df1680888 | ||
|
|
f5a44ea1ad | ||
|
|
e950aa6b9a | ||
|
|
05b5115e67 | ||
|
|
7edc46f492 | ||
|
|
7c6ef7fd63 | ||
|
|
bf37b960ec | ||
|
|
4d86359a09 | ||
|
|
5e5c2f2c48 | ||
|
|
a9b2207bcf | ||
|
|
40fc6c3f0f | ||
|
|
389f9e3779 | ||
|
|
8f37dec498 | ||
|
|
c00b58de9b | ||
|
|
5f752dade7 | ||
|
|
8e7d67fcfe | ||
|
|
b515d48eca | ||
|
|
eb46aa2b22 | ||
|
|
7975394901 | ||
|
|
5a3472847d | ||
|
|
475e487ac3 | ||
|
|
434a5d0edf | ||
|
|
df61a8fea2 | ||
|
|
22f91ab3a2 | ||
|
|
4ccc81fdf9 | ||
|
|
11e7ece9f5 | ||
|
|
c48ca61417 | ||
|
|
69e27288a2 | ||
|
|
d2db34edf2 | ||
|
|
d089ee4e25 | ||
|
|
2f4de81175 | ||
|
|
9ac967e3d8 | ||
|
|
f740a3568d | ||
|
|
e21bc972b0 | ||
|
|
d446a8217a | ||
|
|
2432ad07ea | ||
|
|
9661e8da5d |
10
.travis.yml
10
.travis.yml
@@ -8,8 +8,14 @@ before_script:
|
||||
- rustup component add rustfmt
|
||||
|
||||
script:
|
||||
- cargo build --release
|
||||
- cargo build --release --no-default-features
|
||||
- cargo rustc --bin cloud-hypervisor -- -D warnings
|
||||
- cargo rustc --bin cloud-hypervisor --no-default-features --features "pci" -- -D warnings
|
||||
- cargo rustc --bin cloud-hypervisor --no-default-features --features "pci,acpi" -- -D warnings
|
||||
- cargo rustc --bin cloud-hypervisor --no-default-features --features "mmio" -- -D warnings
|
||||
- cargo rustc --bin vhost_user_net -- -D warnings
|
||||
- cargo rustc --bin vhost_user_net --no-default-features --features "pci" -- -D warnings
|
||||
- cargo rustc --bin vhost_user_net --no-default-features --features "pci,acpi" -- -D warnings
|
||||
- cargo rustc --bin vhost_user_net --no-default-features --features "mmio" -- -D warnings
|
||||
- cargo test
|
||||
- cargo clippy --all-targets --all-features -- -D warnings
|
||||
- find . -name "*.rs" | xargs rustfmt --check
|
||||
|
||||
411
Cargo.lock
generated
411
Cargo.lock
generated
@@ -25,7 +25,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -37,7 +37,7 @@ dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kvm-ioctls 0.2.0 (git+https://github.com/rust-vmm/kvm-ioctls)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"linux-loader 0.1.0 (git+https://github.com/rust-vmm/linux-loader)",
|
||||
"rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
|
||||
@@ -54,10 +54,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.4.11"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -65,7 +65,7 @@ name = "atty"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -76,22 +76,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.37"
|
||||
version = "0.3.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.31"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -107,11 +107,6 @@ name = "bitflags"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.1.0"
|
||||
@@ -119,11 +114,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "blake2b_simd"
|
||||
version = "0.5.7"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -148,12 +143,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.41"
|
||||
version = "1.0.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.9"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -177,11 +172,22 @@ dependencies = [
|
||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"credibility 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"epoll 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ssh2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net_gen 0.1.0",
|
||||
"net_util 0.1.0",
|
||||
"ssh2 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vhost_rs 0.1.0",
|
||||
"vhost_user_backend 0.1.0",
|
||||
"virtio-bindings 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vm-device 0.1.0",
|
||||
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
|
||||
"vm-virtio 0.1.0",
|
||||
"vmm 0.1.0",
|
||||
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -202,8 +208,8 @@ name = "credibility"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -211,7 +217,7 @@ name = "crossbeam-utils"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -223,11 +229,11 @@ dependencies = [
|
||||
"epoll 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kvm-ioctls 0.2.0 (git+https://github.com/rust-vmm/kvm-ioctls)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -235,7 +241,7 @@ name = "dirs"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -244,8 +250,8 @@ name = "dirs-sys"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -256,27 +262,27 @@ version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure_derive"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -286,12 +292,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.11"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wasi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -304,7 +310,7 @@ name = "ipnetwork"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -332,7 +338,7 @@ version = "0.2.0"
|
||||
source = "git+https://github.com/rust-vmm/kvm-ioctls#30adb021584845eed4124bc8ef3b2b96cd9e159f"
|
||||
dependencies = [
|
||||
"kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -342,19 +348,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.62"
|
||||
version = "0.2.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libssh2-sys"
|
||||
version = "0.2.11"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl-sys 0.9.51 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -363,16 +369,16 @@ name = "libz-sys"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-loader"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/linux-loader#b2700818add982aa361842a0b8da776b3d1b5cb3"
|
||||
source = "git+https://github.com/rust-vmm/linux-loader#6cf23a8bca79b5517fa11f90f7f9d828a8290e9d"
|
||||
dependencies = [
|
||||
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
|
||||
]
|
||||
@@ -390,7 +396,7 @@ name = "log"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -398,11 +404,16 @@ name = "memchr"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "micro_http"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/firecracker-microvm/firecracker#2b94334a6a2a5f7dd07ca94f37cc4d9bdbf57ce6"
|
||||
|
||||
[[package]]
|
||||
name = "net_gen"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -410,29 +421,37 @@ name = "net_util"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net_gen 0.1.0",
|
||||
"pnet 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.13"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.49"
|
||||
version = "0.9.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -444,16 +463,16 @@ dependencies = [
|
||||
"devices 0.1.0",
|
||||
"kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kvm-ioctls 0.2.0 (git+https://github.com/rust-vmm/kvm-ioctls)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vm-allocator 0.1.0",
|
||||
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.15"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -480,7 +499,7 @@ version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ipnetwork 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pnet_base 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pnet_sys 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -521,7 +540,7 @@ name = "pnet_sys"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -531,7 +550,7 @@ name = "pnet_transport"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pnet_base 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pnet_packet 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pnet_sys 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -544,15 +563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.1"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -563,19 +574,11 @@ name = "qcow"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"remain 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.6.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -583,7 +586,7 @@ name = "quote"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -592,7 +595,7 @@ version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -603,8 +606,8 @@ name = "rand"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"getrandom 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -637,7 +640,7 @@ name = "rand_core"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"getrandom 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -655,7 +658,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -679,7 +682,7 @@ name = "redox_users"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -707,9 +710,9 @@ name = "remain"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -726,7 +729,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"blake2b_simd 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -742,22 +745,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.99"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.40"
|
||||
version = "1.0.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -765,7 +778,7 @@ name = "signal-hook"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"signal-hook-registry 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -774,18 +787,18 @@ name = "signal-hook-registry"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"arc-swap 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"arc-swap 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ssh2"
|
||||
version = "0.3.3"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libssh2-sys 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libssh2-sys 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -795,33 +808,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.15.44"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.10.2"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -838,7 +841,7 @@ name = "syntex_errors"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syntex_pos 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -860,7 +863,7 @@ version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syntex_errors 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -883,8 +886,8 @@ name = "tempfile"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -916,6 +919,14 @@ dependencies = [
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "threadpool"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.6"
|
||||
@@ -926,11 +937,6 @@ name = "unicode-xid"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.0"
|
||||
@@ -959,20 +965,22 @@ dependencies = [
|
||||
"devices 0.1.0",
|
||||
"kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kvm-ioctls 0.2.0 (git+https://github.com/rust-vmm/kvm-ioctls)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pci 0.1.0",
|
||||
"vfio-bindings 0.0.1",
|
||||
"vfio-bindings 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vm-allocator 0.1.0",
|
||||
"vm-device 0.1.0",
|
||||
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vfio-bindings"
|
||||
version = "0.0.1"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -980,31 +988,53 @@ name = "vhost_rs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vhost_user_backend"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"epoll 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vhost_rs 0.1.0",
|
||||
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
|
||||
"vm-virtio 0.1.0",
|
||||
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtio-bindings"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/virtio-bindings#c5676476f1048d212f53ca80711d2b132dcebe23"
|
||||
|
||||
[[package]]
|
||||
name = "virtio-bindings"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "vm-allocator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vm-device"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "vm-memory"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/vm-memory#8669369d17a470d72743fc616a26ee6b95f230f5"
|
||||
dependencies = [
|
||||
"cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -1015,17 +1045,18 @@ dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"devices 0.1.0",
|
||||
"epoll 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net_gen 0.1.0",
|
||||
"net_util 0.1.0",
|
||||
"pci 0.1.0",
|
||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vhost_rs 0.1.0",
|
||||
"virtio-bindings 0.1.0",
|
||||
"virtio-bindings 0.1.0 (git+https://github.com/rust-vmm/virtio-bindings)",
|
||||
"vm-allocator 0.1.0",
|
||||
"vm-device 0.1.0",
|
||||
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1038,31 +1069,37 @@ dependencies = [
|
||||
"epoll 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kvm-ioctls 0.2.0 (git+https://github.com/rust-vmm/kvm-ioctls)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"linux-loader 0.1.0 (git+https://github.com/rust-vmm/linux-loader)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"micro_http 0.1.0 (git+https://github.com/firecracker-microvm/firecracker)",
|
||||
"net_util 0.1.0",
|
||||
"pci 0.1.0",
|
||||
"qcow 0.1.0",
|
||||
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vfio 0.0.1",
|
||||
"vm-allocator 0.1.0",
|
||||
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
|
||||
"vm-virtio 0.1.0",
|
||||
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
|
||||
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vmm-sys-util"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/vmm-sys-util#fd4dcd172e5e607bb480ff8c9004c90f6671043f"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.5.0"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -1106,23 +1143,22 @@ dependencies = [
|
||||
[metadata]
|
||||
"checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5"
|
||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
"checksum arc-swap 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "854ede29f7a0ce90519fb2439d030320c6201119b87dab0ee96044603e1130b9"
|
||||
"checksum arc-swap 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f1a1eca3195b729bbd64e292ef2f5fff6b1c28504fed762ce2b1013dde4d8e92"
|
||||
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
|
||||
"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba"
|
||||
"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
|
||||
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
|
||||
"checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875"
|
||||
"checksum backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "5180c5a20655b14a819b652fd2378fa5f1697b6c9ddad3e695c2f9cedf6df4e2"
|
||||
"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b"
|
||||
"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea"
|
||||
"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
|
||||
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
|
||||
"checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23"
|
||||
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
|
||||
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
|
||||
"checksum blake2b_simd 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bf775a81bb2d464e20ff170ac20316c7b08a43d11dbc72f0f82e8e8d3d6d0499"
|
||||
"checksum blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5850aeee1552f495dd0250014cf64b82b7c8879a89d83b33bbdace2cc4f63182"
|
||||
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
|
||||
"checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101"
|
||||
"checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427"
|
||||
"checksum cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "8dae9c4b8fedcae85592ba623c4fd08cfdab3e3b72d6df780c6ead964a69bfff"
|
||||
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
|
||||
"checksum cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)" = "0213d356d3c4ea2c18c40b037c3be23cd639825c18f25ee670ac7813beeef99c"
|
||||
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
|
||||
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||
"checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120"
|
||||
@@ -1131,10 +1167,10 @@ dependencies = [
|
||||
"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
|
||||
"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
|
||||
"checksum epoll 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "990bcfe26bea89669ede68c3f970f61d02568dbc8660317c98d805ea4e710685"
|
||||
"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
|
||||
"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
|
||||
"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9"
|
||||
"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08"
|
||||
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
"checksum getrandom 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "fc344b02d3868feb131e8b5fe2b9b0a1cc42942679af493061fc13b853243872"
|
||||
"checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571"
|
||||
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
||||
"checksum ipnetwork 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3d862c86f7867f19b693ec86765e0252d82e53d4240b9b629815675a0714ad1"
|
||||
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
|
||||
@@ -1142,16 +1178,18 @@ dependencies = [
|
||||
"checksum kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c223e8703d2eb76d990c5f58e29c85b0f6f50e24b823babde927948e7c71fc03"
|
||||
"checksum kvm-ioctls 0.2.0 (git+https://github.com/rust-vmm/kvm-ioctls)" = "<none>"
|
||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
|
||||
"checksum libssh2-sys 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "126a1f4078368b163bfdee65fbab072af08a1b374a5551b21e87ade27b1fbf9d"
|
||||
"checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8"
|
||||
"checksum libssh2-sys 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "8914d10b159fc288f2b6f253c94bd0c15a777fd5a297691141d89674b87e66fd"
|
||||
"checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe"
|
||||
"checksum linux-loader 0.1.0 (git+https://github.com/rust-vmm/linux-loader)" = "<none>"
|
||||
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
|
||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
|
||||
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
|
||||
"checksum openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)" = "f4fad9e54bd23bd4cbbe48fdc08a1b8091707ac869ef8508edea2fec77dcc884"
|
||||
"checksum pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c1d2cfa5a714db3b5f24f0915e74fcdf91d09d496ba61329705dda7774d2af"
|
||||
"checksum micro_http 0.1.0 (git+https://github.com/firecracker-microvm/firecracker)" = "<none>"
|
||||
"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||
"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273"
|
||||
"checksum openssl-sys 0.9.51 (registry+https://github.com/rust-lang/crates.io-index)" = "ba24190c8f0805d3bd2ce028f439fe5af1d55882bbe6261bed1dbc93b50dd6b1"
|
||||
"checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea"
|
||||
"checksum pnet 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63d693c84430248366146e3181ff9d330243464fa9e6146c372b2f3eb2e2d8e7"
|
||||
"checksum pnet_base 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4df28acf2fcc77436dd2b91a9a0c2bb617f9ca5f2acefee1a4135058b9f9801f"
|
||||
"checksum pnet_datalink 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b34f8ca857599d05b6b082e9baff8d27c54cb9c26568cf3c0993a5755816966"
|
||||
@@ -1161,9 +1199,7 @@ dependencies = [
|
||||
"checksum pnet_sys 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "682b2eca84cc440bce8336813f78eb6d3cb0fed89fe0e87ae22acfca8363f176"
|
||||
"checksum pnet_transport 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5faa55dcf725487a699adcff88dfea8f17ea34fa2640528866d9acbb4e3a104f"
|
||||
"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b"
|
||||
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
|
||||
"checksum proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5c2380ae88876faae57698be9e9775e3544decad214599c3a6266cca6ac802"
|
||||
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
|
||||
"checksum proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0"
|
||||
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
||||
"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
||||
"checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c"
|
||||
@@ -1183,16 +1219,16 @@ dependencies = [
|
||||
"checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf"
|
||||
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
||||
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997"
|
||||
"checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f"
|
||||
"checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704"
|
||||
"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
|
||||
"checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd"
|
||||
"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e"
|
||||
"checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2"
|
||||
"checksum signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4f61c4d59f3aaa9f61bba6450a9b80ba48362fd7d651689e7a10c453b1f6dc68"
|
||||
"checksum signal-hook-registry 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1797d48f38f91643908bb14e35e79928f9f4b3cefb2420a564dde0991b4358dc"
|
||||
"checksum ssh2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "dee822d619a700f98c4de3b5931f272ecc7cf2e924ceb2df47b61df4ae033a0c"
|
||||
"checksum ssh2 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "875fc74ffc41049e306a247f4191dd9c2ebfb7c5e0a6e4ddfdccacaba732e8aa"
|
||||
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
|
||||
"checksum syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "158521e6f544e7e3dcfc370ac180794aa38cb34a1b1e07609376d4adcf429b93"
|
||||
"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f"
|
||||
"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"
|
||||
"checksum synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f085a5855930c0441ca1288cf044ea4aecf4f43a91668abdb870b4ba546a203"
|
||||
"checksum syntex 0.42.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0a30b08a6b383a22e5f6edc127d169670d48f905bb00ca79a00ea3e442ebe317"
|
||||
"checksum syntex_errors 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)" = "04c48f32867b6114449155b2a82114b86d4b09e1bddb21c47ff104ab9172b646"
|
||||
"checksum syntex_pos 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd49988e52451813c61fecbe9abb5cfd4e1b7bb6cdbb980a6fbcbab859171a6"
|
||||
@@ -1202,16 +1238,19 @@ dependencies = [
|
||||
"checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1"
|
||||
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
|
||||
"checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865"
|
||||
"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
|
||||
"checksum unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "36dff09cafb4ec7c8cf0023eb0b686cb6ce65499116a12201c9e11840ca01beb"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
"checksum utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba"
|
||||
"checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95"
|
||||
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
||||
"checksum vfio-bindings 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df0379abaedb35cac2636dd2ee75496ab921430fa5775b90f13bbda3e2abaf66"
|
||||
"checksum virtio-bindings 0.1.0 (git+https://github.com/rust-vmm/virtio-bindings)" = "<none>"
|
||||
"checksum virtio-bindings 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ff512178285488516ed85f15b5d0113a7cdb89e9e8a760b269ae4f02b84bd6b"
|
||||
"checksum vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)" = "<none>"
|
||||
"checksum vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)" = "<none>"
|
||||
"checksum wasi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fd5442abcac6525a045cc8c795aedb60da7a2e5e89c7bf18a0d5357849bb23c7"
|
||||
"checksum vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "46996f56aeae31fbc0532ae57a944e00089302f03b18c10c76eebfd9249f4a6c"
|
||||
"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"
|
||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770"
|
||||
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||
|
||||
25
Cargo.toml
25
Cargo.toml
@@ -6,21 +6,42 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
epoll = "4.1.0"
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.65"
|
||||
log = { version = "0.4.8", features = ["std"] }
|
||||
net_gen = { path = "net_gen" }
|
||||
net_util = { path = "net_util" }
|
||||
vhost_user_backend = { path = "vhost_user_backend"}
|
||||
virtio-bindings = "0.1.0"
|
||||
vmm = { path = "vmm" }
|
||||
vm-device = { path = "vm-device" }
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
vm-virtio = { path = "vm-virtio" }
|
||||
|
||||
[dev-dependencies]
|
||||
ssh2 = "0.3.3"
|
||||
ssh2 = "0.4.0"
|
||||
dirs = "2.0.2"
|
||||
credibility = "0.1.3"
|
||||
tempdir= "0.3.7"
|
||||
lazy_static= "1.4.0"
|
||||
|
||||
[dependencies.vhost_rs]
|
||||
path = "vhost_rs"
|
||||
features = ["vhost-user-slave"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["acpi", "pci", "cmos"]
|
||||
acpi = ["vmm/acpi"]
|
||||
pci = ["vmm/pci_support"]
|
||||
mmio = ["vmm/mmio_support"]
|
||||
cmos = ["vmm/cmos"]
|
||||
|
||||
# Integration tests require a special environment to run in
|
||||
integration_tests = []
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"vhost_user_backend",
|
||||
]
|
||||
|
||||
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@@ -5,7 +5,7 @@ stage ("Builds") {
|
||||
}
|
||||
stage ('Install system packages') {
|
||||
sh "sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq build-essential mtools libssl-dev pkg-config"
|
||||
sh "sudo apt-get install -yq flex bison libelf-dev qemu-utils qemu-system libglib2.0-dev libpixman-1-dev libseccomp-dev"
|
||||
sh "sudo apt-get install -yq flex bison libelf-dev qemu-utils qemu-system libglib2.0-dev libpixman-1-dev libseccomp-dev socat"
|
||||
}
|
||||
stage ('Install Rust') {
|
||||
sh "nohup curl https://sh.rustup.rs -sSf | sh -s -- -y"
|
||||
|
||||
12
README.md
12
README.md
@@ -104,7 +104,7 @@ We need to get the latest `rust-hypervisor-firmware` release and also a working
|
||||
$ pushd $CLOUDH
|
||||
$ wget https://download.clearlinux.org/releases/29160/clear/clear-29160-kvm.img.xz
|
||||
$ unxz clear-29160-kvm.img.xz
|
||||
$ wget https://github.com/intel/rust-hypervisor-firmware/releases/download/0.1.0/hypervisor-fw
|
||||
$ wget https://github.com/intel/rust-hypervisor-firmware/releases/download/0.2.0/hypervisor-fw
|
||||
$ popd
|
||||
```
|
||||
|
||||
@@ -113,7 +113,7 @@ $ pushd $CLOUDH
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel ./hypervisor-fw \
|
||||
--disk ./clear-29160-kvm.img \
|
||||
--disk path=clear-29160-kvm.img \
|
||||
--cpus 4 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
@@ -121,7 +121,7 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
$ popd
|
||||
```
|
||||
|
||||
Multiple arguments can be given to the `--disk` parameter, currently the firmware requires that the bootable image is on the first disk.
|
||||
Multiple arguments can be given to the `--disk` parameter.
|
||||
|
||||
### Custom kernel and disk image
|
||||
|
||||
@@ -167,7 +167,7 @@ $ pushd $CLOUDH
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--disk ./clear-29160-kvm.img \
|
||||
--disk path=clear-29160-kvm.img \
|
||||
--cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||
--cpus 4 \
|
||||
--memory size=1024M \
|
||||
@@ -187,7 +187,7 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--console off \
|
||||
--serial tty \
|
||||
--disk ./clear-29160-kvm.img \
|
||||
--disk path=clear-29160-kvm.img \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||
--cpus 4 \
|
||||
--memory size=1024M \
|
||||
@@ -255,7 +255,7 @@ etc, are all equal and welcome means of contribution. See the [CONTRIBUTING](CON
|
||||
|
||||
## Join us
|
||||
|
||||
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LTc0YzlmYzQxZDkxNDVhYzZjZjA5MTkxMGY3NTI3YzMzYTFkM2IyY2E0YTIxMzkyYTEwYzdlMzBhMWYxYzVmNDI)
|
||||
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LWQ1MTA1ZDVmODkwMWQ1MTRhYzk4ZGNlN2UwNTI3ZmFlODU0OTcwOWZjMTkwZDExYWE3YjFmNzgzY2FmNDAyMjI)
|
||||
and [join us on Slack](https://cloud-hypervisor.slack.com/).
|
||||
|
||||
# 6. Security
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[features]
|
||||
default = ["acpi"]
|
||||
default = []
|
||||
acpi = ["acpi_tables"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -17,6 +17,7 @@ pub fn configure_system(
|
||||
_cmdline_size: usize,
|
||||
_num_cpus: u8,
|
||||
_serial_enabled: bool,
|
||||
_virt_iommu: Option<(u32, &[u32])>,
|
||||
) -> super::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ extern crate byteorder;
|
||||
extern crate kvm_bindings;
|
||||
extern crate libc;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
extern crate acpi_tables;
|
||||
extern crate arch_gen;
|
||||
extern crate kvm_ioctls;
|
||||
@@ -20,7 +21,6 @@ extern crate linux_loader;
|
||||
extern crate vm_memory;
|
||||
|
||||
use std::result;
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
@@ -52,9 +52,6 @@ pub enum RegionType {
|
||||
Reserved,
|
||||
}
|
||||
|
||||
// 1MB. We don't put anything above here except the kernel itself.
|
||||
pub const HIMEM_START: GuestAddress = GuestAddress(0x100000);
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod aarch64;
|
||||
|
||||
@@ -69,6 +66,5 @@ pub mod x86_64;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::{
|
||||
arch_memory_regions, configure_system, get_32bit_gap_start as get_reserved_mem_addr,
|
||||
layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START,
|
||||
arch_memory_regions, configure_system, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START,
|
||||
};
|
||||
|
||||
@@ -10,6 +10,10 @@ use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
|
||||
use vm_memory::{Address, ByteValued, Bytes};
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use super::layout;
|
||||
|
||||
#[repr(packed)]
|
||||
struct LocalAPIC {
|
||||
pub r#type: u8,
|
||||
@@ -51,12 +55,67 @@ struct PCIRangeEntry {
|
||||
_reserved: u32,
|
||||
}
|
||||
|
||||
pub fn create_dsdt_table(serial_enabled: bool) -> SDT {
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct IortParavirtIommuNode {
|
||||
pub type_: u8,
|
||||
pub length: u16,
|
||||
pub revision: u8,
|
||||
_reserved1: u32,
|
||||
pub num_id_mappings: u32,
|
||||
pub ref_id_mappings: u32,
|
||||
pub device_id: u32,
|
||||
_reserved2: [u32; 3],
|
||||
pub model: u32,
|
||||
pub flags: u32,
|
||||
_reserved3: [u32; 4],
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct IortPciRootComplexNode {
|
||||
pub type_: u8,
|
||||
pub length: u16,
|
||||
pub revision: u8,
|
||||
_reserved1: u32,
|
||||
pub num_id_mappings: u32,
|
||||
pub ref_id_mappings: u32,
|
||||
pub mem_access_props: IortMemoryAccessProperties,
|
||||
pub ats_attr: u32,
|
||||
pub pci_seg_num: u32,
|
||||
pub mem_addr_size_limit: u8,
|
||||
_reserved2: [u8; 3],
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct IortMemoryAccessProperties {
|
||||
pub cca: u32,
|
||||
pub ah: u8,
|
||||
_reserved: u16,
|
||||
pub maf: u8,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct IortIdMapping {
|
||||
pub input_base: u32,
|
||||
pub num_of_ids: u32,
|
||||
pub ouput_base: u32,
|
||||
pub output_ref: u32,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
pub fn create_dsdt_table(
|
||||
serial_enabled: bool,
|
||||
start_of_device_area: GuestAddress,
|
||||
end_of_device_area: GuestAddress,
|
||||
) -> SDT {
|
||||
/*
|
||||
The hex tables in this file are generated from the ASL below with:
|
||||
"iasl -tc <dsdt.asl>"
|
||||
|
||||
As the output contains a table header that is not required the first 40 bytes
|
||||
As the output contains a table header that is not required the first 36 bytes
|
||||
should be disregarded.
|
||||
*/
|
||||
|
||||
@@ -112,9 +171,9 @@ pub fn create_dsdt_table(serial_enabled: bool) -> SDT {
|
||||
DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, NonCacheable, ReadWrite,
|
||||
0x00000000, // Granularity
|
||||
0xC0000000, // Range Minimum
|
||||
0xFEC00000, // Range Maximum
|
||||
0xFEBFFFFF, // Range Maximum
|
||||
0x00000000, // Translation Offset
|
||||
0x3EC00001, // Length
|
||||
0x3EC00000, // Length
|
||||
,, , AddressRangeMemory, TypeStatic)
|
||||
QWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
|
||||
0x0000000000000000, // Granularity
|
||||
@@ -126,7 +185,7 @@ pub fn create_dsdt_table(serial_enabled: bool) -> SDT {
|
||||
})
|
||||
}
|
||||
*/
|
||||
let pci_dsdt_data = [
|
||||
let mut pci_dsdt_data = [
|
||||
0x5Bu8, 0x82, 0x36, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x50, 0x43, 0x49, 0x30, 0x08, 0x5F, 0x48,
|
||||
0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0A, 0x08, 0x08, 0x5F, 0x43, 0x49, 0x44, 0x0C, 0x41, 0xD0,
|
||||
0x0A, 0x03, 0x08, 0x5F, 0x41, 0x44, 0x52, 0x00, 0x08, 0x5F, 0x53, 0x45, 0x47, 0x00, 0x08,
|
||||
@@ -138,13 +197,57 @@ pub fn create_dsdt_table(serial_enabled: bool) -> SDT {
|
||||
0x00, 0x01, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xF3, 0x87,
|
||||
0x17, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xFF, 0xFF,
|
||||
0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x87, 0x17, 0x00, 0x00, 0x0C,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0xFE, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0xC0, 0x3E, 0x8A, 0x2B, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0xBF, 0xFE, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xC0, 0x3E, 0x8A, 0x2B, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x79, 0x00,
|
||||
];
|
||||
|
||||
// Patch Range Minimum/Range Maximum/Length for the the 64-bit device area
|
||||
pci_dsdt_data[170..174].copy_from_slice(&layout::MEM_32BIT_DEVICES_START.0.to_le_bytes()[0..4]);
|
||||
pci_dsdt_data[174..178].copy_from_slice(
|
||||
&(layout::MEM_32BIT_DEVICES_START.0 + layout::MEM_32BIT_DEVICES_SIZE - 1).to_le_bytes()
|
||||
[0..4],
|
||||
);
|
||||
pci_dsdt_data[182..186].copy_from_slice(&layout::MEM_32BIT_DEVICES_SIZE.to_le_bytes()[0..4]);
|
||||
|
||||
// Patch the Range Minimum/Range Maximum/Length for the the 64-bit device area
|
||||
pci_dsdt_data[200..208].copy_from_slice(&(start_of_device_area.0).to_le_bytes());
|
||||
pci_dsdt_data[208..216].copy_from_slice(&end_of_device_area.0.to_le_bytes());
|
||||
pci_dsdt_data[224..232].copy_from_slice(
|
||||
&(end_of_device_area.unchecked_offset_from(start_of_device_area) + 1).to_le_bytes(),
|
||||
);
|
||||
|
||||
/*
|
||||
Device (_SB.MBRD)
|
||||
{
|
||||
Name (_HID, EisaId ("PNP0C02") /* PNP Motherboard Resources */) // _HID: Hardware ID
|
||||
Name (_UID, Zero) // _UID: Unique ID
|
||||
}
|
||||
|
||||
Scope (_SB.MBRD)
|
||||
{
|
||||
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
|
||||
{
|
||||
Memory32Fixed (ReadWrite,
|
||||
0xE8000000, // Address Base
|
||||
0x10000000, // Address Length
|
||||
)
|
||||
})
|
||||
}
|
||||
*/
|
||||
let mut mbrd_dsdt_data = [
|
||||
0x5Bu8, 0x82, 0x1A, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x4D, 0x42, 0x52, 0x44, 0x08, 0x5F, 0x48,
|
||||
0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0C, 0x02, 0x08, 0x5F, 0x55, 0x49, 0x44, 0x00, 0x10, 0x21,
|
||||
0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x4D, 0x42, 0x52, 0x44, 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11,
|
||||
0x11, 0x0A, 0x0E, 0x86, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00, 0xE8, 0x00, 0x00, 0x00, 0x10,
|
||||
0x79, 0x00,
|
||||
];
|
||||
|
||||
mbrd_dsdt_data[52..56].copy_from_slice(&layout::PCI_MMCONFIG_START.0.to_le_bytes()[0..4]);
|
||||
mbrd_dsdt_data[56..60].copy_from_slice(&layout::PCI_MMCONFIG_SIZE.to_le_bytes()[0..4]);
|
||||
|
||||
/*
|
||||
Device (_SB.COM1)
|
||||
{
|
||||
@@ -183,6 +286,7 @@ pub fn create_dsdt_table(serial_enabled: bool) -> SDT {
|
||||
// DSDT
|
||||
let mut dsdt = SDT::new(*b"DSDT", 36, 6, *b"CLOUDH", *b"CHDSDT ", 1);
|
||||
dsdt.append(pci_dsdt_data);
|
||||
dsdt.append(mbrd_dsdt_data);
|
||||
if serial_enabled {
|
||||
dsdt.append(com1_dsdt_data);
|
||||
}
|
||||
@@ -194,13 +298,16 @@ pub fn create_acpi_tables(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
num_cpus: u8,
|
||||
serial_enabled: bool,
|
||||
start_of_device_area: GuestAddress,
|
||||
end_of_device_area: GuestAddress,
|
||||
virt_iommu: Option<(u32, &[u32])>,
|
||||
) -> GuestAddress {
|
||||
// RSDP is at the EBDA
|
||||
let rsdp_offset = super::EBDA_START;
|
||||
let rsdp_offset = layout::RSDP_POINTER;
|
||||
let mut tables: Vec<u64> = Vec::new();
|
||||
|
||||
// DSDT
|
||||
let dsdt = create_dsdt_table(serial_enabled);
|
||||
let dsdt = create_dsdt_table(serial_enabled, start_of_device_area, end_of_device_area);
|
||||
let dsdt_offset = rsdp_offset.checked_add(RSDP::len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(dsdt.as_slice(), dsdt_offset)
|
||||
@@ -238,7 +345,7 @@ pub fn create_acpi_tables(
|
||||
|
||||
// MADT
|
||||
let mut madt = SDT::new(*b"APIC", 44, 5, *b"CLOUDH", *b"CHMADT ", 1);
|
||||
madt.write(36, super::mptable::APIC_DEFAULT_PHYS_BASE);
|
||||
madt.write(36, layout::APIC_START);
|
||||
|
||||
for cpu in 0..num_cpus {
|
||||
let lapic = LocalAPIC {
|
||||
@@ -255,7 +362,7 @@ pub fn create_acpi_tables(
|
||||
r#type: 1,
|
||||
length: 12,
|
||||
ioapic_id: 0,
|
||||
apic_address: super::mptable::IO_APIC_DEFAULT_PHYS_BASE,
|
||||
apic_address: layout::IOAPIC_START.0 as u32,
|
||||
gsi_base: 0,
|
||||
..Default::default()
|
||||
});
|
||||
@@ -276,11 +383,14 @@ pub fn create_acpi_tables(
|
||||
tables.push(madt_offset.0);
|
||||
|
||||
// MCFG
|
||||
let mut mcfg = SDT::new(*b"MCFG", 60, 1, *b"CLOUDH", *b"CHMCFG ", 1);
|
||||
let mut mcfg = SDT::new(*b"MCFG", 36, 1, *b"CLOUDH", *b"CHMCFG ", 1);
|
||||
|
||||
// MCFG reserved 8 bytes
|
||||
mcfg.append(0u64);
|
||||
|
||||
// 32-bit PCI enhanced configuration mechanism
|
||||
mcfg.append(PCIRangeEntry {
|
||||
base_address: super::MEM_32BIT_DEVICES_GAP_SIZE,
|
||||
base_address: layout::PCI_MMCONFIG_START.0,
|
||||
segment: 0,
|
||||
start: 0,
|
||||
end: 0xff,
|
||||
@@ -293,6 +403,65 @@ pub fn create_acpi_tables(
|
||||
.expect("Error writing MCFG table");
|
||||
tables.push(mcfg_offset.0);
|
||||
|
||||
let (prev_tbl_len, prev_tbl_off) = if let Some((iommu_id, dev_ids)) = &virt_iommu {
|
||||
// IORT
|
||||
let mut iort = SDT::new(*b"IORT", 36, 1, *b"CLOUDH", *b"CHIORT ", 1);
|
||||
// IORT number of nodes
|
||||
iort.append(2u32);
|
||||
// IORT offset to array of IORT nodes
|
||||
iort.append(48u32);
|
||||
// IORT reserved 4 bytes
|
||||
iort.append(0u32);
|
||||
// IORT paravirtualized IOMMU node
|
||||
iort.append(IortParavirtIommuNode {
|
||||
type_: 128,
|
||||
length: 56,
|
||||
revision: 0,
|
||||
num_id_mappings: 0,
|
||||
ref_id_mappings: 56,
|
||||
device_id: *iommu_id,
|
||||
model: 1,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let num_entries = dev_ids.len();
|
||||
let length: u16 = (36 + (20 * num_entries)).try_into().unwrap();
|
||||
|
||||
// IORT PCI root complex node
|
||||
iort.append(IortPciRootComplexNode {
|
||||
type_: 2,
|
||||
length,
|
||||
revision: 0,
|
||||
num_id_mappings: num_entries as u32,
|
||||
ref_id_mappings: 36,
|
||||
ats_attr: 0,
|
||||
pci_seg_num: 0,
|
||||
mem_addr_size_limit: 255,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
for dev_id in dev_ids.iter() {
|
||||
// IORT ID mapping
|
||||
iort.append(IortIdMapping {
|
||||
input_base: *dev_id,
|
||||
num_of_ids: 1,
|
||||
ouput_base: *dev_id,
|
||||
output_ref: 48,
|
||||
flags: 0,
|
||||
});
|
||||
}
|
||||
|
||||
let iort_offset = mcfg_offset.checked_add(mcfg.len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(iort.as_slice(), iort_offset)
|
||||
.expect("Error writing IORT table");
|
||||
tables.push(iort_offset.0);
|
||||
|
||||
(iort.len(), iort_offset)
|
||||
} else {
|
||||
(mcfg.len(), mcfg_offset)
|
||||
};
|
||||
|
||||
// XSDT
|
||||
let mut xsdt = SDT::new(*b"XSDT", 36, 1, *b"CLOUDH", *b"CHXSDT ", 1);
|
||||
for table in tables {
|
||||
@@ -300,7 +469,7 @@ pub fn create_acpi_tables(
|
||||
}
|
||||
xsdt.update_checksum();
|
||||
|
||||
let xsdt_offset = mcfg_offset.checked_add(mcfg.len() as u64).unwrap();
|
||||
let xsdt_offset = prev_tbl_off.checked_add(prev_tbl_len as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(xsdt.as_slice(), xsdt_offset)
|
||||
.expect("Error writing XSDT table");
|
||||
|
||||
@@ -5,21 +5,90 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
|
||||
/// Magic addresses externally used to lay out x86_64 VMs.
|
||||
/*
|
||||
|
||||
Memory layout documentation and constants
|
||||
~~~~~~ ~~~~~~ ~~~~~~~~~~~~~ ~~~ ~~~~~~~~~
|
||||
|
||||
Constants are in order and grouped by range. Take care to update all references
|
||||
when making changes and keep them in order.
|
||||
|
||||
*/
|
||||
|
||||
// ** Low RAM (start: 0, length: 640KiB) **
|
||||
pub const LOW_RAM_START: GuestAddress = GuestAddress(0x0);
|
||||
|
||||
// == Fixed addresses within the "Low RAM" range: ==
|
||||
|
||||
// Initial GDT/IDT needed to boot kernel
|
||||
pub const BOOT_GDT_START: GuestAddress = GuestAddress(0x500);
|
||||
pub const BOOT_IDT_START: GuestAddress = GuestAddress(0x520);
|
||||
|
||||
/// The 'zero page', a.k.a linux kernel bootparams.
|
||||
pub const ZERO_PAGE_START: GuestAddress = GuestAddress(0x7000);
|
||||
|
||||
/// Initial stack for the boot CPU.
|
||||
pub const BOOT_STACK_START: GuestAddress = GuestAddress(0x8000);
|
||||
pub const BOOT_STACK_POINTER: GuestAddress = GuestAddress(0x8ff0);
|
||||
|
||||
// Initial pagetables.
|
||||
pub const PML4_START: GuestAddress = GuestAddress(0x9000);
|
||||
pub const PDPTE_START: GuestAddress = GuestAddress(0xa000);
|
||||
pub const PDE_START: GuestAddress = GuestAddress(0xb000);
|
||||
|
||||
/// Kernel command line start address.
|
||||
pub const CMDLINE_START: GuestAddress = GuestAddress(0x20000);
|
||||
/// Kernel command line start address maximum size.
|
||||
pub const CMDLINE_MAX_SIZE: usize = 0x10000;
|
||||
|
||||
/// Address for the TSS setup.
|
||||
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffbd000);
|
||||
// MPTABLE, describing VCPUS.
|
||||
pub const MPTABLE_START: GuestAddress = GuestAddress(0x9fc00);
|
||||
|
||||
/// The 'zero page', a.k.a linux kernel bootparams.
|
||||
pub const ZERO_PAGE_START: GuestAddress = GuestAddress(0x7000);
|
||||
// == End of "Low RAM" range. ==
|
||||
|
||||
// ** EBDA reserved area (start: 640KiB, length: 384KiB) **
|
||||
pub const EBDA_START: GuestAddress = GuestAddress(0xa0000);
|
||||
|
||||
// == Fixed constants within the "EBDA" range ==
|
||||
|
||||
// ACPI RSDP table
|
||||
pub const RSDP_POINTER: GuestAddress = EBDA_START;
|
||||
|
||||
// == End of "EBDA" range ==
|
||||
|
||||
// ** High RAM (start: 1MiB, length: 3071MiB) **
|
||||
pub const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
|
||||
|
||||
// == No fixed addresses in the "High RAM" range ==
|
||||
|
||||
// ** 32-bit reserved area (start: 3GiB, length: 1GiB) **
|
||||
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xc000_0000);
|
||||
pub const MEM_32BIT_RESERVED_SIZE: GuestUsize = (1024 << 20);
|
||||
|
||||
// == Fixed constants within the "32-bit reserved" range ==
|
||||
|
||||
// Sub range: 32-bit PCI devices (start: 3GiB, length: 640Mib)
|
||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = MEM_32BIT_RESERVED_START;
|
||||
pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = (640 << 20);
|
||||
|
||||
// PCI MMCONFIG space (start: after the device space, length: 256MiB)
|
||||
pub const PCI_MMCONFIG_START: GuestAddress =
|
||||
GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE);
|
||||
pub const PCI_MMCONFIG_SIZE: GuestUsize = (256 << 20);
|
||||
|
||||
// IOAPIC
|
||||
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
|
||||
pub const IOAPIC_SIZE: GuestUsize = 0x20;
|
||||
|
||||
// APIC
|
||||
pub const APIC_START: GuestAddress = GuestAddress(0xfee0_0000);
|
||||
|
||||
/// Address for the TSS setup.
|
||||
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffb_d000);
|
||||
|
||||
// == End of "32-bit reserved" range. ==
|
||||
|
||||
// ** 64-bit RAM start (start: 4GiB, length: varies) **
|
||||
pub const RAM_64BIT_START: GuestAddress = GuestAddress(0x1_0000_0000);
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
mod acpi;
|
||||
|
||||
mod gdt;
|
||||
pub mod interrupts;
|
||||
pub mod layout;
|
||||
@@ -20,6 +22,7 @@ use vm_memory::{
|
||||
};
|
||||
|
||||
const E820_RAM: u32 = 1;
|
||||
const E820_RESERVED: u32 = 2;
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `DataInit`) where:
|
||||
@@ -46,73 +49,53 @@ impl From<Error> for super::Error {
|
||||
}
|
||||
}
|
||||
|
||||
// Where BIOS/VGA magic would live on a real PC.
|
||||
const EBDA_START: GuestAddress = GuestAddress(0xa0000);
|
||||
const FIRST_ADDR_PAST_32BITS: GuestAddress = GuestAddress(1 << 32);
|
||||
|
||||
// Our 32-bit memory gap starts at 3G.
|
||||
pub const MEM_32BIT_GAP_START: GuestAddress = GuestAddress(0xc000_0000);
|
||||
|
||||
// Our 32-bit memory gap size is 1GB.
|
||||
const MEM_32BIT_GAP_SIZE: GuestUsize = (1024 << 20);
|
||||
|
||||
// We reserve 768MB in our memory gap for 32-bit devices (e.g. 32-bit PCI BARs).
|
||||
const MEM_32BIT_DEVICES_GAP_SIZE: GuestUsize = (768 << 20);
|
||||
|
||||
/// Returns a Vec of the valid memory addresses.
|
||||
/// These should be used to configure the GuestMemory structure for the platform.
|
||||
/// For x86_64 all addresses are valid from the start of the kernel except a
|
||||
/// carve out at the end of 32bit address space.
|
||||
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
|
||||
let reserved_memory_gap_start = MEM_32BIT_GAP_START
|
||||
.checked_add(MEM_32BIT_DEVICES_GAP_SIZE)
|
||||
let reserved_memory_gap_start = layout::MEM_32BIT_RESERVED_START
|
||||
.checked_add(layout::MEM_32BIT_DEVICES_SIZE)
|
||||
.expect("32-bit reserved region is too large");
|
||||
|
||||
let requested_memory_size = GuestAddress(size as u64);
|
||||
let mut regions = Vec::new();
|
||||
|
||||
// case1: guest memory fits before the gap
|
||||
if size as u64 <= MEM_32BIT_GAP_START.raw_value() {
|
||||
if size as u64 <= layout::MEM_32BIT_RESERVED_START.raw_value() {
|
||||
regions.push((GuestAddress(0), size as usize, RegionType::Ram));
|
||||
// case2: guest memory extends beyond the gap
|
||||
} else {
|
||||
// push memory before the gap
|
||||
regions.push((
|
||||
GuestAddress(0),
|
||||
MEM_32BIT_GAP_START.raw_value() as usize,
|
||||
layout::MEM_32BIT_RESERVED_START.raw_value() as usize,
|
||||
RegionType::Ram,
|
||||
));
|
||||
regions.push((
|
||||
FIRST_ADDR_PAST_32BITS,
|
||||
requested_memory_size.unchecked_offset_from(MEM_32BIT_GAP_START) as usize,
|
||||
layout::RAM_64BIT_START,
|
||||
requested_memory_size.unchecked_offset_from(layout::MEM_32BIT_RESERVED_START) as usize,
|
||||
RegionType::Ram,
|
||||
));
|
||||
}
|
||||
|
||||
// Add the 32-bit device memory hole as a sub region.
|
||||
regions.push((
|
||||
MEM_32BIT_GAP_START,
|
||||
MEM_32BIT_DEVICES_GAP_SIZE as usize,
|
||||
layout::MEM_32BIT_RESERVED_START,
|
||||
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
||||
RegionType::SubRegion,
|
||||
));
|
||||
|
||||
// Add the 32-bit reserved memory hole as a sub region.
|
||||
regions.push((
|
||||
reserved_memory_gap_start,
|
||||
(MEM_32BIT_GAP_SIZE - MEM_32BIT_DEVICES_GAP_SIZE) as usize,
|
||||
(layout::MEM_32BIT_RESERVED_SIZE - layout::MEM_32BIT_DEVICES_SIZE) as usize,
|
||||
RegionType::Reserved,
|
||||
));
|
||||
|
||||
regions
|
||||
}
|
||||
|
||||
/// X86 specific memory hole/memory mapped devices/reserved area.
|
||||
pub fn get_32bit_gap_start() -> GuestAddress {
|
||||
FIRST_ADDR_PAST_32BITS
|
||||
.checked_sub(MEM_32BIT_GAP_SIZE)
|
||||
.expect("32-bit hole is too large")
|
||||
}
|
||||
|
||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -121,30 +104,29 @@ pub fn get_32bit_gap_start() -> GuestAddress {
|
||||
/// * `cmdline_addr` - Address in `guest_mem` where the kernel command line was loaded.
|
||||
/// * `cmdline_size` - Size of the kernel command line in bytes including the null terminator.
|
||||
/// * `num_cpus` - Number of virtual CPUs the guest will have.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn configure_system(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
cmdline_size: usize,
|
||||
num_cpus: u8,
|
||||
setup_hdr: Option<setup_header>,
|
||||
serial_enabled: bool,
|
||||
_serial_enabled: bool,
|
||||
_end_of_range: GuestAddress,
|
||||
_virt_iommu: Option<(u32, &[u32])>,
|
||||
) -> super::Result<()> {
|
||||
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
|
||||
const KERNEL_HDR_MAGIC: u32 = 0x53726448;
|
||||
const KERNEL_LOADER_OTHER: u8 = 0xff;
|
||||
const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; // Must be non-zero.
|
||||
let first_addr_past_32bits = FIRST_ADDR_PAST_32BITS;
|
||||
let end_32bit_gap_start = get_32bit_gap_start();
|
||||
|
||||
let himem_start = super::HIMEM_START;
|
||||
|
||||
// Note that this puts the mptable at the last 1k of Linux's 640k base RAM
|
||||
mptable::setup_mptable(guest_mem, num_cpus).map_err(Error::MpTableSetup)?;
|
||||
|
||||
let mut params: BootParamsWrapper = BootParamsWrapper(boot_params::default());
|
||||
|
||||
if setup_hdr.is_some() {
|
||||
params.0.hdr = setup_hdr.unwrap();
|
||||
if let Some(hdr) = setup_hdr {
|
||||
params.0.hdr = hdr;
|
||||
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||
params.0.hdr.cmdline_size = cmdline_size as u32;
|
||||
} else {
|
||||
@@ -156,36 +138,55 @@ pub fn configure_system(
|
||||
params.0.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
|
||||
};
|
||||
|
||||
add_e820_entry(&mut params.0, 0, EBDA_START.raw_value(), E820_RAM)?;
|
||||
add_e820_entry(&mut params.0, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
|
||||
|
||||
let mem_end = guest_mem.end_addr();
|
||||
if mem_end < end_32bit_gap_start {
|
||||
if mem_end < layout::MEM_32BIT_RESERVED_START {
|
||||
add_e820_entry(
|
||||
&mut params.0,
|
||||
himem_start.raw_value(),
|
||||
mem_end.unchecked_offset_from(himem_start),
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::HIGH_RAM_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
} else {
|
||||
add_e820_entry(
|
||||
&mut params.0,
|
||||
himem_start.raw_value(),
|
||||
end_32bit_gap_start.unchecked_offset_from(himem_start),
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::HIGH_RAM_START),
|
||||
E820_RAM,
|
||||
)?;
|
||||
if mem_end > first_addr_past_32bits {
|
||||
if mem_end > layout::RAM_64BIT_START {
|
||||
add_e820_entry(
|
||||
&mut params.0,
|
||||
first_addr_past_32bits.raw_value(),
|
||||
mem_end.unchecked_offset_from(first_addr_past_32bits),
|
||||
layout::RAM_64BIT_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::RAM_64BIT_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
add_e820_entry(
|
||||
&mut params.0,
|
||||
layout::PCI_MMCONFIG_START.0,
|
||||
layout::PCI_MMCONFIG_SIZE,
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
{
|
||||
let rsdp_addr = acpi::create_acpi_tables(guest_mem, num_cpus, serial_enabled);
|
||||
let start_of_device_area = if mem_end < layout::MEM_32BIT_RESERVED_START {
|
||||
layout::RAM_64BIT_START
|
||||
} else {
|
||||
guest_mem.end_addr().unchecked_add(1)
|
||||
};
|
||||
let rsdp_addr = acpi::create_acpi_tables(
|
||||
guest_mem,
|
||||
num_cpus,
|
||||
_serial_enabled,
|
||||
start_of_device_area,
|
||||
_end_of_range,
|
||||
_virt_iommu,
|
||||
);
|
||||
params.0.acpi_rsdp_addr = rsdp_addr.0;
|
||||
}
|
||||
|
||||
@@ -241,21 +242,20 @@ mod tests {
|
||||
assert_eq!(GuestAddress(1 << 32), regions[1].0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_32bit_gap() {
|
||||
assert_eq!(
|
||||
get_32bit_gap_start(),
|
||||
FIRST_ADDR_PAST_32BITS
|
||||
.checked_sub(MEM_32BIT_GAP_SIZE as u64)
|
||||
.expect("32-bit hole is too large")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_configuration() {
|
||||
let no_vcpus = 4;
|
||||
let gm = GuestMemoryMmap::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let config_err = configure_system(&gm, GuestAddress(0), 0, 1, None, false);
|
||||
let config_err = configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
1,
|
||||
None,
|
||||
false,
|
||||
GuestAddress((1 << 36) - 1),
|
||||
None,
|
||||
);
|
||||
assert!(config_err.is_err());
|
||||
assert_eq!(
|
||||
config_err.unwrap_err(),
|
||||
@@ -273,7 +273,17 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::new(&ram_regions).unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, false).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
no_vcpus,
|
||||
None,
|
||||
false,
|
||||
GuestAddress((1 << 36) - 1),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Now assigning some memory that is equal to the start of the 32bit memory hole.
|
||||
let mem_size = 3328 << 20;
|
||||
@@ -284,7 +294,17 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::new(&ram_regions).unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, false).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
no_vcpus,
|
||||
None,
|
||||
false,
|
||||
GuestAddress((1 << 36) - 1),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Now assigning some memory that falls after the 32bit memory hole.
|
||||
let mem_size = 3330 << 20;
|
||||
@@ -295,7 +315,17 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::new(&ram_regions).unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, false).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
no_vcpus,
|
||||
None,
|
||||
false,
|
||||
GuestAddress((1 << 36) - 1),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -13,7 +13,8 @@ use std::slice;
|
||||
use libc::c_char;
|
||||
|
||||
use arch_gen::x86::mpspec;
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap};
|
||||
use layout::{APIC_START, IOAPIC_START, MPTABLE_START};
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestMemory, GuestMemoryMmap};
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `ByteValued`) where:
|
||||
@@ -44,9 +45,6 @@ unsafe impl ByteValued for MpcTableWrapper {}
|
||||
unsafe impl ByteValued for MpcLintsrcWrapper {}
|
||||
unsafe impl ByteValued for MpfIntelWrapper {}
|
||||
|
||||
// MPTABLE, describing VCPUS.
|
||||
const MPTABLE_START: GuestAddress = GuestAddress(0x9fc00);
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// There was too little guest memory to store the entire MP table.
|
||||
@@ -92,8 +90,6 @@ 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', ' ', ' ', ' ');
|
||||
pub const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000; // source: linux/arch/x86/include/asm/apicdef.h
|
||||
pub const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee00000; // source: linux/arch/x86/include/asm/apicdef.h
|
||||
const APIC_VERSION: u8 = 0x14;
|
||||
const CPU_STEPPING: u32 = 0x600;
|
||||
const CPU_FEATURE_APIC: u32 = 0x200;
|
||||
@@ -208,7 +204,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
mpc_ioapic.0.apicid = ioapicid;
|
||||
mpc_ioapic.0.apicver = APIC_VERSION;
|
||||
mpc_ioapic.0.flags = mpspec::MPC_APIC_USABLE as u8;
|
||||
mpc_ioapic.0.apicaddr = IO_APIC_DEFAULT_PHYS_BASE;
|
||||
mpc_ioapic.0.apicaddr = IOAPIC_START.0 as u32;
|
||||
mem.write_obj(mpc_ioapic, base_mp)
|
||||
.map_err(|_| Error::WriteMpcIoapic)?;
|
||||
base_mp = base_mp.unchecked_add(size as u64);
|
||||
@@ -271,7 +267,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
mpc_table.0.spec = MPC_SPEC;
|
||||
mpc_table.0.oem = MPC_OEM;
|
||||
mpc_table.0.productid = MPC_PRODUCT_ID;
|
||||
mpc_table.0.lapic = APIC_DEFAULT_PHYS_BASE;
|
||||
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;
|
||||
mem.write_obj(mpc_table, table_base)
|
||||
@@ -284,7 +280,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use vm_memory::GuestUsize;
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
|
||||
fn table_entry_size(type_: u8) -> usize {
|
||||
match type_ as u32 {
|
||||
|
||||
@@ -11,12 +11,8 @@ use super::gdt::{gdt_entry, kvm_segment_from_gdt};
|
||||
use arch_gen::x86::msr_index;
|
||||
use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_msrs, kvm_regs, kvm_sregs};
|
||||
use kvm_ioctls::VcpuFd;
|
||||
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap};
|
||||
|
||||
// Initial pagetables.
|
||||
const PML4_START: GuestAddress = GuestAddress(0x9000);
|
||||
const PDPTE_START: GuestAddress = GuestAddress(0xa000);
|
||||
const PDE_START: GuestAddress = GuestAddress(0xb000);
|
||||
use layout::{BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryMmap};
|
||||
|
||||
// MTRR constants
|
||||
const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11
|
||||
@@ -127,9 +123,6 @@ pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &VcpuFd) -> Result<()> {
|
||||
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
||||
}
|
||||
|
||||
const BOOT_GDT_OFFSET: GuestAddress = GuestAddress(0x500);
|
||||
const BOOT_IDT_OFFSET: GuestAddress = GuestAddress(0x520);
|
||||
|
||||
const BOOT_GDT_MAX: usize = 4;
|
||||
|
||||
const EFER_LMA: u64 = 0x400;
|
||||
@@ -140,7 +133,7 @@ const X86_CR0_PG: u64 = 0x80000000;
|
||||
const X86_CR4_PAE: u64 = 0x20;
|
||||
|
||||
fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
let boot_gdt_addr = BOOT_GDT_OFFSET;
|
||||
let boot_gdt_addr = BOOT_GDT_START;
|
||||
for (index, entry) in table.iter().enumerate() {
|
||||
let addr = guest_mem
|
||||
.checked_offset(boot_gdt_addr, index * mem::size_of::<u64>())
|
||||
@@ -153,7 +146,7 @@ fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
}
|
||||
|
||||
fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
let boot_idt_addr = BOOT_IDT_OFFSET;
|
||||
let boot_idt_addr = BOOT_IDT_START;
|
||||
guest_mem
|
||||
.write_obj(val, boot_idt_addr)
|
||||
.map_err(|_| Error::WriteIDT)
|
||||
@@ -173,11 +166,11 @@ fn configure_segments_and_sregs(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) ->
|
||||
|
||||
// Write segments
|
||||
write_gdt_table(&gdt_table[..], mem)?;
|
||||
sregs.gdt.base = BOOT_GDT_OFFSET.raw_value();
|
||||
sregs.gdt.base = BOOT_GDT_START.raw_value();
|
||||
sregs.gdt.limit = mem::size_of_val(&gdt_table) as u16 - 1;
|
||||
|
||||
write_idt_value(0, mem)?;
|
||||
sregs.idt.base = BOOT_IDT_OFFSET.raw_value();
|
||||
sregs.idt.base = BOOT_IDT_START.raw_value();
|
||||
sregs.idt.limit = mem::size_of::<u64>() as u16 - 1;
|
||||
|
||||
sregs.cs = code_seg;
|
||||
@@ -305,20 +298,20 @@ mod tests {
|
||||
let gm = create_guest_mem();
|
||||
configure_segments_and_sregs(&gm, &mut sregs).unwrap();
|
||||
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_OFFSET));
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||
assert_eq!(
|
||||
0xaf9b000000ffff,
|
||||
read_u64(&gm, BOOT_GDT_OFFSET.unchecked_add(8))
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(8))
|
||||
);
|
||||
assert_eq!(
|
||||
0xcf93000000ffff,
|
||||
read_u64(&gm, BOOT_GDT_OFFSET.unchecked_add(16))
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(16))
|
||||
);
|
||||
assert_eq!(
|
||||
0x8f8b000000ffff,
|
||||
read_u64(&gm, BOOT_GDT_OFFSET.unchecked_add(24))
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(24))
|
||||
);
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_OFFSET));
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_START));
|
||||
|
||||
assert_eq!(0, sregs.cs.base);
|
||||
assert_eq!(0xfffff, sregs.ds.limit);
|
||||
|
||||
@@ -11,11 +11,12 @@ kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master"
|
||||
libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[features]
|
||||
default = ["acpi"]
|
||||
acpi = []
|
||||
default = []
|
||||
acpi = []
|
||||
cmos = []
|
||||
|
||||
@@ -15,6 +15,7 @@ use kvm_bindings::kvm_msi;
|
||||
use kvm_ioctls::VmFd;
|
||||
use std::sync::Arc;
|
||||
use std::{io, result};
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -156,6 +157,7 @@ pub struct Ioapic {
|
||||
reg_sel: u32,
|
||||
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
|
||||
vm_fd: Arc<VmFd>,
|
||||
apic_address: GuestAddress,
|
||||
}
|
||||
|
||||
impl BusDevice for Ioapic {
|
||||
@@ -194,12 +196,13 @@ impl BusDevice for Ioapic {
|
||||
}
|
||||
|
||||
impl Ioapic {
|
||||
pub fn new(vm_fd: Arc<VmFd>) -> Ioapic {
|
||||
pub fn new(vm_fd: Arc<VmFd>, apic_address: GuestAddress) -> Ioapic {
|
||||
Ioapic {
|
||||
id: 0,
|
||||
reg_sel: 0,
|
||||
reg_entries: [0; NUM_IOAPIC_PINS],
|
||||
vm_fd,
|
||||
apic_address,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +242,7 @@ impl Ioapic {
|
||||
let redirection_hint: u8 = 1;
|
||||
|
||||
// Generate MSI message address
|
||||
let address_lo: u32 = 0xfee0_0000
|
||||
let address_lo: u32 = self.apic_address.0 as u32
|
||||
| u32::from(destination_id) << 12
|
||||
| u32::from(redirection_hint) << 3
|
||||
| u32::from(destination_mode) << 2;
|
||||
|
||||
115
devices/src/legacy/cmos.rs
Normal file
115
devices/src/legacy/cmos.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use libc::{gmtime_r, time, time_t, tm};
|
||||
use std::cmp::min;
|
||||
use std::mem;
|
||||
|
||||
use crate::BusDevice;
|
||||
|
||||
const INDEX_MASK: u8 = 0x7f;
|
||||
const INDEX_OFFSET: u64 = 0x0;
|
||||
const DATA_OFFSET: u64 = 0x1;
|
||||
const DATA_LEN: usize = 128;
|
||||
|
||||
/// A CMOS/RTC device commonly seen on x86 I/O port 0x70/0x71.
|
||||
pub struct Cmos {
|
||||
index: u8,
|
||||
data: [u8; DATA_LEN],
|
||||
}
|
||||
|
||||
impl Cmos {
|
||||
/// Constructs a CMOS/RTC device with initial data.
|
||||
/// `mem_below_4g` is the size of memory in bytes below the 32-bit gap.
|
||||
/// `mem_above_4g` is the size of memory in bytes above the 32-bit gap.
|
||||
pub fn new(mem_below_4g: u64, mem_above_4g: u64) -> Cmos {
|
||||
let mut data = [0u8; DATA_LEN];
|
||||
|
||||
// Extended memory from 16 MB to 4 GB in units of 64 KB
|
||||
let ext_mem = min(
|
||||
0xFFFF,
|
||||
mem_below_4g.saturating_sub(16 * 1024 * 1024) / (64 * 1024),
|
||||
);
|
||||
data[0x34] = ext_mem as u8;
|
||||
data[0x35] = (ext_mem >> 8) as u8;
|
||||
|
||||
// High memory (> 4GB) in units of 64 KB
|
||||
let high_mem = min(0x00FF_FFFF, mem_above_4g / (64 * 1024));
|
||||
data[0x5b] = high_mem as u8;
|
||||
data[0x5c] = (high_mem >> 8) as u8;
|
||||
data[0x5d] = (high_mem >> 16) as u8;
|
||||
|
||||
Cmos { index: 0, data }
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for Cmos {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
if data.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
match offset {
|
||||
INDEX_OFFSET => self.index = data[0] & INDEX_MASK,
|
||||
DATA_OFFSET => self.data[self.index as usize] = data[0],
|
||||
o => panic!("bad write offset on CMOS device: {}", o),
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
fn to_bcd(v: u8) -> u8 {
|
||||
assert!(v < 100);
|
||||
((v / 10) << 4) | (v % 10)
|
||||
}
|
||||
|
||||
if data.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
data[0] = match offset {
|
||||
INDEX_OFFSET => self.index,
|
||||
DATA_OFFSET => {
|
||||
let seconds;
|
||||
let minutes;
|
||||
let hours;
|
||||
let week_day;
|
||||
let day;
|
||||
let month;
|
||||
let year;
|
||||
// The time and gmtime_r calls are safe as long as the structs they are given are
|
||||
// large enough, and neither of them fail. It is safe to zero initialize the tm
|
||||
// struct because it contains only plain data.
|
||||
unsafe {
|
||||
let mut tm: tm = mem::zeroed();
|
||||
let mut now: time_t = 0;
|
||||
time(&mut now as *mut _);
|
||||
gmtime_r(&now, &mut tm as *mut _);
|
||||
// The following lines of code are safe but depend on tm being in scope.
|
||||
seconds = tm.tm_sec;
|
||||
minutes = tm.tm_min;
|
||||
hours = tm.tm_hour;
|
||||
week_day = tm.tm_wday + 1;
|
||||
day = tm.tm_mday;
|
||||
month = tm.tm_mon + 1;
|
||||
year = tm.tm_year;
|
||||
};
|
||||
match self.index {
|
||||
0x00 => to_bcd(seconds as u8),
|
||||
0x02 => to_bcd(minutes as u8),
|
||||
0x04 => to_bcd(hours as u8),
|
||||
0x06 => to_bcd(week_day as u8),
|
||||
0x07 => to_bcd(day as u8),
|
||||
0x08 => to_bcd(month as u8),
|
||||
0x09 => to_bcd((year % 100) as u8),
|
||||
0x32 => to_bcd(((year + 1900) / 100) as u8),
|
||||
_ => {
|
||||
// self.index is always guaranteed to be in range via INDEX_MASK.
|
||||
self.data[(self.index & INDEX_MASK) as usize]
|
||||
}
|
||||
}
|
||||
}
|
||||
o => panic!("bad read offset on CMOS device: {}", o),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,12 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
#[cfg(feature = "cmos")]
|
||||
mod cmos;
|
||||
mod i8042;
|
||||
mod serial;
|
||||
|
||||
#[cfg(feature = "cmos")]
|
||||
pub use self::cmos::Cmos;
|
||||
pub use self::i8042::I8042Device;
|
||||
pub use self::serial::Serial;
|
||||
|
||||
@@ -67,6 +67,6 @@ pub enum Error {
|
||||
IoError(io::Error),
|
||||
}
|
||||
|
||||
pub trait Interrupt: Send {
|
||||
pub trait Interrupt: Send + Sync {
|
||||
fn deliver(&self) -> result::Result<(), std::io::Error>;
|
||||
}
|
||||
|
||||
96
docs/custom-image.md
Normal file
96
docs/custom-image.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# How to create a custom Clear Linux image
|
||||
|
||||
In the context of adding more utility to the cloudguest image being used
|
||||
for integration testing, this is a quick guide on how to achieve the creation
|
||||
of your own Clear Linux image using the official Clear Linux tooling.
|
||||
|
||||
## Prepare the environment
|
||||
|
||||
From the host, the goal is run a Clear Linux VM that will allow us to build
|
||||
the custom image we want.
|
||||
|
||||
```bash
|
||||
# Get latest CL version:
|
||||
IMG_VERSION=$(curl https://download.clearlinux.org/latest)
|
||||
# Get latest clear-kvm image:
|
||||
wget -P $HOME/workloads/ https://download.clearlinux.org/current/clear-${IMG_VERSION}-kvm.img.xz
|
||||
# Extract the image
|
||||
unxz $HOME/workloads/clear-${IMG_VERSION}-kvm.img.xz
|
||||
# Make sure cloud-hypervisor binary has CAP_NET_ADMIN capability set
|
||||
sudo setcap cap_net_admin+ep cloud-hypervisor
|
||||
# Boot cloud-hypervisor VM with the downloaded image
|
||||
./cloud-hypervisor -v --kernel $HOME/workloads/vmlinux --disk path=clear-${IMG_VERSION}-kvm.img --cmdline "console=ttyS0 console=hvc0 reboot=k panic=1 nomodules root=/dev/vda3 rw" --cpus 1 --memory size=4G --net tap=,mac=
|
||||
# Setup connectivity
|
||||
# First make sure to enable IP forwarding (disabled on Linux by default)
|
||||
sudo bash -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
|
||||
# Retrieve the interface name and the gateway IP
|
||||
IFACE=$(ip route | grep default | awk -F 'dev' '{print $2}' | awk -F ' ' '{print $1}')
|
||||
GW=$(ip route | grep vmtap0 | awk -F ' ' '{print $1}')
|
||||
# Create a new masquerade rule to tag the packets going out
|
||||
sudo iptables -t nat -A POSTROUTING -s ${GW} -o ${IFACE} -j MASQUERADE
|
||||
```
|
||||
|
||||
## Create the image
|
||||
|
||||
From the guest, we can now create the image.
|
||||
|
||||
```bash
|
||||
# Setup connectivity
|
||||
sudo ip addr add 192.168.249.2/24 dev enp0s3
|
||||
sudo ip route add default via 192.168.249.1
|
||||
# Install necessary bundles
|
||||
sudo swupd bundle-add clr-installer
|
||||
sudo swupd bundle-add os-installer
|
||||
# Download and update cloudguest image configuration
|
||||
wget https://download.clearlinux.org/current/config/image/cloudguest.yaml
|
||||
sed -i '/size: \"864M\"/d' cloudguest.yaml
|
||||
sed -i 's/\"800M\"/\"2G\"/g' cloudguest.yaml
|
||||
sed -i 's/bootloader,/bootloader,\n iperf,/g' cloudguest.yaml
|
||||
sed -i 's/systemd-networkd-autostart/sysadmin-basic,\n systemd-networkd-autostart/g' cloudguest.yaml
|
||||
# Create the custom cloudguest image
|
||||
clr-installer -c cloudguest.yaml
|
||||
# Make the guest accessible through ssh
|
||||
sudo mkdir -p /etc/ssh
|
||||
sudo bash -c "echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config"
|
||||
```
|
||||
|
||||
### Retrieve the image
|
||||
|
||||
Once the new image has been created and the guest is accessible through
|
||||
`ssh`, it is time to retrieve the image from the host.
|
||||
|
||||
```bash
|
||||
# Retrieve new image (this is a raw image)
|
||||
scp root@192.168.249.2:cloudguest.img .
|
||||
mv cloudguest.img clear-cloudguest-raw.img
|
||||
# Create the QCOW image from the RAW image
|
||||
qemu-img convert -p -f raw -O qcow2 clear-cloudguest-raw.img clear-cloudguest.img
|
||||
# Compress the QCOW image
|
||||
xz -k -T $(nproc) clear-cloudguest.img
|
||||
```
|
||||
|
||||
## Switch CI to use the new image
|
||||
|
||||
### Upload to Azure storage
|
||||
|
||||
The next step is to update the image stored as part of the Azure storage
|
||||
account, replacing it with the newly created image. This will make this
|
||||
new image available from the integration tests.
|
||||
This is usually achieved through the web interface.
|
||||
|
||||
### Update integration tests
|
||||
|
||||
Last step is about updating the integration tests to work with this new image.
|
||||
The key point is to identify the UUID of this new image so that it can be used
|
||||
directly from the tests.
|
||||
|
||||
Proceed as follow to determine this UUID:
|
||||
|
||||
```bash
|
||||
# Mount the image
|
||||
sudo mount -o loop,offset=$((2048 * 512)) clear-cloudguest-raw.img /mnt/
|
||||
# Identify UUID
|
||||
sudo cat /mnt/loader/entries/Clear-linux-kvm-*.conf | grep "root=PARTUUID="
|
||||
# Unmount the image
|
||||
sudo umount /mnt
|
||||
```
|
||||
@@ -43,7 +43,7 @@ to easily grep for the tracing logs (e.g.
|
||||
```
|
||||
./target/debug/cloud-hypervisor \
|
||||
--kernel ~/rust-hypervisor-firmware/target/target/release/hypervisor-fw \
|
||||
--disk ~/hypervisor/images/clear-30080-kvm.img \
|
||||
--disk path=~/hypervisor/images/clear-30080-kvm.img \
|
||||
--cpus 4 \
|
||||
--memory size=1024M \
|
||||
--rng \
|
||||
|
||||
@@ -53,7 +53,7 @@ Assuming you have `clear-kvm.img` and `custom-vmlinux.bin` on your system, here
|
||||
./cloud-hypervisor \
|
||||
--cpus 4 \
|
||||
--memory "size=512,file=/dev/shm" \
|
||||
--disk clear-kvm.img \
|
||||
--disk path=clear-kvm.img \
|
||||
--kernel custom-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
|
||||
--fs tag=virtiofs,sock=/tmp/virtiofs,num_queues=1,queue_size=512
|
||||
|
||||
209
docs/iommu.md
Normal file
209
docs/iommu.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# Virtual IOMMU
|
||||
|
||||
## Rationales
|
||||
|
||||
Having the possibility to expose a virtual IOMMU to the guest can be
|
||||
interesting to support specific use cases. That being said, it is always
|
||||
important to keep in mind a virtual IOMMU can impact the performance of the
|
||||
attached devices, which is the reason why one should be careful when enabling
|
||||
this feature.
|
||||
|
||||
### Protect nested virtual machines
|
||||
|
||||
The first reason why one might want to expose a virtual IOMMU to the guest is
|
||||
to increase the security regarding the memory accesses performed by the virtual
|
||||
devices (VIRTIO devices), on behalf of the guest drivers.
|
||||
|
||||
With a virtual IOMMU, the VMM stands between the guest driver and its device
|
||||
counterpart, validating and translating every address before to try accessing
|
||||
the guest memory. This is standard interposition that is performed here by the
|
||||
VMM.
|
||||
|
||||
The increased security does not apply for a simple case where we have one VM
|
||||
per VMM. Because the guest cannot be trusted, as we always consider it could
|
||||
be malicious and gain unauthorized privileges inside the VM, preventing some
|
||||
devices from accessing the entire guest memory is pointless.
|
||||
|
||||
But let's take the interesting case of nested virtualization, and let's assume
|
||||
we have a VMM running a first layer VM. This L1 guest is fully trusted as the
|
||||
user intends to run multiple VMs from this L1. We can end up with multiple L2
|
||||
VMs running on a single L1 VM. In this particular case, and without exposing a
|
||||
virtual IOMMU to the L1 guest, it would be possible for any L2 guest to use the
|
||||
device implementation from the host VMM to access the entire guest L1 memory.
|
||||
The virtual IOMMU prevents from this kind of trouble as it will validate the
|
||||
addresses the device is authorized to access.
|
||||
|
||||
### Achieve VFIO nested
|
||||
|
||||
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
|
||||
through the VFIO framework on the host as this is the only userspace interface
|
||||
to interact with a physical IOMMU.
|
||||
|
||||
Relying on this update mechanism, it is possible to attach physical devices to
|
||||
the virtual IOMMU, which allows these devices to be passed from L1 to another
|
||||
layer of virtualization.
|
||||
|
||||
## Why virtio-iommu?
|
||||
|
||||
The Cloud Hypervisor project decided to implement the brand new virtio-iommu
|
||||
device in order to provide a virtual IOMMU to its users. The reason being the
|
||||
simplicity brought by the paravirtualization solution. By having one side
|
||||
handled from the guest itself, it removes the complexity of trapping memory
|
||||
page accesses and shadowing them. This is why the project will not try to
|
||||
implement a full emulation of a physical IOMMU.
|
||||
|
||||
## Pre-requisites
|
||||
|
||||
### Kernel
|
||||
|
||||
Since virtio-iommu has landed partially into the version 5.3 of the Linux
|
||||
kernel, a special branch is needed to get things working with Cloud Hypervisor.
|
||||
By partially, we are talking about x86 specifically, as it is already fully
|
||||
functional for ARM architectures.
|
||||
|
||||
## Usage
|
||||
|
||||
In order to expose a virtual IOMMU to the guest, it is required to create a
|
||||
virtio-iommu device and expose it through the ACPI IORT table. This can be
|
||||
simply achieved by attaching at least one device to the virtual IOMMU.
|
||||
|
||||
The way to expose to the guest a specific device as sitting behind this IOMMU
|
||||
is to explicitly tag it from the command line with the option `iommu=on`.
|
||||
|
||||
Not all devices support this extra option, and the default value will always
|
||||
be `off` since we want to avoid the performance impact for most users who don't
|
||||
need this.
|
||||
|
||||
Refer to the command line `--help` to find out which device support to be
|
||||
attached to the virtual IOMMU.
|
||||
|
||||
Below is a simple example exposing the `virtio-blk` device as attached to the
|
||||
virtual IOMMU:
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus 1 \
|
||||
--memory size=512M \
|
||||
--disk path=clear-kvm.img,iommu=on \
|
||||
--kernel custom-bzImage \
|
||||
--cmdline "console=ttyS0 root=/dev/vda3" \
|
||||
```
|
||||
|
||||
From a guest perspective, it is easy to verify if the device is protected by
|
||||
the virtual IOMMU. Check the directories listed under
|
||||
`/sys/kernel/iommu_groups`:
|
||||
|
||||
```bash
|
||||
ls /sys/kernel/iommu_groups
|
||||
0
|
||||
```
|
||||
|
||||
In this case, only one IOMMU group should be created. Under this group, it is
|
||||
possible to find out the b/d/f of the device(s) part of this group.
|
||||
|
||||
```bash
|
||||
ls /sys/kernel/iommu_groups/0/devices/
|
||||
0000:00:03.0
|
||||
```
|
||||
|
||||
And you can validate the device is the one we expect running `lspci`:
|
||||
|
||||
```bash
|
||||
lspci
|
||||
00:00.0 Host bridge: Intel Corporation Device 0d57
|
||||
00:01.0 Unassigned class [ffff]: Red Hat, Inc. Device 1057
|
||||
00:02.0 Unassigned class [ffff]: Red Hat, Inc. Virtio console
|
||||
00:03.0 Mass storage controller: Red Hat, Inc. Virtio block device
|
||||
00:04.0 Unassigned class [ffff]: Red Hat, Inc. Virtio RNG
|
||||
```
|
||||
|
||||
## Faster mappings
|
||||
|
||||
By default, the guest memory is mapped with 4k pages and no huge pages, which
|
||||
causes the virtual IOMMU device to be asked for 4k mappings only. This
|
||||
configuration slows down the setup of the physical IOMMU as an important number
|
||||
of requests need to be issued in order to create large mappings.
|
||||
|
||||
One use case is even more impacted by the slowdown, the nested VFIO case. When
|
||||
passing a device through a L2 guest, the VFIO driver running in L1 will update
|
||||
the DMAR entries for the specific device. Because VFIO pins the entire guest
|
||||
memory, this means the entire mapping of the L2 guest need to be stored into
|
||||
multiple 4k mappings. Obviously, the bigger the L2 guest RAM is, the longer the
|
||||
update of the mappings will last. There is an additional problem happening in
|
||||
this case, if the L2 guest RAM is quite large, it will require a large number
|
||||
of mappings, which might exceed the VFIO limit set on the host. The default
|
||||
value is 65536, which can simply be reached with a 256MiB sized RAM.
|
||||
|
||||
The way to solve both problems, the slowdown and the limit being exceeded, is
|
||||
to reduce the amount of requests to describe those same large mappings. This
|
||||
can be achieved by using 2MiB pages, known as huge pages. By seeing the guest
|
||||
RAM as larger pages, and because the virtual IOMMU device supports it, the
|
||||
guest will require less mappings, which will prevent the limit from being
|
||||
exceeded, but also will take less time to process them on the host. That's
|
||||
how using huge pages as much as possible can speed up VM boot time.
|
||||
|
||||
### Basic usage
|
||||
|
||||
Let's look at an example of how to run a guest with huge pages.
|
||||
|
||||
First, make sure your system has enough pages to cover the entire guest RAM:
|
||||
```bash
|
||||
# This example creates 4096 hugepages
|
||||
echo 4096 > /proc/sys/vm/nr_hugepages
|
||||
```
|
||||
|
||||
Next step is simply to create the VM. Two things are important, first we want
|
||||
the VM RAM to be mapped on huge pages by backing it with `/dev/hugepages`. And
|
||||
second thing, we need to create some huge pages in the guest itself so they can
|
||||
be consumed.
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus 1 \
|
||||
--memory size=8G,file=/dev/hugepages \
|
||||
--disk path=clear-kvm.img \
|
||||
--kernel custom-bzImage \
|
||||
--cmdline "console=ttyS0 root=/dev/vda3 hugepagesz=2M hugepages=2048" \
|
||||
--net tap=,mac=,iommu=on
|
||||
```
|
||||
|
||||
### Nested usage
|
||||
|
||||
Let's now look at the specific example of nested virtualization. In order to
|
||||
reach optimized performances, the L2 guest also need to be mapped based on
|
||||
huge pages. Here is how to achieve this, assuming the physical device you are
|
||||
passing through is `0000:00:01.0`.
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus 1 \
|
||||
--memory size=8G,file=/dev/hugepages \
|
||||
--disk path=clear-kvm.img \
|
||||
--kernel custom-bzImage \
|
||||
--cmdline "console=ttyS0 root=/dev/vda3 kvm-intel.nested=1 vfio_iommu_type1.allow_unsafe_interrupts rw hugepagesz=2M hugepages=2048" \
|
||||
--device path=/sys/bus/pci/devices/0000:00:01.0,iommu=on
|
||||
```
|
||||
|
||||
Once the L1 VM is running, unbind the device from the default driver in the
|
||||
guest, and bind it to VFIO (it should appear as `0000:00:04.0`).
|
||||
|
||||
```bash
|
||||
echo 0000:00:04.0 > /sys/bus/pci/devices/0000\:00\:04.0/driver/unbind
|
||||
echo 8086 1502 > /sys/bus/pci/drivers/vfio-pci/new_id
|
||||
```
|
||||
|
||||
Last thing is to start the L2 guest with the huge pages memory backend.
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus 1 \
|
||||
--memory size=4G,file=/dev/hugepages \
|
||||
--disk path=clear-kvm.img \
|
||||
--kernel custom-bzImage \
|
||||
--cmdline "console=ttyS0 root=/dev/vda3" \
|
||||
--device path=/sys/bus/pci/devices/0000:00:04.0
|
||||
```
|
||||
@@ -10,7 +10,7 @@ Use one `--net` command-line argument from cloud-hypervisor to specify the emula
|
||||
./cloud-hypervisor \
|
||||
--cpus 4 \
|
||||
--memory "size=512M" \
|
||||
--disk my-root-disk.img \
|
||||
--disk path=my-root-disk.img \
|
||||
--kernel my-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
|
||||
--net tap=ich0,mac=a4:a1:c2:00:00:01,ip=192.168.4.2,mask=255.255.255.0 \
|
||||
|
||||
@@ -66,13 +66,13 @@ takes the device's sysfs path as an argument. In our example it is
|
||||
```
|
||||
./target/debug/cloud-hypervisor \
|
||||
--kernel ~/vmlinux \
|
||||
--disk ~/clear-29160-kvm.img \
|
||||
--disk path=~/clear-29160-kvm.img \
|
||||
--console off \
|
||||
--serial tty \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||
--cpus 4 \
|
||||
--memory size=512M \
|
||||
--device /sys/bus/pci/devices/0000:01:00.0/
|
||||
--device path=/sys/bus/pci/devices/0000:01:00.0/
|
||||
```
|
||||
|
||||
The guest kernel will then detect the card reader on its PCI bus and provided
|
||||
|
||||
@@ -4,4 +4,4 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
@@ -7,9 +7,9 @@ authors = ["The Chromium OS Authors"]
|
||||
libc = "0.2.60"
|
||||
rand = "0.7.0"
|
||||
serde = "1.0.98"
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
net_gen = { path = "../net_gen" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.3.0"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use rand::Rng;
|
||||
use std::fmt;
|
||||
use std::result::Result;
|
||||
|
||||
use serde::de::{Deserialize, Deserializer, Error};
|
||||
@@ -66,14 +67,6 @@ impl MacAddr {
|
||||
&self.bytes
|
||||
}
|
||||
|
||||
pub fn to_string(self) -> String {
|
||||
let b = &self.bytes;
|
||||
format!(
|
||||
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
|
||||
b[0], b[1], b[2], b[3], b[4], b[5]
|
||||
)
|
||||
}
|
||||
|
||||
pub fn local_random() -> MacAddr {
|
||||
// Generate a fully random MAC
|
||||
let mut random_bytes = rand::thread_rng().gen::<[u8; MAC_ADDR_LEN]>();
|
||||
@@ -87,6 +80,17 @@ impl MacAddr {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MacAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let b = &self.bytes;
|
||||
write!(
|
||||
f,
|
||||
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
|
||||
b[0], b[1], b[2], b[3], b[4], b[5]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for MacAddr {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
|
||||
@@ -49,6 +49,15 @@ impl PartialEq for Tap {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::clone::Clone for Tap {
|
||||
fn clone(&self) -> Self {
|
||||
Tap {
|
||||
tap_file: self.tap_file.try_clone().unwrap(),
|
||||
if_name: self.if_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a byte vector representing the contents of a null terminated C string which
|
||||
// contains if_name.
|
||||
fn build_terminated_if_name(if_name: &str) -> Result<Vec<u8>> {
|
||||
|
||||
@@ -13,4 +13,4 @@ kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master"
|
||||
libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
@@ -71,23 +71,19 @@ impl PciDevice for PciRoot {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PciConfigIo {
|
||||
pub struct PciBus {
|
||||
/// Devices attached to this bus.
|
||||
/// Device 0 is host bridge.
|
||||
devices: Vec<Arc<Mutex<dyn PciDevice>>>,
|
||||
/// Config space register.
|
||||
config_address: u32,
|
||||
}
|
||||
|
||||
impl PciConfigIo {
|
||||
impl PciBus {
|
||||
pub fn new(pci_root: PciRoot) -> Self {
|
||||
let mut devices: Vec<Arc<Mutex<dyn PciDevice>>> = Vec::new();
|
||||
|
||||
devices.push(Arc::new(Mutex::new(pci_root)));
|
||||
|
||||
PciConfigIo {
|
||||
devices,
|
||||
config_address: 0,
|
||||
}
|
||||
PciBus { devices }
|
||||
}
|
||||
|
||||
pub fn register_mapping(
|
||||
@@ -119,6 +115,25 @@ impl PciConfigIo {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next_device_id(&self) -> u32 {
|
||||
self.devices.len() as u32
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PciConfigIo {
|
||||
/// Config space register.
|
||||
config_address: u32,
|
||||
pci_bus: Arc<Mutex<PciBus>>,
|
||||
}
|
||||
|
||||
impl PciConfigIo {
|
||||
pub fn new(pci_bus: Arc<Mutex<PciBus>>) -> Self {
|
||||
PciConfigIo {
|
||||
pci_bus,
|
||||
config_address: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config_space_read(&self) -> u32 {
|
||||
let enabled = (self.config_address & 0x8000_0000) != 0;
|
||||
if !enabled {
|
||||
@@ -138,9 +153,14 @@ impl PciConfigIo {
|
||||
return 0xffff_ffff;
|
||||
}
|
||||
|
||||
self.devices.get(device).map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
})
|
||||
self.pci_bus
|
||||
.lock()
|
||||
.unwrap()
|
||||
.devices
|
||||
.get(device)
|
||||
.map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn config_space_write(&mut self, offset: u64, data: &[u8]) {
|
||||
@@ -161,7 +181,7 @@ impl PciConfigIo {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(d) = self.devices.get(device) {
|
||||
if let Some(d) = self.pci_bus.lock().unwrap().devices.get(device) {
|
||||
d.lock()
|
||||
.unwrap()
|
||||
.write_config_register(register, offset, data);
|
||||
@@ -223,17 +243,12 @@ impl BusDevice for PciConfigIo {
|
||||
|
||||
/// Emulates PCI memory-mapped configuration access mechanism.
|
||||
pub struct PciConfigMmio {
|
||||
/// Devices attached to this bus.
|
||||
/// Device 0 is host bridge.
|
||||
devices: Vec<Arc<Mutex<dyn PciDevice>>>,
|
||||
pci_bus: Arc<Mutex<PciBus>>,
|
||||
}
|
||||
|
||||
impl PciConfigMmio {
|
||||
pub fn new(pci_root: PciRoot) -> Self {
|
||||
let mut devices: Vec<Arc<Mutex<dyn PciDevice>>> = Vec::new();
|
||||
|
||||
devices.push(Arc::new(Mutex::new(pci_root)));
|
||||
PciConfigMmio { devices }
|
||||
pub fn new(pci_bus: Arc<Mutex<PciBus>>) -> Self {
|
||||
PciConfigMmio { pci_bus }
|
||||
}
|
||||
|
||||
fn config_space_read(&self, config_address: u32) -> u32 {
|
||||
@@ -244,9 +259,14 @@ impl PciConfigMmio {
|
||||
return 0xffff_ffff;
|
||||
}
|
||||
|
||||
self.devices.get(device).map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
})
|
||||
self.pci_bus
|
||||
.lock()
|
||||
.unwrap()
|
||||
.devices
|
||||
.get(device)
|
||||
.map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
})
|
||||
}
|
||||
|
||||
fn config_space_write(&mut self, config_address: u32, offset: u64, data: &[u8]) {
|
||||
@@ -261,7 +281,7 @@ impl PciConfigMmio {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(d) = self.devices.get(device) {
|
||||
if let Some(d) = self.pci_bus.lock().unwrap().devices.get(device) {
|
||||
d.lock()
|
||||
.unwrap()
|
||||
.write_config_register(register, offset, data);
|
||||
|
||||
@@ -16,7 +16,7 @@ mod device;
|
||||
mod msi;
|
||||
mod msix;
|
||||
|
||||
pub use self::bus::{PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
|
||||
pub use self::bus::{PciBus, PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
|
||||
pub use self::configuration::{
|
||||
PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability, PciCapabilityID,
|
||||
PciClassCode, PciConfiguration, PciHeaderType, PciMassStorageSubclass,
|
||||
|
||||
@@ -13,7 +13,7 @@ byteorder = "1.3.2"
|
||||
libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
remain = "0.1.3"
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
107
release-notes.md
107
release-notes.md
@@ -1,19 +1,98 @@
|
||||
- [v0.3.0](#v030)
|
||||
+ [Block device offloading](#block-device-offloading)
|
||||
+ [Network device backend](#network-device-backend)
|
||||
+ [Virtual sockets](#virtual-sockets)
|
||||
+ [HTTP based API](#http-based-api)
|
||||
+ [Memory mapped virtio transport](#memory-mapped-virtio-transport)
|
||||
+ [Paravirtualized IOMMU](#paravirtualized-iommu)
|
||||
+ [Ubuntu 19.10](#ubuntu-1910)
|
||||
+ [Guest large memory](#guest-large-memory)
|
||||
- [v0.2.0](#v020)
|
||||
- [Network device offloading](#network-device-offloading)
|
||||
- [Minimal hardware-reduced ACPI](#minimal-hardware-reduced-acpi)
|
||||
- [Debug I/O port](#debug-io-port)
|
||||
- [Improved direct device assignment](#improved-direct-device-assignment)
|
||||
- [Improved shared filesystem](#improved-shared-filesystem)
|
||||
- [Ubuntu bionic based CI](#ubuntu-bionic-based-ci)
|
||||
+ [Network device offloading](#network-device-offloading)
|
||||
+ [Minimal hardware-reduced ACPI](#minimal-hardware-reduced-acpi)
|
||||
+ [Debug I/O port](#debug-i-o-port)
|
||||
+ [Improved direct device assignment](#improved-direct-device-assignment)
|
||||
+ [Improved shared filesystem](#improved-shared-filesystem)
|
||||
+ [Ubuntu bionic based CI](#ubuntu-bionic-based-ci)
|
||||
- [v0.1.0](#v010)
|
||||
- [Shared filesystem](#shared-filesystem)
|
||||
- [Initial direct device assignment support](#initial-direct-device-assignment-support)
|
||||
- [Userspace IOAPIC](#userspace-ioapic)
|
||||
- [Virtual persistent memory](#virtual-persistent-memory)
|
||||
- [Linux kernel bzImage](#linux-kernel-bzimage)
|
||||
- [Console over virtio](#console-over-virtio)
|
||||
- [Unit testing](#unit-testing)
|
||||
- [Integration tests parallelization](#integration-tests-parallelization)
|
||||
+ [Shared filesystem](#shared-filesystem)
|
||||
+ [Initial direct device assignment support](#initial-direct-device-assignment-support)
|
||||
+ [Userspace IOAPIC](#userspace-ioapic)
|
||||
+ [Virtual persistent memory](#virtual-persistent-memory)
|
||||
+ [Linux kernel bzImage](#linux-kernel-bzimage)
|
||||
+ [Console over virtio](#console-over-virtio)
|
||||
+ [Unit testing](#unit-testing)
|
||||
+ [Integration tests parallelization](#integration-tests-parallelization)
|
||||
|
||||
# v0.3.0
|
||||
|
||||
This release has been tracked through the [0.3.0 project](https://github.com/intel/cloud-hypervisor/projects/3).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.3.0 include:
|
||||
|
||||
### Block device offloading
|
||||
|
||||
We continue to work on offloading paravirtualized I/O to external processes,
|
||||
and we added support for
|
||||
[vhost-user-blk](https://access.redhat.com/solutions/3394851) backends.
|
||||
This enables `cloud-hypervisor` users to plug a `vhost-user` based block device
|
||||
like [SPDK](https://spdk.io)) into the VMM as their paravirtualized storage
|
||||
backend.
|
||||
|
||||
### Network device backend
|
||||
|
||||
The previous release provided support for
|
||||
[vhost-user-net](https://access.redhat.com/solutions/3394851) backends. Now we
|
||||
also provide a TAP based vhost-user-net backend, implemented in Rust. Together
|
||||
with the vhost-user-net device implementation, this will eventually become the
|
||||
Cloud Hypervisor default paravirtualized networking architecture.
|
||||
|
||||
### Virtual sockets
|
||||
|
||||
In order to more efficiently and securely communicate between host and guest,
|
||||
we added an hybrid implementation of the
|
||||
[VSOCK](http://man7.org/linux/man-pages/man7/vsock.7.html) socket address
|
||||
family over virtio. Credits go to the
|
||||
[Firecracker](https://github.com/firecracker-microvm/firecracker/blob/master/docs/vsock.md)
|
||||
project as our implementation is a copy of theirs.
|
||||
|
||||
### HTTP based API
|
||||
|
||||
In anticipation of the need to support asynchronous operations to Cloud
|
||||
Hypervisor guests (e.g. resources hotplug and guest migration), we added a HTTP
|
||||
based API to the VMM. The API will be more extensively documented during the
|
||||
next release cycle.
|
||||
|
||||
### Memory mapped virtio transport
|
||||
|
||||
In order to support potential PCI-free use cases, we added support for the
|
||||
[virtio MMIO](https://docs.oasis-open.org/virtio/virtio/v1.1/cs01/virtio-v1.1-cs01.html#x1-1440002)
|
||||
transport layer. This will allow us to support simple, minimal guest
|
||||
configurations that do not require a PCI bus emulation.
|
||||
|
||||
### Paravirtualized IOMMU
|
||||
|
||||
As we want to improve our nested guests support, we added support for exposing
|
||||
a [paravirtualized IOMMU](https://github.com/intel/cloud-hypervisor/blob/master/docs/iommu.md)
|
||||
device through virtio. This allows for a safer nested virtio and directly
|
||||
assigned devices support.
|
||||
|
||||
To add the IOMMU support, we had to make some CLI changes for Cloud Hypervisor
|
||||
users to be able to specify if devices had to be handled through this virtual
|
||||
IOMMU or not. In particular, the `--disk` option now expects disk paths to be
|
||||
prefixed with a `path=` string, and supports an optional `iommu=[on|off]`
|
||||
setting.
|
||||
|
||||
### Ubuntu 19.10
|
||||
|
||||
With the latest [hypervisor firmware](https://github.com/intel/rust-hypervisor-firmware),
|
||||
we can now support the latest
|
||||
[Ubuntu 19.10 (Eoan Ermine)](http://releases.ubuntu.com/19.10/) cloud images.
|
||||
|
||||
### Large memory guests
|
||||
|
||||
After simplifying and changing our guest address space handling, we can now
|
||||
support guests with large amount of memory (more than 64GB).
|
||||
|
||||
# v0.2.0
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
#
|
||||
|
||||
#
|
||||
# Compiler: gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
|
||||
# Compiler: gcc (Ubuntu 8.3.0-6ubuntu1) 8.3.0
|
||||
#
|
||||
CONFIG_CC_IS_GCC=y
|
||||
CONFIG_GCC_VERSION=70400
|
||||
CONFIG_GCC_VERSION=80300
|
||||
CONFIG_CLANG_VERSION=0
|
||||
CONFIG_CC_CAN_LINK=y
|
||||
CONFIG_CC_HAS_ASM_GOTO=y
|
||||
@@ -61,6 +61,7 @@ CONFIG_IRQ_DOMAIN=y
|
||||
CONFIG_IRQ_DOMAIN_HIERARCHY=y
|
||||
CONFIG_GENERIC_MSI_IRQ=y
|
||||
CONFIG_GENERIC_MSI_IRQ_DOMAIN=y
|
||||
CONFIG_IRQ_MSI_IOMMU=y
|
||||
CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR=y
|
||||
CONFIG_GENERIC_IRQ_RESERVATION_MODE=y
|
||||
CONFIG_IRQ_FORCED_THREADING=y
|
||||
@@ -485,6 +486,7 @@ CONFIG_HAVE_ACPI_APEI_NMI=y
|
||||
# CONFIG_DPTF_POWER is not set
|
||||
# CONFIG_PMIC_OPREGION is not set
|
||||
# CONFIG_ACPI_CONFIGFS is not set
|
||||
CONFIG_ACPI_IORT=y
|
||||
CONFIG_X86_PM_TIMER=y
|
||||
# CONFIG_SFI is not set
|
||||
|
||||
@@ -584,7 +586,25 @@ CONFIG_EFI_EARLYCON=y
|
||||
# end of Firmware Drivers
|
||||
|
||||
CONFIG_HAVE_KVM=y
|
||||
# CONFIG_VIRTUALIZATION is not set
|
||||
CONFIG_HAVE_KVM_IRQCHIP=y
|
||||
CONFIG_HAVE_KVM_IRQFD=y
|
||||
CONFIG_HAVE_KVM_IRQ_ROUTING=y
|
||||
CONFIG_HAVE_KVM_EVENTFD=y
|
||||
CONFIG_KVM_MMIO=y
|
||||
CONFIG_KVM_ASYNC_PF=y
|
||||
CONFIG_HAVE_KVM_MSI=y
|
||||
CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT=y
|
||||
CONFIG_KVM_VFIO=y
|
||||
CONFIG_KVM_GENERIC_DIRTYLOG_READ_PROTECT=y
|
||||
CONFIG_HAVE_KVM_IRQ_BYPASS=y
|
||||
CONFIG_HAVE_KVM_NO_POLL=y
|
||||
CONFIG_VIRTUALIZATION=y
|
||||
CONFIG_KVM=y
|
||||
CONFIG_KVM_INTEL=y
|
||||
# CONFIG_KVM_AMD is not set
|
||||
# CONFIG_VHOST_NET is not set
|
||||
# CONFIG_VHOST_VSOCK is not set
|
||||
# CONFIG_VHOST_CROSS_ENDIAN_LEGACY is not set
|
||||
|
||||
#
|
||||
# General architecture-dependent options
|
||||
@@ -598,6 +618,7 @@ CONFIG_JUMP_LABEL=y
|
||||
# CONFIG_STATIC_KEYS_SELFTEST is not set
|
||||
CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y
|
||||
CONFIG_ARCH_USE_BUILTIN_BSWAP=y
|
||||
CONFIG_USER_RETURN_NOTIFIER=y
|
||||
CONFIG_HAVE_IOREMAP_PROT=y
|
||||
CONFIG_HAVE_KPROBES=y
|
||||
CONFIG_HAVE_KRETPROBES=y
|
||||
@@ -733,6 +754,7 @@ CONFIG_BLK_PM=y
|
||||
# CONFIG_IOSCHED_BFQ is not set
|
||||
# end of IO Schedulers
|
||||
|
||||
CONFIG_PREEMPT_NOTIFIERS=y
|
||||
CONFIG_INLINE_SPIN_UNLOCK_IRQ=y
|
||||
CONFIG_INLINE_READ_UNLOCK=y
|
||||
CONFIG_INLINE_READ_UNLOCK_IRQ=y
|
||||
@@ -789,6 +811,7 @@ CONFIG_CONTIG_ALLOC=y
|
||||
CONFIG_PHYS_ADDR_T_64BIT=y
|
||||
CONFIG_BOUNCE=y
|
||||
CONFIG_VIRT_TO_BUS=y
|
||||
CONFIG_MMU_NOTIFIER=y
|
||||
CONFIG_KSM=y
|
||||
CONFIG_DEFAULT_MMAP_MIN_ADDR=4096
|
||||
CONFIG_TRANSPARENT_HUGEPAGE=y
|
||||
@@ -1750,6 +1773,17 @@ CONFIG_UIO_DMEM_GENIRQ=y
|
||||
# CONFIG_UIO_NETX is not set
|
||||
# CONFIG_UIO_PRUSS is not set
|
||||
# CONFIG_UIO_MF624 is not set
|
||||
CONFIG_VFIO_IOMMU_TYPE1=y
|
||||
CONFIG_VFIO_VIRQFD=y
|
||||
CONFIG_VFIO=y
|
||||
# CONFIG_VFIO_NOIOMMU is not set
|
||||
CONFIG_VFIO_PCI=y
|
||||
# CONFIG_VFIO_PCI_VGA is not set
|
||||
CONFIG_VFIO_PCI_MMAP=y
|
||||
CONFIG_VFIO_PCI_INTX=y
|
||||
# CONFIG_VFIO_PCI_IGD is not set
|
||||
# CONFIG_VFIO_MDEV is not set
|
||||
CONFIG_IRQ_BYPASS_MANAGER=y
|
||||
# CONFIG_VIRT_DRIVERS is not set
|
||||
CONFIG_VIRTIO=y
|
||||
CONFIG_VIRTIO_MENU=y
|
||||
@@ -1794,6 +1828,8 @@ CONFIG_CLKBLD_I8253=y
|
||||
CONFIG_MAILBOX=y
|
||||
CONFIG_PCC=y
|
||||
# CONFIG_ALTERA_MBOX is not set
|
||||
CONFIG_IOMMU_IOVA=y
|
||||
CONFIG_IOMMU_API=y
|
||||
CONFIG_IOMMU_SUPPORT=y
|
||||
|
||||
#
|
||||
@@ -1802,9 +1838,12 @@ CONFIG_IOMMU_SUPPORT=y
|
||||
# end of Generic IOMMU Pagetable Support
|
||||
|
||||
# CONFIG_IOMMU_DEBUGFS is not set
|
||||
# CONFIG_IOMMU_DEFAULT_PASSTHROUGH is not set
|
||||
CONFIG_IOMMU_DMA=y
|
||||
# CONFIG_AMD_IOMMU is not set
|
||||
# CONFIG_INTEL_IOMMU is not set
|
||||
# CONFIG_IRQ_REMAP is not set
|
||||
CONFIG_VIRTIO_IOMMU=y
|
||||
|
||||
#
|
||||
# Remoteproc drivers
|
||||
@@ -2368,6 +2407,7 @@ CONFIG_ZLIB_DEFLATE=y
|
||||
CONFIG_LZO_COMPRESS=y
|
||||
CONFIG_LZO_DECOMPRESS=y
|
||||
# CONFIG_XZ_DEC is not set
|
||||
CONFIG_INTERVAL_TREE=y
|
||||
CONFIG_XARRAY_MULTI=y
|
||||
CONFIG_ASSOCIATIVE_ARRAY=y
|
||||
CONFIG_HAS_IOMEM=y
|
||||
@@ -14,25 +14,16 @@ if [ ! -f "$FW" ]; then
|
||||
popd
|
||||
fi
|
||||
|
||||
OVMF_URL="https://cdn.download.clearlinux.org/image/OVMF.fd"
|
||||
OVMF="$WORKLOADS_DIR/OVMF.fd"
|
||||
if [ ! -f "$OVMF" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
wget --quiet $OVMF_URL
|
||||
popd
|
||||
fi
|
||||
|
||||
CLEAR_OS_IMAGE_NAME="clear-29810-cloud.img"
|
||||
CLEAR_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$CLEAR_OS_IMAGE_NAME.xz"
|
||||
CLEAR_OS_IMAGE_NAME="clear-cloudguest.img"
|
||||
CLEAR_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$CLEAR_OS_IMAGE_NAME"
|
||||
CLEAR_OS_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_IMAGE_NAME"
|
||||
if [ ! -f "$CLEAR_OS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
wget --quiet $CLEAR_OS_IMAGE_URL
|
||||
unxz $CLEAR_OS_IMAGE_NAME.xz
|
||||
popd
|
||||
fi
|
||||
|
||||
CLEAR_OS_RAW_IMAGE_NAME="clear-29810-cloud-raw.img"
|
||||
CLEAR_OS_RAW_IMAGE_NAME="clear-cloudguest-raw.img"
|
||||
CLEAR_OS_RAW_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$CLEAR_OS_RAW_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
@@ -58,6 +49,24 @@ if [ ! -f "$BIONIC_OS_RAW_IMAGE" ]; then
|
||||
fi
|
||||
|
||||
|
||||
EOAN_OS_IMAGE_NAME="eoan-server-cloudimg-amd64.img"
|
||||
EOAN_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$EOAN_OS_IMAGE_NAME"
|
||||
EOAN_OS_IMAGE="$WORKLOADS_DIR/$EOAN_OS_IMAGE_NAME"
|
||||
if [ ! -f "$EOAN_OS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
wget --quiet $EOAN_OS_IMAGE_URL
|
||||
popd
|
||||
fi
|
||||
|
||||
EOAN_OS_RAW_IMAGE_NAME="eoan-server-cloudimg-amd64-raw.img"
|
||||
EOAN_OS_RAW_IMAGE="$WORKLOADS_DIR/$EOAN_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$EOAN_OS_RAW_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
qemu-img convert -p -f qcow2 -O raw $EOAN_OS_IMAGE_NAME $EOAN_OS_RAW_IMAGE_NAME
|
||||
popd
|
||||
fi
|
||||
|
||||
|
||||
# Build custom kernel based on virtio-pmem and virtio-fs upstream patches
|
||||
VMLINUX_IMAGE="$WORKLOADS_DIR/vmlinux"
|
||||
BZIMAGE_IMAGE="$WORKLOADS_DIR/bzImage"
|
||||
@@ -67,9 +76,9 @@ LINUX_CUSTOM_DIR="linux-custom"
|
||||
if [ ! -f "$VMLINUX_IMAGE" ]; then
|
||||
SRCDIR=$PWD
|
||||
pushd $WORKLOADS_DIR
|
||||
git clone --depth 1 "https://github.com/sboeuf/linux.git" -b "virtio-fs" $LINUX_CUSTOM_DIR
|
||||
git clone --depth 1 "https://github.com/sboeuf/linux.git" -b "virtio-fs-virtio-iommu" $LINUX_CUSTOM_DIR
|
||||
pushd $LINUX_CUSTOM_DIR
|
||||
cp $SRCDIR/resources/linux-virtio-fs-config .config
|
||||
cp $SRCDIR/resources/linux-virtio-fs-virtio-iommu-config .config
|
||||
make bzImage -j `nproc`
|
||||
cp vmlinux $VMLINUX_IMAGE
|
||||
cp arch/x86/boot/bzImage $BZIMAGE_IMAGE
|
||||
@@ -79,22 +88,36 @@ if [ ! -f "$VMLINUX_IMAGE" ]; then
|
||||
fi
|
||||
|
||||
VIRTIOFSD="$WORKLOADS_DIR/virtiofsd"
|
||||
VUBRIDGE="$WORKLOADS_DIR/vubridge"
|
||||
VUBD="$WORKLOADS_DIR/vubd"
|
||||
QEMU_DIR="qemu_build"
|
||||
if [ ! -f "$VIRTIOFSD" ] || [ ! -f "$VUBRIDGE" ]; then
|
||||
if [ ! -f "$VIRTIOFSD" ] || [ ! -f "$VUBD" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
git clone --depth 1 "https://github.com/sboeuf/qemu.git" -b "virtio-fs" $QEMU_DIR
|
||||
pushd $QEMU_DIR
|
||||
./configure --prefix=$PWD --target-list=x86_64-softmmu
|
||||
make virtiofsd tests/vhost-user-bridge -j `nproc`
|
||||
make virtiofsd vhost-user-blk -j `nproc`
|
||||
cp virtiofsd $VIRTIOFSD
|
||||
cp tests/vhost-user-bridge $VUBRIDGE
|
||||
cp vhost-user-blk $VUBD
|
||||
popd
|
||||
rm -rf $QEMU_DIR
|
||||
sudo setcap cap_dac_override,cap_sys_admin+epi "virtiofsd"
|
||||
popd
|
||||
fi
|
||||
|
||||
BLK_IMAGE="$WORKLOADS_DIR/blk.img"
|
||||
MNT_DIR="mount_image"
|
||||
if [ ! -f "$BLK_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
fallocate -l 16M $BLK_IMAGE
|
||||
mkfs.ext4 -j $BLK_IMAGE
|
||||
mkdir $MNT_DIR
|
||||
sudo mount -t ext4 $BLK_IMAGE $MNT_DIR
|
||||
sudo bash -c "echo bar > $MNT_DIR/foo"
|
||||
sudo umount $BLK_IMAGE
|
||||
rm -r $MNT_DIR
|
||||
popd
|
||||
fi
|
||||
|
||||
SHARED_DIR="$WORKLOADS_DIR/shared_dir"
|
||||
if [ ! -d "$SHARED_DIR" ]; then
|
||||
mkdir -p $SHARED_DIR
|
||||
@@ -111,10 +134,10 @@ if [ ! -d "$VFIO_DIR" ]; then
|
||||
fi
|
||||
|
||||
# VFIO test network setup.
|
||||
# We reserve a different IP class for it: 172.16.0.0/24.
|
||||
# We reserve a different IP class for it: 172.17.0.0/24.
|
||||
sudo ip link add name vfio-br0 type bridge
|
||||
sudo ip link set vfio-br0 up
|
||||
sudo ip addr add 172.16.0.1/24 dev vfio-br0
|
||||
sudo ip addr add 172.17.0.1/24 dev vfio-br0
|
||||
|
||||
sudo ip tuntap add vfio-tap0 mode tap
|
||||
sudo ip link set vfio-tap0 master vfio-br0
|
||||
@@ -126,19 +149,31 @@ sudo ip link set vfio-tap1 up
|
||||
|
||||
cargo build
|
||||
sudo setcap cap_net_admin+ep target/debug/cloud-hypervisor
|
||||
sudo setcap cap_net_admin+ep target/debug/vhost_user_net
|
||||
|
||||
# We always copy a fresh version of our binary for our L2 guest.
|
||||
cp target/debug/cloud-hypervisor $VFIO_DIR
|
||||
# We need qemu to have NET_ADMIN as well.
|
||||
sudo setcap cap_net_admin+ep /usr/bin/qemu-system-x86_64
|
||||
|
||||
sudo adduser $USER kvm
|
||||
newgrp kvm << EOF
|
||||
export RUST_BACKTRACE=1
|
||||
cargo test --features "integration_tests"
|
||||
cargo test --features "integration_tests" -- --nocapture
|
||||
EOF
|
||||
RES=$?
|
||||
|
||||
if [ $RES -eq 0 ]; then
|
||||
# virtio-mmio based testing
|
||||
cargo build --no-default-features --features "mmio"
|
||||
sudo setcap cap_net_admin+ep target/debug/cloud-hypervisor
|
||||
|
||||
newgrp kvm << EOF
|
||||
export RUST_BACKTRACE=1
|
||||
cargo test --features "integration_tests,mmio" -- --nocapture
|
||||
EOF
|
||||
|
||||
RES=$?
|
||||
fi
|
||||
|
||||
# Tear VFIO test network down
|
||||
sudo ip link del vfio-br0
|
||||
sudo ip link del vfio-tap0
|
||||
|
||||
597
src/bin/vhost_user_net.rs
Normal file
597
src/bin/vhost_user_net.rs
Normal file
@@ -0,0 +1,597 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause)
|
||||
|
||||
#[macro_use(crate_version, crate_authors)]
|
||||
extern crate clap;
|
||||
extern crate log;
|
||||
extern crate net_util;
|
||||
extern crate vhost_rs;
|
||||
extern crate vhost_user_backend;
|
||||
extern crate vm_virtio;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use epoll;
|
||||
use libc::{self, EAGAIN, EFD_NONBLOCK};
|
||||
use log::*;
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
use std::io::Read;
|
||||
use std::io::{self, Write};
|
||||
use std::mem;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::process;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::vec::Vec;
|
||||
|
||||
use vhost_rs::vhost_user::message::*;
|
||||
use vhost_rs::vhost_user::Error as VhostUserError;
|
||||
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
|
||||
|
||||
use net_gen;
|
||||
|
||||
use net_util::{Tap, TapError};
|
||||
use virtio_bindings::bindings::virtio_net::*;
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// The maximum buffer size when segmentation offload is enabled. This
|
||||
/// includes the 12-byte virtio net header.
|
||||
/// http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html#x1-1740003
|
||||
const MAX_BUFFER_SIZE: usize = 65562;
|
||||
const QUEUE_SIZE: usize = 1024;
|
||||
const NUM_QUEUES: usize = 2;
|
||||
|
||||
// The guest has made a buffer available to receive a frame into.
|
||||
const RX_QUEUE_EVENT: u16 = 0;
|
||||
// The transmit queue has a frame that is ready to send from the guest.
|
||||
const TX_QUEUE_EVENT: u16 = 1;
|
||||
// A frame is available for reading from the tap device to receive in the guest.
|
||||
const RX_TAP_EVENT: u16 = 2;
|
||||
// The device has been dropped.
|
||||
const KILL_EVENT: u16 = 3;
|
||||
|
||||
pub type VhostUserResult<T> = std::result::Result<T, VhostUserError>;
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to activate device.
|
||||
BadActivate,
|
||||
/// Failed to create kill eventfd
|
||||
CreateKillEventFd,
|
||||
/// Failed to add event.
|
||||
EpollCtl(io::Error),
|
||||
/// Fail to wait event.
|
||||
EpollWait(io::Error),
|
||||
/// Failed to create EventFd.
|
||||
EpollCreateFd,
|
||||
/// Failed to read Tap.
|
||||
FailedReadTap,
|
||||
/// Failed to signal used queue.
|
||||
FailedSignalingUsedQueue,
|
||||
/// Failed to handle event other than input event.
|
||||
HandleEventNotEpollIn,
|
||||
/// Failed to handle unknown event.
|
||||
HandleEventUnknownEvent,
|
||||
/// Invalid vring address.
|
||||
InvalidVringAddr,
|
||||
/// No vring call fd to notify.
|
||||
NoVringCallFdNotify,
|
||||
/// No memory configured.
|
||||
NoMemoryConfigured,
|
||||
/// Failed to parse sock parameter.
|
||||
ParseSockParam,
|
||||
/// Failed to parse ip parameter.
|
||||
ParseIpParam,
|
||||
/// Failed to parse mask parameter.
|
||||
ParseMaskParam,
|
||||
/// Open tap device failed.
|
||||
TapOpen(TapError),
|
||||
/// Setting tap IP failed.
|
||||
TapSetIp(TapError),
|
||||
/// Setting tap netmask failed.
|
||||
TapSetNetmask(TapError),
|
||||
/// Setting tap interface offload flags failed.
|
||||
TapSetOffload(TapError),
|
||||
/// Setting vnet header size failed.
|
||||
TapSetVnetHdrSize(TapError),
|
||||
/// Enabling tap interface failed.
|
||||
TapEnable(TapError),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "vhost_user_net_error: {:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl std::convert::From<Error> for std::io::Error {
|
||||
fn from(e: Error) -> Self {
|
||||
std::io::Error::new(io::ErrorKind::Other, e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TxVirtio {
|
||||
iovec: Vec<(GuestAddress, usize)>,
|
||||
frame_buf: [u8; MAX_BUFFER_SIZE],
|
||||
}
|
||||
|
||||
impl TxVirtio {
|
||||
fn new() -> Self {
|
||||
TxVirtio {
|
||||
iovec: Vec::new(),
|
||||
frame_buf: [0u8; MAX_BUFFER_SIZE],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RxVirtio {
|
||||
deferred_frame: bool,
|
||||
deferred_irqs: bool,
|
||||
bytes_read: usize,
|
||||
frame_buf: [u8; MAX_BUFFER_SIZE],
|
||||
}
|
||||
|
||||
impl RxVirtio {
|
||||
fn new() -> Self {
|
||||
RxVirtio {
|
||||
deferred_frame: false,
|
||||
deferred_irqs: false,
|
||||
bytes_read: 0,
|
||||
frame_buf: [0u8; MAX_BUFFER_SIZE],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn vnet_hdr_len() -> usize {
|
||||
mem::size_of::<virtio_net_hdr_v1>()
|
||||
}
|
||||
|
||||
struct VhostUserNetBackend {
|
||||
mem: Option<GuestMemoryMmap>,
|
||||
vring_worker: Option<Arc<VringWorker>>,
|
||||
kill_evt: EventFd,
|
||||
tap: Tap,
|
||||
rx: RxVirtio,
|
||||
tx: TxVirtio,
|
||||
rx_tap_listening: bool,
|
||||
}
|
||||
|
||||
impl std::clone::Clone for VhostUserNetBackend {
|
||||
fn clone(&self) -> Self {
|
||||
VhostUserNetBackend {
|
||||
mem: self.mem.clone(),
|
||||
vring_worker: self.vring_worker.clone(),
|
||||
kill_evt: self.kill_evt.try_clone().unwrap(),
|
||||
tap: self.tap.clone(),
|
||||
rx: self.rx.clone(),
|
||||
tx: self.tx.clone(),
|
||||
rx_tap_listening: self.rx_tap_listening,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserNetBackend {
|
||||
/// Create a new virtio network device with the given TAP interface.
|
||||
pub fn new_with_tap(tap: Tap) -> Result<Self> {
|
||||
// Set offload flags to match the virtio features below.
|
||||
tap.set_offload(
|
||||
net_gen::TUN_F_CSUM | net_gen::TUN_F_UFO | net_gen::TUN_F_TSO4 | net_gen::TUN_F_TSO6,
|
||||
)
|
||||
.map_err(Error::TapSetOffload)?;
|
||||
|
||||
let vnet_hdr_size = vnet_hdr_len() as i32;
|
||||
tap.set_vnet_hdr_size(vnet_hdr_size)
|
||||
.map_err(Error::TapSetVnetHdrSize)?;
|
||||
|
||||
let rx = RxVirtio::new();
|
||||
let tx = TxVirtio::new();
|
||||
|
||||
Ok(VhostUserNetBackend {
|
||||
mem: None,
|
||||
vring_worker: None,
|
||||
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(|_| Error::CreateKillEventFd)?,
|
||||
tap,
|
||||
rx,
|
||||
tx,
|
||||
rx_tap_listening: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new virtio network device with the given IP address and
|
||||
/// netmask.
|
||||
pub fn new(ip_addr: Ipv4Addr, netmask: Ipv4Addr) -> Result<Self> {
|
||||
let tap = Tap::new().map_err(Error::TapOpen)?;
|
||||
tap.set_ip_addr(ip_addr).map_err(Error::TapSetIp)?;
|
||||
tap.set_netmask(netmask).map_err(Error::TapSetNetmask)?;
|
||||
tap.enable().map_err(Error::TapEnable)?;
|
||||
|
||||
Self::new_with_tap(tap)
|
||||
}
|
||||
|
||||
// Copies a single frame from `self.rx.frame_buf` into the guest. Returns true
|
||||
// if a buffer was used, and false if the frame must be deferred until a buffer
|
||||
// is made available by the driver.
|
||||
fn rx_single_frame(&mut self, vring: &mut Vring) -> Result<bool> {
|
||||
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
|
||||
|
||||
let mut next_desc = vring.mut_queue().iter(&mem).next();
|
||||
|
||||
if next_desc.is_none() {
|
||||
// Queue has no available descriptors
|
||||
if self.rx_tap_listening {
|
||||
self.vring_worker
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.unregister_listener(
|
||||
self.tap.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(RX_TAP_EVENT),
|
||||
)
|
||||
.unwrap();
|
||||
self.rx_tap_listening = false;
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// We just checked that the head descriptor exists.
|
||||
let head_index = next_desc.as_ref().unwrap().index;
|
||||
let mut write_count = 0;
|
||||
|
||||
// Copy from frame into buffer, which may span multiple descriptors.
|
||||
loop {
|
||||
match next_desc {
|
||||
Some(desc) => {
|
||||
if !desc.is_write_only() {
|
||||
break;
|
||||
}
|
||||
let limit = cmp::min(write_count + desc.len as usize, self.rx.bytes_read);
|
||||
let source_slice = &self.rx.frame_buf[write_count..limit];
|
||||
let write_result = mem.write_slice(source_slice, desc.addr);
|
||||
|
||||
match write_result {
|
||||
Ok(_) => {
|
||||
write_count = limit;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to write slice: {:?}", e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if write_count >= self.rx.bytes_read {
|
||||
break;
|
||||
}
|
||||
next_desc = desc.next_descriptor();
|
||||
}
|
||||
None => {
|
||||
warn!("Receiving buffer is too small to hold frame of current size");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vring
|
||||
.mut_queue()
|
||||
.add_used(&mem, head_index, write_count as u32);
|
||||
|
||||
// Mark that we have at least one pending packet and we need to interrupt the guest.
|
||||
self.rx.deferred_irqs = true;
|
||||
|
||||
Ok(write_count >= self.rx.bytes_read)
|
||||
}
|
||||
|
||||
fn process_rx(&mut self, vring: &mut Vring) -> Result<()> {
|
||||
// Read as many frames as possible.
|
||||
loop {
|
||||
match self.read_tap() {
|
||||
Ok(count) => {
|
||||
self.rx.bytes_read = count;
|
||||
if !self.rx_single_frame(vring)? {
|
||||
self.rx.deferred_frame = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// The tap device is non-blocking, so any error aside from EAGAIN is
|
||||
// unexpected.
|
||||
match e.raw_os_error() {
|
||||
Some(err) if err == EAGAIN => (),
|
||||
_ => {
|
||||
error!("Failed to read tap: {:?}", e);
|
||||
return Err(Error::FailedReadTap);
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.rx.deferred_irqs {
|
||||
self.rx.deferred_irqs = false;
|
||||
vring.signal_used_queue().unwrap();
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn resume_rx(&mut self, vring: &mut Vring) -> Result<()> {
|
||||
if self.rx.deferred_frame {
|
||||
if self.rx_single_frame(vring)? {
|
||||
self.rx.deferred_frame = false;
|
||||
// process_rx() was interrupted possibly before consuming all
|
||||
// packets in the tap; try continuing now.
|
||||
self.process_rx(vring)
|
||||
} else if self.rx.deferred_irqs {
|
||||
self.rx.deferred_irqs = false;
|
||||
vring.signal_used_queue().unwrap();
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn process_tx(&mut self, vring: &mut Vring) -> Result<()> {
|
||||
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
|
||||
|
||||
let mut used_desc_heads = [(0, 0); QUEUE_SIZE];
|
||||
let mut used_count = 0;
|
||||
while let Some(avail_desc) = vring.mut_queue().iter(&mem).next() {
|
||||
let head_index = avail_desc.index;
|
||||
let mut read_count = 0;
|
||||
let mut next_desc = Some(avail_desc);
|
||||
|
||||
self.tx.iovec.clear();
|
||||
while let Some(desc) = next_desc {
|
||||
if desc.is_write_only() {
|
||||
break;
|
||||
}
|
||||
self.tx.iovec.push((desc.addr, desc.len as usize));
|
||||
read_count += desc.len as usize;
|
||||
next_desc = desc.next_descriptor();
|
||||
}
|
||||
used_desc_heads[used_count] = (head_index, read_count);
|
||||
used_count += 1;
|
||||
read_count = 0;
|
||||
// Copy buffer from across multiple descriptors.
|
||||
// TODO(performance - Issue #420): change this to use `writev()` instead of `write()`
|
||||
// and get rid of the intermediate buffer.
|
||||
for (desc_addr, desc_len) in self.tx.iovec.drain(..) {
|
||||
let limit = cmp::min((read_count + desc_len) as usize, self.tx.frame_buf.len());
|
||||
|
||||
let read_result = mem.read_slice(
|
||||
&mut self.tx.frame_buf[read_count..limit as usize],
|
||||
desc_addr,
|
||||
);
|
||||
match read_result {
|
||||
Ok(_) => {
|
||||
// Increment by number of bytes actually read
|
||||
read_count += limit - read_count;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to read slice: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let write_result = self.tap.write(&self.tx.frame_buf[..read_count as usize]);
|
||||
match write_result {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("net: tx: error failed to write to tap: {}", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if used_count > 0 {
|
||||
for &(desc_index, _) in &used_desc_heads[..used_count] {
|
||||
vring.mut_queue().add_used(&mem, desc_index, 0);
|
||||
}
|
||||
vring.signal_used_queue().unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_tap(&mut self) -> io::Result<usize> {
|
||||
self.tap.read(&mut self.rx.frame_buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserBackend for VhostUserNetBackend {
|
||||
fn num_queues(&self) -> usize {
|
||||
NUM_QUEUES
|
||||
}
|
||||
|
||||
fn max_queue_size(&self) -> usize {
|
||||
QUEUE_SIZE
|
||||
}
|
||||
|
||||
fn features(&self) -> u64 {
|
||||
1 << VIRTIO_NET_F_GUEST_CSUM
|
||||
| 1 << VIRTIO_NET_F_CSUM
|
||||
| 1 << VIRTIO_NET_F_GUEST_TSO4
|
||||
| 1 << VIRTIO_NET_F_GUEST_UFO
|
||||
| 1 << VIRTIO_NET_F_HOST_TSO4
|
||||
| 1 << VIRTIO_NET_F_HOST_UFO
|
||||
| 1 << VIRTIO_F_VERSION_1
|
||||
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
|
||||
}
|
||||
|
||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
||||
self.mem = Some(mem);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
vrings: &[Arc<RwLock<Vring>>],
|
||||
) -> VhostUserBackendResult<bool> {
|
||||
if evset != epoll::Events::EPOLLIN {
|
||||
return Err(Error::HandleEventNotEpollIn.into());
|
||||
}
|
||||
|
||||
match device_event {
|
||||
RX_QUEUE_EVENT => {
|
||||
let mut vring = vrings[0].write().unwrap();
|
||||
self.resume_rx(&mut vring)?;
|
||||
|
||||
if !self.rx_tap_listening {
|
||||
self.vring_worker.as_ref().unwrap().register_listener(
|
||||
self.tap.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(RX_TAP_EVENT),
|
||||
)?;
|
||||
self.rx_tap_listening = true;
|
||||
}
|
||||
}
|
||||
TX_QUEUE_EVENT => {
|
||||
let mut vring = vrings[1].write().unwrap();
|
||||
self.process_tx(&mut vring)?;
|
||||
}
|
||||
RX_TAP_EVENT => {
|
||||
let mut vring = vrings[0].write().unwrap();
|
||||
if self.rx.deferred_frame
|
||||
// Process a deferred frame first if available. Don't read from tap again
|
||||
// until we manage to receive this deferred frame.
|
||||
{
|
||||
if self.rx_single_frame(&mut vring)? {
|
||||
self.rx.deferred_frame = false;
|
||||
self.process_rx(&mut vring)?;
|
||||
} else if self.rx.deferred_irqs {
|
||||
self.rx.deferred_irqs = false;
|
||||
vring.signal_used_queue()?;
|
||||
}
|
||||
} else {
|
||||
self.process_rx(&mut vring)?;
|
||||
}
|
||||
}
|
||||
KILL_EVENT => {
|
||||
self.kill_evt.read().unwrap();
|
||||
return Ok(true);
|
||||
}
|
||||
_ => return Err(Error::HandleEventUnknownEvent.into()),
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VhostUserNetBackendConfig<'a> {
|
||||
pub ip: Ipv4Addr,
|
||||
pub mask: Ipv4Addr,
|
||||
pub sock: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> VhostUserNetBackendConfig<'a> {
|
||||
pub fn parse(backend: &'a str) -> Result<Self> {
|
||||
let params_list: Vec<&str> = backend.split(',').collect();
|
||||
|
||||
let mut ip_str: &str = "";
|
||||
let mut mask_str: &str = "";
|
||||
let mut sock: &str = "";
|
||||
|
||||
for param in params_list.iter() {
|
||||
if param.starts_with("ip=") {
|
||||
ip_str = ¶m[3..];
|
||||
} else if param.starts_with("mask=") {
|
||||
mask_str = ¶m[5..];
|
||||
} else if param.starts_with("sock=") {
|
||||
sock = ¶m[5..];
|
||||
}
|
||||
}
|
||||
|
||||
let mut ip: Ipv4Addr = Ipv4Addr::new(192, 168, 100, 1);
|
||||
let mut mask: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0);
|
||||
|
||||
if sock.is_empty() {
|
||||
return Err(Error::ParseSockParam);
|
||||
}
|
||||
if !ip_str.is_empty() {
|
||||
ip = ip_str.parse().map_err(|_| Error::ParseIpParam)?;
|
||||
}
|
||||
if !mask_str.is_empty() {
|
||||
mask = mask_str.parse().map_err(|_| Error::ParseMaskParam)?;
|
||||
}
|
||||
|
||||
Ok(VhostUserNetBackendConfig { ip, mask, sock })
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cmd_arguments = App::new("vhost-user-net backend")
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.about("Launch a vhost-user-net backend.")
|
||||
.arg(
|
||||
Arg::with_name("backend")
|
||||
.long("backend")
|
||||
.help(
|
||||
"Backend parameters \"ip=<ip_addr>,\
|
||||
mask=<net_mask>,sock=<socket_path>\"",
|
||||
)
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let vhost_user_net_backend = cmd_arguments.value_of("backend").unwrap();
|
||||
|
||||
let backend_config = match VhostUserNetBackendConfig::parse(vhost_user_net_backend) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
println!("Failed parsing parameters {:?}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let net_backend = Arc::new(RwLock::new(
|
||||
VhostUserNetBackend::new(backend_config.ip, backend_config.mask).unwrap(),
|
||||
));
|
||||
let name = "vhost-user-net-backend";
|
||||
let mut net_daemon = VhostUserDaemon::new(
|
||||
name.to_string(),
|
||||
backend_config.sock.to_string(),
|
||||
net_backend.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let vring_worker = net_daemon.get_vring_worker();
|
||||
|
||||
if let Err(e) = vring_worker.register_listener(
|
||||
net_backend.read().unwrap().kill_evt.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(KILL_EVENT),
|
||||
) {
|
||||
println!("failed to register listener for kill event: {:?}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
net_backend.write().unwrap().vring_worker = Some(vring_worker);
|
||||
|
||||
if let Err(e) = net_daemon.start() {
|
||||
println!(
|
||||
"failed to start daemon for vhost-user-net with error: {:?}",
|
||||
e
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
net_daemon.wait().unwrap();
|
||||
}
|
||||
1598
src/main.rs
1598
src/main.rs
File diff suppressed because it is too large
Load Diff
@@ -47,10 +47,8 @@ write_files:
|
||||
content: |
|
||||
#!/bin/bash
|
||||
|
||||
mount -t 9p -o trans=virtio cloud_hypervisor /mnt -oversion=9p2000.L,posixacl,cache=loose
|
||||
modprobe vfio_iommu_type1 allow_unsafe_interrupts
|
||||
modprobe vfio_pci
|
||||
bash -c "echo 0000:00:03.0 > /sys/bus/pci/devices/0000\:00\:03.0/driver/unbind"
|
||||
mount -t virtio_fs virtiofs /mnt -o rootmode=040000,user_id=0,group_id=0,dax
|
||||
bash -c "echo 0000:00:05.0 > /sys/bus/pci/devices/0000\:00\:05.0/driver/unbind"
|
||||
bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id"
|
||||
|
||||
/mnt/cloud-hypervisor --console off --serial tty --kernel /mnt/vmlinux --cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda2 VFIOTAG" --disk /mnt/clear-29810-cloud.img /mnt/cloudinit.img --cpus 1 --memory size=512M --rng --device /sys/bus/pci/devices/0000:00:03.0/
|
||||
/mnt/cloud-hypervisor --kernel /mnt/vmlinux --cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda2 VFIOTAG" --disk path=/mnt/clear-cloudguest.img path=/mnt/cloudinit.img --cpus 1 --memory size=512M --rng --device path=/sys/bus/pci/devices/0000:00:05.0/
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
[package]
|
||||
name = "vfio-bindings"
|
||||
version = "0.0.1"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
||||
[features]
|
||||
default = ["v5_0_0"]
|
||||
v5_0_0 = []
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
// generated with bindgen linux/uapi/linux/vfio.h --constified-enum '*' --with-derive-default
|
||||
#[cfg(feature = "v5_0_0")]
|
||||
mod v5_0_0;
|
||||
|
||||
pub mod bindings {
|
||||
#[cfg(feature = "v5_0_0")]
|
||||
pub use super::v5_0_0::*;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#![allow(clippy::all)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
pub mod vfio;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,9 +11,10 @@ kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master"
|
||||
libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
pci = { path = "../pci" }
|
||||
vfio-bindings = { path = "../vfio-bindings" }
|
||||
vfio-bindings = "0.1.0"
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
[dependencies.vm-memory]
|
||||
git = "https://github.com/rust-vmm/vm-memory"
|
||||
|
||||
@@ -14,6 +14,7 @@ extern crate log;
|
||||
extern crate pci;
|
||||
extern crate vfio_bindings;
|
||||
extern crate vm_allocator;
|
||||
extern crate vm_device;
|
||||
extern crate vm_memory;
|
||||
#[macro_use]
|
||||
extern crate vmm_sys_util;
|
||||
@@ -24,7 +25,7 @@ mod vfio_pci;
|
||||
|
||||
use std::mem::size_of;
|
||||
|
||||
pub use vfio_device::{VfioDevice, VfioError};
|
||||
pub use vfio_device::{VfioContainer, VfioDevice, VfioDmaMapping, VfioError};
|
||||
pub use vfio_pci::{VfioPciDevice, VfioPciError};
|
||||
|
||||
// Returns a `Vec<T>` with a size in bytes at least as large as `size_in_bytes`.
|
||||
|
||||
@@ -14,11 +14,13 @@ use std::mem;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::os::unix::prelude::FileExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::result;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::u32;
|
||||
use vfio_bindings::bindings::vfio::*;
|
||||
use vfio_ioctls::*;
|
||||
use vm_memory::{Address, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
|
||||
use vm_device::ExternalDmaMapping;
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::ioctl::*;
|
||||
|
||||
@@ -103,7 +105,7 @@ struct vfio_region_info_with_cap {
|
||||
cap_info: __IncompleteArrayField<u8>,
|
||||
}
|
||||
|
||||
struct VfioContainer {
|
||||
pub struct VfioContainer {
|
||||
container: File,
|
||||
}
|
||||
|
||||
@@ -151,7 +153,7 @@ impl VfioContainer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
|
||||
pub fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
|
||||
let dma_map = vfio_iommu_type1_dma_map {
|
||||
argsz: mem::size_of::<vfio_iommu_type1_dma_map>() as u32,
|
||||
flags: VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
|
||||
@@ -170,7 +172,7 @@ impl VfioContainer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
|
||||
pub fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
|
||||
let mut dma_unmap = vfio_iommu_type1_dma_unmap {
|
||||
argsz: mem::size_of::<vfio_iommu_type1_dma_unmap>() as u32,
|
||||
flags: 0,
|
||||
@@ -198,7 +200,7 @@ impl AsRawFd for VfioContainer {
|
||||
struct VfioGroup {
|
||||
group: File,
|
||||
device: Arc<DeviceFd>,
|
||||
container: VfioContainer,
|
||||
container: Arc<VfioContainer>,
|
||||
}
|
||||
|
||||
impl VfioGroup {
|
||||
@@ -225,7 +227,7 @@ impl VfioGroup {
|
||||
return Err(VfioError::GroupViable);
|
||||
}
|
||||
|
||||
let container = VfioContainer::new()?;
|
||||
let container = Arc::new(VfioContainer::new()?);
|
||||
if container.get_api_version() as u32 != VFIO_API_VERSION {
|
||||
return Err(VfioError::VfioApiVersion);
|
||||
}
|
||||
@@ -506,6 +508,68 @@ impl VfioDeviceInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// This structure implements the ExternalDmaMapping trait. It is meant to
|
||||
/// be used when the caller tries to provide a way to update the mappings
|
||||
/// associated with a specific VFIO container.
|
||||
pub struct VfioDmaMapping {
|
||||
container: Arc<VfioContainer>,
|
||||
memory: Arc<RwLock<GuestMemoryMmap>>,
|
||||
}
|
||||
|
||||
impl VfioDmaMapping {
|
||||
pub fn new(container: Arc<VfioContainer>, memory: Arc<RwLock<GuestMemoryMmap>>) -> Self {
|
||||
VfioDmaMapping { container, memory }
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternalDmaMapping for VfioDmaMapping {
|
||||
fn map(&self, iova: u64, gpa: u64, size: u64) -> result::Result<(), io::Error> {
|
||||
let user_addr = if let Some(addr) = self
|
||||
.memory
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_host_address(GuestAddress(gpa))
|
||||
{
|
||||
addr as u64
|
||||
} else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"failed to convert guest address 0x{:x} into \
|
||||
host user virtual address",
|
||||
gpa
|
||||
),
|
||||
));
|
||||
};
|
||||
|
||||
self.container
|
||||
.vfio_dma_map(iova, size, user_addr)
|
||||
.map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"failed to map memory for VFIO container, \
|
||||
iova 0x{:x}, gpa 0x{:x}, size 0x{:x}: {:?}",
|
||||
iova, gpa, size, e
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn unmap(&self, iova: u64, size: u64) -> result::Result<(), io::Error> {
|
||||
self.container.vfio_dma_unmap(iova, size).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"failed to unmap memory for VFIO container, \
|
||||
iova 0x{:x}, size 0x{:x}: {:?}",
|
||||
iova, size, e
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Vfio device for exposing regions which could be read/write to kernel vfio device.
|
||||
pub struct VfioDevice {
|
||||
device: File,
|
||||
@@ -514,6 +578,7 @@ pub struct VfioDevice {
|
||||
regions: Vec<VfioRegion>,
|
||||
irqs: HashMap<u32, VfioIrq>,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
iommu_attached: bool,
|
||||
}
|
||||
|
||||
impl VfioDevice {
|
||||
@@ -524,6 +589,7 @@ impl VfioDevice {
|
||||
sysfspath: &Path,
|
||||
device_fd: Arc<DeviceFd>,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
iommu_attached: bool,
|
||||
) -> Result<Self> {
|
||||
let uuid_path: PathBuf = [sysfspath, Path::new("iommu_group")].iter().collect();
|
||||
let group_path = uuid_path.read_link().map_err(|_| VfioError::InvalidPath)?;
|
||||
@@ -545,6 +611,7 @@ impl VfioDevice {
|
||||
regions,
|
||||
irqs,
|
||||
mem,
|
||||
iommu_attached,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -762,6 +829,10 @@ impl VfioDevice {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_container(&self) -> Arc<VfioContainer> {
|
||||
self.group.container.clone()
|
||||
}
|
||||
|
||||
fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
|
||||
self.group.container.vfio_dma_map(iova, size, user_addr)
|
||||
}
|
||||
@@ -773,22 +844,26 @@ impl VfioDevice {
|
||||
/// Add all guest memory regions into vfio container's iommu table,
|
||||
/// then vfio kernel driver could access guest memory from gfn
|
||||
pub fn setup_dma_map(&self) -> Result<()> {
|
||||
self.mem.read().unwrap().with_regions(|_index, region| {
|
||||
self.vfio_dma_map(
|
||||
region.start_addr().raw_value(),
|
||||
region.len() as u64,
|
||||
region.as_ptr() as u64,
|
||||
)
|
||||
})?;
|
||||
if !self.iommu_attached {
|
||||
self.mem.read().unwrap().with_regions(|_index, region| {
|
||||
self.vfio_dma_map(
|
||||
region.start_addr().raw_value(),
|
||||
region.len() as u64,
|
||||
region.as_ptr() as u64,
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// remove all guest memory regions from vfio containers iommu table
|
||||
/// then vfio kernel driver couldn't access this guest memory
|
||||
pub fn unset_dma_map(&self) -> Result<()> {
|
||||
self.mem.read().unwrap().with_regions(|_index, region| {
|
||||
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64)
|
||||
})?;
|
||||
if !self.iommu_attached {
|
||||
self.mem.read().unwrap().with_regions(|_index, region| {
|
||||
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ vhost-user-slave = []
|
||||
[dependencies]
|
||||
bitflags = "1.1.0"
|
||||
libc = "0.2.60"
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
[dependencies.vm-memory]
|
||||
git = "https://github.com/rust-vmm/vm-memory"
|
||||
|
||||
@@ -197,45 +197,6 @@ impl<R: Req> Endpoint<R> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a message with header, body and config info. Optional file descriptors may be attached to
|
||||
/// the message.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - number of bytes sent on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
pub fn send_config_message(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<R>,
|
||||
user_config: &VhostUserConfig,
|
||||
buf: &mut [u8],
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> Result<()> {
|
||||
// Safe because there can't be other mutable referance to hdr and body.
|
||||
let iovs = unsafe {
|
||||
[
|
||||
slice::from_raw_parts(
|
||||
hdr as *const VhostUserMsgHeader<R> as *const u8,
|
||||
mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
),
|
||||
slice::from_raw_parts(
|
||||
user_config as *const VhostUserConfig as *const u8,
|
||||
mem::size_of::<VhostUserConfig>(),
|
||||
),
|
||||
slice::from_raw_parts(buf.as_ptr() as *const u8, buf.len()),
|
||||
]
|
||||
};
|
||||
let bytes = self.send_iovec(&iovs[..], fds)?;
|
||||
let total =
|
||||
mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<VhostUserConfig>() + buf.len();
|
||||
if bytes != total {
|
||||
return Err(Error::PartialMessage);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a message with header, body and payload. Optional file descriptors
|
||||
/// may also be attached to the message.
|
||||
///
|
||||
|
||||
@@ -40,11 +40,12 @@ pub trait VhostUserMaster: VhostBackend {
|
||||
offset: u32,
|
||||
size: u32,
|
||||
flags: VhostUserConfigFlags,
|
||||
) -> Result<Vec<u8>>;
|
||||
buf: &[u8],
|
||||
) -> Result<(VhostUserConfig, VhostUserConfigPayload)>;
|
||||
|
||||
/// Change the virtio device configuration space. It also can be used for live migration on the
|
||||
/// destination host to set readonly configuration space fields.
|
||||
fn set_config(&mut self, offset: u32, buf: &[u8], flags: VhostUserConfigFlags) -> Result<()>;
|
||||
fn set_config(&mut self, offset: u32, flags: VhostUserConfigFlags, buf: &[u8]) -> Result<()>;
|
||||
|
||||
/// Setup slave communication channel.
|
||||
fn set_slave_request_fd(&mut self, fd: RawFd) -> Result<()>;
|
||||
@@ -166,9 +167,12 @@ impl VhostBackend for Master {
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
// Clippy doesn't seem to know that if let with && is still experimental
|
||||
#[allow(clippy::unnecessary_unwrap)]
|
||||
fn set_log_base(&mut self, base: u64, fd: Option<RawFd>) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let val = VhostUserU64::new(base);
|
||||
|
||||
if node.acked_protocol_features & VhostUserProtocolFeatures::LOG_SHMFD.bits() != 0
|
||||
&& fd.is_some()
|
||||
{
|
||||
@@ -344,7 +348,8 @@ impl VhostUserMaster for Master {
|
||||
offset: u32,
|
||||
size: u32,
|
||||
flags: VhostUserConfigFlags,
|
||||
) -> Result<Vec<u8>> {
|
||||
buf: &[u8],
|
||||
) -> Result<(VhostUserConfig, VhostUserConfigPayload)> {
|
||||
let body = VhostUserConfig::new(offset, size, flags);
|
||||
if !body.is_valid() {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
@@ -356,24 +361,24 @@ impl VhostUserMaster for Master {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
|
||||
// TODO: vhost-user spec states that:
|
||||
// vhost-user spec states that:
|
||||
// "Master payload: virtio device config space"
|
||||
// But what content should the payload contains for a get_config() request?
|
||||
// So current implementation doesn't conform to the spec.
|
||||
let hdr = node.send_request_with_config_body(MasterReq::GET_CONFIG, &body, None)?;
|
||||
let (reply, buf, rfds) = node.recv_reply_with_payload::<VhostUserConfig>(&hdr)?;
|
||||
// "Slave payload: virtio device config space"
|
||||
let hdr = node.send_request_with_payload(MasterReq::GET_CONFIG, &body, buf, None)?;
|
||||
let (body_reply, buf_reply, rfds) =
|
||||
node.recv_reply_with_payload::<VhostUserConfig>(&hdr)?;
|
||||
if rfds.is_some() {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return error_code(VhostUserError::InvalidMessage);
|
||||
} else if reply.size == 0 {
|
||||
} else if body_reply.size == 0 {
|
||||
return error_code(VhostUserError::SlaveInternalError);
|
||||
} else if reply.size != body.size || reply.size as usize != buf.len() {
|
||||
} else if body_reply.size != body.size || body_reply.size as usize != buf.len() {
|
||||
return error_code(VhostUserError::InvalidMessage);
|
||||
}
|
||||
Ok(buf)
|
||||
Ok((body_reply, buf_reply))
|
||||
}
|
||||
|
||||
fn set_config(&mut self, offset: u32, buf: &[u8], flags: VhostUserConfigFlags) -> Result<()> {
|
||||
fn set_config(&mut self, offset: u32, flags: VhostUserConfigFlags, buf: &[u8]) -> Result<()> {
|
||||
if buf.len() > MAX_MSG_SIZE {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
@@ -480,27 +485,6 @@ impl MasterInternal {
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
fn send_request_with_config_body(
|
||||
&mut self,
|
||||
code: MasterReq,
|
||||
user_config: &VhostUserConfig,
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> VhostUserResult<VhostUserMsgHeader<MasterReq>> {
|
||||
if mem::size_of::<VhostUserConfig>() + user_config.size as usize > MAX_MSG_SIZE {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
let hdr = Self::new_request_header(
|
||||
code,
|
||||
mem::size_of::<VhostUserConfig>() as u32 + user_config.size,
|
||||
);
|
||||
let mut buf = vec![0; user_config.size as usize];
|
||||
self.main_sock
|
||||
.send_config_message(&hdr, user_config, &mut buf, fds)?;
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
fn send_request_with_payload<T: Sized, P: Sized>(
|
||||
&mut self,
|
||||
code: MasterReq,
|
||||
@@ -571,7 +555,7 @@ impl MasterInternal {
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
let mut buf = vec![0; MAX_MSG_SIZE - mem::size_of::<T>()];
|
||||
let mut buf: Vec<u8> = vec![0; hdr.get_size() as usize - mem::size_of::<T>()];
|
||||
let (reply, body, bytes, rfds) = self.main_sock.recv_payload_into_buf::<T>(&mut buf)?;
|
||||
if !reply.is_reply_for(hdr)
|
||||
|| reply.get_size() as usize != mem::size_of::<T>() + bytes
|
||||
|
||||
@@ -346,6 +346,8 @@ bitflags! {
|
||||
const SLAVE_SEND_FD = 0x0000_0400;
|
||||
/// Allow the slave to register a host notifier.
|
||||
const HOST_NOTIFIER = 0x0000_0800;
|
||||
/// Support inflight shmfd.
|
||||
const INFLIGHT_SHMFD = 0x0000_1000;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
22
vhost_user_backend/Cargo.toml
Normal file
22
vhost_user_backend/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "vhost_user_backend"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
pci_support = ["vm-virtio/pci_support"]
|
||||
mmio_support = ["vm-virtio/mmio_support"]
|
||||
|
||||
[dependencies]
|
||||
epoll = "4.1.0"
|
||||
libc = "0.2.65"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
[dependencies.vhost_rs]
|
||||
path = "../vhost_rs"
|
||||
features = ["vhost-user-slave"]
|
||||
|
||||
759
vhost_user_backend/src/lib.rs
Normal file
759
vhost_user_backend/src/lib.rs
Normal file
@@ -0,0 +1,759 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::error;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::num::Wrapping;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread;
|
||||
use vhost_rs::vhost_user::message::{
|
||||
VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures,
|
||||
VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState,
|
||||
VHOST_USER_CONFIG_OFFSET, VHOST_USER_CONFIG_SIZE,
|
||||
};
|
||||
use vhost_rs::vhost_user::{
|
||||
Error as VhostUserError, Result as VhostUserResult, SlaveListener, VhostUserSlaveReqHandler,
|
||||
};
|
||||
use vm_memory::guest_memory::FileOffset;
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
use vm_virtio::Queue;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Errors related to vhost-user daemon.
|
||||
pub enum Error {
|
||||
/// Failed to create a new vhost-user handler.
|
||||
NewVhostUserHandler(VhostUserHandlerError),
|
||||
/// Failed creating vhost-user slave listener.
|
||||
CreateSlaveListener(VhostUserError),
|
||||
/// Failed creating vhost-user slave handler.
|
||||
CreateSlaveReqHandler(VhostUserError),
|
||||
/// Failed starting daemon thread.
|
||||
StartDaemon(io::Error),
|
||||
/// Failed waiting for daemon thread.
|
||||
WaitDaemon(std::boxed::Box<dyn std::any::Any + std::marker::Send>),
|
||||
/// Failed handling a vhost-user request.
|
||||
HandleRequest(VhostUserError),
|
||||
/// Failed to process queue.
|
||||
ProcessQueue(VringEpollHandlerError),
|
||||
/// Failed to register listener.
|
||||
RegisterListener(io::Error),
|
||||
/// Failed to unregister listener.
|
||||
UnregisterListener(io::Error),
|
||||
}
|
||||
|
||||
/// Result of vhost-user daemon operations.
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// This trait must be implemented by the caller in order to provide backend
|
||||
/// specific implementation.
|
||||
pub trait VhostUserBackend: Send + Sync + 'static {
|
||||
/// Number of queues.
|
||||
fn num_queues(&self) -> usize;
|
||||
|
||||
/// Depth of each queue.
|
||||
fn max_queue_size(&self) -> usize;
|
||||
|
||||
/// Virtio features.
|
||||
fn features(&self) -> u64;
|
||||
|
||||
/// Update guest memory regions.
|
||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> result::Result<(), io::Error>;
|
||||
|
||||
/// This function gets called if the backend registered some additional
|
||||
/// listeners onto specific file descriptors. The library can handle
|
||||
/// virtqueues on its own, but does not know what to do with events
|
||||
/// happening on custom listeners.
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
vrings: &[Arc<RwLock<Vring>>],
|
||||
) -> result::Result<bool, io::Error>;
|
||||
|
||||
/// Get virtio device configuration.
|
||||
/// A default implementation is provided as we cannot expect all backends
|
||||
/// to implement this function.
|
||||
fn get_config(&self, _offset: u32, _size: u32) -> Vec<u8> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Set virtio device configuration.
|
||||
/// A default implementation is provided as we cannot expect all backends
|
||||
/// to implement this function.
|
||||
fn set_config(&mut self, _offset: u32, _buf: &[u8]) -> result::Result<(), io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This structure is the public API the backend is allowed to interact with
|
||||
/// in order to run a fully functional vhost-user daemon.
|
||||
pub struct VhostUserDaemon<S: VhostUserBackend> {
|
||||
name: String,
|
||||
sock_path: String,
|
||||
handler: Arc<Mutex<VhostUserHandler<S>>>,
|
||||
main_thread: Option<thread::JoinHandle<Result<()>>>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VhostUserDaemon<S> {
|
||||
/// Create the daemon instance, providing the backend implementation of
|
||||
/// VhostUserBackend.
|
||||
/// Under the hood, this will start a dedicated thread responsible for
|
||||
/// listening onto registered event. Those events can be vring events or
|
||||
/// custom events from the backend, but they get to be registered later
|
||||
/// during the sequence.
|
||||
pub fn new(name: String, sock_path: String, backend: Arc<RwLock<S>>) -> Result<Self> {
|
||||
let handler = Arc::new(Mutex::new(
|
||||
VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?,
|
||||
));
|
||||
|
||||
Ok(VhostUserDaemon {
|
||||
name,
|
||||
sock_path,
|
||||
handler,
|
||||
main_thread: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Connect to the vhost-user socket and run a dedicated thread handling
|
||||
/// all requests coming through this socket. This runs in an infinite loop
|
||||
/// that should be terminating once the other end of the socket (the VMM)
|
||||
/// disconnects.
|
||||
pub fn start(&mut self) -> Result<()> {
|
||||
let mut slave_listener =
|
||||
SlaveListener::new(self.sock_path.as_str(), true, self.handler.clone())
|
||||
.map_err(Error::CreateSlaveListener)?;
|
||||
let mut slave_handler = slave_listener
|
||||
.accept()
|
||||
.map_err(Error::CreateSlaveReqHandler)?
|
||||
.unwrap();
|
||||
let handle = thread::Builder::new()
|
||||
.name(self.name.clone())
|
||||
.spawn(move || loop {
|
||||
slave_handler
|
||||
.handle_request()
|
||||
.map_err(Error::HandleRequest)?;
|
||||
})
|
||||
.map_err(Error::StartDaemon)?;
|
||||
|
||||
self.main_thread = Some(handle);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wait for the thread handling the vhost-user socket connection to
|
||||
/// terminate.
|
||||
pub fn wait(&mut self) -> Result<()> {
|
||||
if let Some(handle) = self.main_thread.take() {
|
||||
let _ = handle.join().map_err(Error::WaitDaemon)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve the vring worker. This is necessary to perform further
|
||||
/// actions like registering and unregistering some extra event file
|
||||
/// descriptors.
|
||||
pub fn get_vring_worker(&self) -> Arc<VringWorker> {
|
||||
self.handler.lock().unwrap().get_vring_worker()
|
||||
}
|
||||
}
|
||||
|
||||
struct AddrMapping {
|
||||
vmm_addr: u64,
|
||||
size: u64,
|
||||
offset: u64,
|
||||
}
|
||||
|
||||
struct Memory {
|
||||
mappings: Vec<AddrMapping>,
|
||||
}
|
||||
|
||||
pub struct Vring {
|
||||
queue: Queue,
|
||||
kick: Option<EventFd>,
|
||||
call: Option<EventFd>,
|
||||
err: Option<EventFd>,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl Vring {
|
||||
fn new(max_queue_size: u16) -> Self {
|
||||
Vring {
|
||||
queue: Queue::new(max_queue_size),
|
||||
kick: None,
|
||||
call: None,
|
||||
err: None,
|
||||
enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mut_queue(&mut self) -> &mut Queue {
|
||||
&mut self.queue
|
||||
}
|
||||
|
||||
pub fn signal_used_queue(&self) -> result::Result<(), io::Error> {
|
||||
if let Some(call) = self.call.as_ref() {
|
||||
return call.write(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Errors related to vring epoll handler.
|
||||
pub enum VringEpollHandlerError {
|
||||
/// Failed to process the queue from the backend.
|
||||
ProcessQueueBackendProcessing(io::Error),
|
||||
/// Failed to signal used queue.
|
||||
SignalUsedQueue(io::Error),
|
||||
/// Failed to read the event from kick EventFd.
|
||||
HandleEventReadKick(io::Error),
|
||||
/// Failed to handle the event from the backend.
|
||||
HandleEventBackendHandling(io::Error),
|
||||
}
|
||||
|
||||
/// Result of vring epoll handler operations.
|
||||
type VringEpollHandlerResult<T> = std::result::Result<T, VringEpollHandlerError>;
|
||||
|
||||
struct VringEpollHandler<S: VhostUserBackend> {
|
||||
backend: Arc<RwLock<S>>,
|
||||
vrings: Vec<Arc<RwLock<Vring>>>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VringEpollHandler<S> {
|
||||
fn handle_event(
|
||||
&self,
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
) -> VringEpollHandlerResult<bool> {
|
||||
let num_queues = self.vrings.len();
|
||||
if (device_event as usize) < num_queues {
|
||||
if let Some(kick) = &self.vrings[device_event as usize].read().unwrap().kick {
|
||||
kick.read()
|
||||
.map_err(VringEpollHandlerError::HandleEventReadKick)?;
|
||||
}
|
||||
|
||||
// If the vring is not enabled, it should not be processed.
|
||||
// The event is only read to be discarded.
|
||||
if !self.vrings[device_event as usize].read().unwrap().enabled {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
self.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.handle_event(device_event, evset, &self.vrings)
|
||||
.map_err(VringEpollHandlerError::HandleEventBackendHandling)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Errors related to vring worker.
|
||||
enum VringWorkerError {
|
||||
/// Failed while waiting for events.
|
||||
EpollWait(io::Error),
|
||||
}
|
||||
|
||||
/// Result of vring worker operations.
|
||||
type VringWorkerResult<T> = std::result::Result<T, VringWorkerError>;
|
||||
|
||||
pub struct VringWorker {
|
||||
epoll_fd: RawFd,
|
||||
}
|
||||
|
||||
impl VringWorker {
|
||||
fn run<S: VhostUserBackend>(&self, handler: VringEpollHandler<S>) -> VringWorkerResult<()> {
|
||||
const EPOLL_EVENTS_LEN: usize = 100;
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events = match epoll::wait(self.epoll_fd, -1, &mut events[..]) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
// It's well defined from the epoll_wait() syscall
|
||||
// documentation that the epoll loop can be interrupted
|
||||
// before any of the requested events occurred or the
|
||||
// timeout expired. In both those cases, epoll_wait()
|
||||
// returns an error of type EINTR, but this should not
|
||||
// be considered as a regular error. Instead it is more
|
||||
// appropriate to retry, by calling into epoll_wait().
|
||||
continue;
|
||||
}
|
||||
return Err(VringWorkerError::EpollWait(e));
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let evset = match epoll::Events::from_bits(event.events) {
|
||||
Some(evset) => evset,
|
||||
None => {
|
||||
let evbits = event.events;
|
||||
println!("epoll: ignoring unknown event set: 0x{:x}", evbits);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let ev_type = event.data as u16;
|
||||
|
||||
if let Err(e) = handler.handle_event(ev_type, evset) {
|
||||
println!(
|
||||
"vring handler handle event {} with error {:?}\n",
|
||||
ev_type, e
|
||||
);
|
||||
break 'epoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Register a custom event only meaningful to the caller. When this event
|
||||
/// is later triggered, and because only the caller knows what to do about
|
||||
/// it, the backend implementation of `handle_event` will be called.
|
||||
/// This lets entire control to the caller about what needs to be done for
|
||||
/// this special event, without forcing it to run its own dedicated epoll
|
||||
/// loop for it.
|
||||
pub fn register_listener(
|
||||
&self,
|
||||
fd: RawFd,
|
||||
ev_type: epoll::Events,
|
||||
data: u64,
|
||||
) -> result::Result<(), io::Error> {
|
||||
epoll::ctl(
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
fd,
|
||||
epoll::Event::new(ev_type, data),
|
||||
)
|
||||
}
|
||||
|
||||
/// Unregister a custom event. If the custom event is triggered after this
|
||||
/// function has been called, nothing will happen as it will be removed
|
||||
/// from the list of file descriptors the epoll loop is listening to.
|
||||
pub fn unregister_listener(
|
||||
&self,
|
||||
fd: RawFd,
|
||||
ev_type: epoll::Events,
|
||||
data: u64,
|
||||
) -> result::Result<(), io::Error> {
|
||||
epoll::ctl(
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_DEL,
|
||||
fd,
|
||||
epoll::Event::new(ev_type, data),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Errors related to vhost-user handler.
|
||||
pub enum VhostUserHandlerError {
|
||||
/// Failed to create epoll file descriptor.
|
||||
EpollCreateFd(io::Error),
|
||||
/// Failed to spawn vring worker.
|
||||
SpawnVringWorker(io::Error),
|
||||
/// Could not find the mapping from memory regions.
|
||||
MissingMemoryMapping,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VhostUserHandlerError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
VhostUserHandlerError::EpollCreateFd(e) => write!(f, "failed creating epoll fd: {}", e),
|
||||
VhostUserHandlerError::SpawnVringWorker(e) => {
|
||||
write!(f, "failed spawning the vring worker: {}", e)
|
||||
}
|
||||
VhostUserHandlerError::MissingMemoryMapping => write!(f, "Missing memory mapping"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for VhostUserHandlerError {}
|
||||
|
||||
/// Result of vhost-user handler operations.
|
||||
type VhostUserHandlerResult<T> = std::result::Result<T, VhostUserHandlerError>;
|
||||
|
||||
struct VhostUserHandler<S: VhostUserBackend> {
|
||||
backend: Arc<RwLock<S>>,
|
||||
worker: Arc<VringWorker>,
|
||||
owned: bool,
|
||||
features_acked: bool,
|
||||
acked_features: u64,
|
||||
acked_protocol_features: u64,
|
||||
num_queues: usize,
|
||||
max_queue_size: usize,
|
||||
memory: Option<Memory>,
|
||||
vrings: Vec<Arc<RwLock<Vring>>>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VhostUserHandler<S> {
|
||||
fn new(backend: Arc<RwLock<S>>) -> VhostUserHandlerResult<Self> {
|
||||
let num_queues = backend.read().unwrap().num_queues();
|
||||
let max_queue_size = backend.read().unwrap().max_queue_size();
|
||||
|
||||
let mut vrings: Vec<Arc<RwLock<Vring>>> = Vec::new();
|
||||
for _ in 0..num_queues {
|
||||
let vring = Arc::new(RwLock::new(Vring::new(max_queue_size as u16)));
|
||||
vrings.push(vring);
|
||||
}
|
||||
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?;
|
||||
|
||||
let vring_handler = VringEpollHandler {
|
||||
backend: backend.clone(),
|
||||
vrings: vrings.clone(),
|
||||
};
|
||||
let vring_worker = Arc::new(VringWorker { epoll_fd });
|
||||
let worker = vring_worker.clone();
|
||||
|
||||
thread::Builder::new()
|
||||
.name("vring_worker".to_string())
|
||||
.spawn(move || vring_worker.run(vring_handler))
|
||||
.map_err(VhostUserHandlerError::SpawnVringWorker)?;
|
||||
|
||||
Ok(VhostUserHandler {
|
||||
backend,
|
||||
worker,
|
||||
owned: false,
|
||||
features_acked: false,
|
||||
acked_features: 0,
|
||||
acked_protocol_features: 0,
|
||||
num_queues,
|
||||
max_queue_size,
|
||||
memory: None,
|
||||
vrings,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_vring_worker(&self) -> Arc<VringWorker> {
|
||||
self.worker.clone()
|
||||
}
|
||||
|
||||
fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult<u64> {
|
||||
if let Some(memory) = &self.memory {
|
||||
for mapping in memory.mappings.iter() {
|
||||
if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size {
|
||||
return Ok(vmm_va - mapping.vmm_addr + mapping.offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(VhostUserHandlerError::MissingMemoryMapping)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
||||
fn set_owner(&mut self) -> VhostUserResult<()> {
|
||||
if self.owned {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
}
|
||||
self.owned = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_owner(&mut self) -> VhostUserResult<()> {
|
||||
self.owned = false;
|
||||
self.features_acked = false;
|
||||
self.acked_features = 0;
|
||||
self.acked_protocol_features = 0;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_features(&mut self) -> VhostUserResult<u64> {
|
||||
Ok(self.backend.read().unwrap().features())
|
||||
}
|
||||
|
||||
fn set_features(&mut self, features: u64) -> VhostUserResult<()> {
|
||||
if !self.owned {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
} else if (features & !self.backend.read().unwrap().features()) != 0 {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
self.acked_features = features;
|
||||
self.features_acked = true;
|
||||
|
||||
// If VHOST_USER_F_PROTOCOL_FEATURES has not been negotiated,
|
||||
// the ring is initialized in an enabled state.
|
||||
// If VHOST_USER_F_PROTOCOL_FEATURES has been negotiated,
|
||||
// the ring is initialized in a disabled state. Client must not
|
||||
// pass data to/from the backend until ring is enabled by
|
||||
// VHOST_USER_SET_VRING_ENABLE with parameter 1, or after it has
|
||||
// been disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0.
|
||||
let vring_enabled =
|
||||
self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0;
|
||||
for vring in self.vrings.iter_mut() {
|
||||
vring.write().unwrap().enabled = vring_enabled;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_protocol_features(&mut self) -> VhostUserResult<VhostUserProtocolFeatures> {
|
||||
Ok(VhostUserProtocolFeatures::all())
|
||||
}
|
||||
|
||||
fn set_protocol_features(&mut self, features: u64) -> VhostUserResult<()> {
|
||||
// Note: slave that reported VHOST_USER_F_PROTOCOL_FEATURES must
|
||||
// support this message even before VHOST_USER_SET_FEATURES was
|
||||
// called.
|
||||
self.acked_protocol_features = features;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_mem_table(
|
||||
&mut self,
|
||||
ctx: &[VhostUserMemoryRegion],
|
||||
fds: &[RawFd],
|
||||
) -> VhostUserResult<()> {
|
||||
// We need to create tuple of ranges from the list of VhostUserMemoryRegion
|
||||
// that we get from the caller.
|
||||
let mut regions: Vec<(GuestAddress, usize, Option<FileOffset>)> = Vec::new();
|
||||
let mut mappings: Vec<AddrMapping> = Vec::new();
|
||||
|
||||
for (idx, region) in ctx.iter().enumerate() {
|
||||
let g_addr = GuestAddress(region.guest_phys_addr);
|
||||
let len = (region.memory_size + region.mmap_offset) as usize;
|
||||
let file = unsafe { File::from_raw_fd(fds[idx]) };
|
||||
let f_off = FileOffset::new(file, 0);
|
||||
|
||||
regions.push((g_addr, len, Some(f_off)));
|
||||
mappings.push(AddrMapping {
|
||||
vmm_addr: region.user_addr,
|
||||
size: region.memory_size,
|
||||
offset: region.mmap_offset,
|
||||
});
|
||||
}
|
||||
|
||||
let mem = GuestMemoryMmap::with_files(regions).map_err(|e| {
|
||||
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||
})?;
|
||||
self.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.update_memory(mem)
|
||||
.map_err(|e| {
|
||||
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||
})?;
|
||||
self.memory = Some(Memory { mappings });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_queue_num(&mut self) -> VhostUserResult<u64> {
|
||||
Ok(self.num_queues as u64)
|
||||
}
|
||||
|
||||
fn set_vring_num(&mut self, index: u32, num: u32) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues || num == 0 || num as usize > self.max_queue_size {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
self.vrings[index as usize].write().unwrap().queue.size = num as u16;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_addr(
|
||||
&mut self,
|
||||
index: u32,
|
||||
_flags: VhostUserVringAddrFlags,
|
||||
descriptor: u64,
|
||||
used: u64,
|
||||
available: u64,
|
||||
_log: u64,
|
||||
) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
if self.memory.is_some() {
|
||||
let desc_table = self.vmm_va_to_gpa(descriptor).map_err(|e| {
|
||||
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||
})?;
|
||||
let avail_ring = self.vmm_va_to_gpa(available).map_err(|e| {
|
||||
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||
})?;
|
||||
let used_ring = self.vmm_va_to_gpa(used).map_err(|e| {
|
||||
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||
})?;
|
||||
self.vrings[index as usize]
|
||||
.write()
|
||||
.unwrap()
|
||||
.queue
|
||||
.desc_table = GuestAddress(desc_table);
|
||||
self.vrings[index as usize]
|
||||
.write()
|
||||
.unwrap()
|
||||
.queue
|
||||
.avail_ring = GuestAddress(avail_ring);
|
||||
self.vrings[index as usize].write().unwrap().queue.used_ring = GuestAddress(used_ring);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(VhostUserError::InvalidParam)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_vring_base(&mut self, index: u32, base: u32) -> VhostUserResult<()> {
|
||||
self.vrings[index as usize]
|
||||
.write()
|
||||
.unwrap()
|
||||
.queue
|
||||
.next_avail = Wrapping(base as u16);
|
||||
self.vrings[index as usize].write().unwrap().queue.next_used = Wrapping(base as u16);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_vring_base(&mut self, index: u32) -> VhostUserResult<VhostUserVringState> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
// Quote from vhost-user specification:
|
||||
// Client must start ring upon receiving a kick (that is, detecting
|
||||
// that file descriptor is readable) on the descriptor specified by
|
||||
// VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
|
||||
// VHOST_USER_GET_VRING_BASE.
|
||||
self.vrings[index as usize].write().unwrap().queue.ready = false;
|
||||
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
|
||||
self.worker
|
||||
.unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index))
|
||||
.map_err(VhostUserError::ReqHandlerError)?;
|
||||
}
|
||||
|
||||
let next_avail = self.vrings[index as usize]
|
||||
.read()
|
||||
.unwrap()
|
||||
.queue
|
||||
.next_avail
|
||||
.0 as u16;
|
||||
|
||||
Ok(VhostUserVringState::new(index, u32::from(next_avail)))
|
||||
}
|
||||
|
||||
fn set_vring_kick(&mut self, index: u8, fd: Option<RawFd>) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
if let Some(kick) = self.vrings[index as usize].write().unwrap().kick.take() {
|
||||
// Close file descriptor set by previous operations.
|
||||
let _ = unsafe { libc::close(kick.as_raw_fd()) };
|
||||
}
|
||||
self.vrings[index as usize].write().unwrap().kick =
|
||||
fd.map(|x| unsafe { EventFd::from_raw_fd(x) });
|
||||
|
||||
// Quote from vhost-user specification:
|
||||
// Client must start ring upon receiving a kick (that is, detecting
|
||||
// that file descriptor is readable) on the descriptor specified by
|
||||
// VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
|
||||
// VHOST_USER_GET_VRING_BASE.
|
||||
self.vrings[index as usize].write().unwrap().queue.ready = true;
|
||||
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
|
||||
self.worker
|
||||
.register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index))
|
||||
.map_err(VhostUserError::ReqHandlerError)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_call(&mut self, index: u8, fd: Option<RawFd>) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
if let Some(call) = self.vrings[index as usize].write().unwrap().call.take() {
|
||||
// Close file descriptor set by previous operations.
|
||||
let _ = unsafe { libc::close(call.as_raw_fd()) };
|
||||
}
|
||||
self.vrings[index as usize].write().unwrap().call =
|
||||
fd.map(|x| unsafe { EventFd::from_raw_fd(x) });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_err(&mut self, index: u8, fd: Option<RawFd>) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
if let Some(err) = self.vrings[index as usize].write().unwrap().err.take() {
|
||||
// Close file descriptor set by previous operations.
|
||||
let _ = unsafe { libc::close(err.as_raw_fd()) };
|
||||
}
|
||||
self.vrings[index as usize].write().unwrap().err =
|
||||
fd.map(|x| unsafe { EventFd::from_raw_fd(x) });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_enable(&mut self, index: u32, enable: bool) -> VhostUserResult<()> {
|
||||
// This request should be handled only when VHOST_USER_F_PROTOCOL_FEATURES
|
||||
// has been negotiated.
|
||||
if self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0 {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
} else if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
// Slave must not pass data to/from the backend until ring is
|
||||
// enabled by VHOST_USER_SET_VRING_ENABLE with parameter 1,
|
||||
// or after it has been disabled by VHOST_USER_SET_VRING_ENABLE
|
||||
// with parameter 0.
|
||||
self.vrings[index as usize].write().unwrap().enabled = enable;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_config(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
_flags: VhostUserConfigFlags,
|
||||
) -> VhostUserResult<Vec<u8>> {
|
||||
if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
} else if offset < VHOST_USER_CONFIG_OFFSET
|
||||
|| offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
|
||||
|| size + offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
Ok(self.backend.read().unwrap().get_config(offset, size))
|
||||
}
|
||||
|
||||
fn set_config(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
buf: &[u8],
|
||||
_flags: VhostUserConfigFlags,
|
||||
) -> VhostUserResult<()> {
|
||||
let size = buf.len() as u32;
|
||||
if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
} else if offset < VHOST_USER_CONFIG_OFFSET
|
||||
|| offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
|
||||
|| size + offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
self.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_config(offset, buf)
|
||||
.map_err(VhostUserError::ReqHandlerError)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
[package]
|
||||
name = "virtio-bindings"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
@@ -1,31 +0,0 @@
|
||||
From 67247b7abc56a11d8cae7eb354994d818fb72e93 Mon Sep 17 00:00:00 2001
|
||||
From: Andreea Florescu <fandree@amazon.com>
|
||||
Date: Tue, 15 Jan 2019 18:30:07 +0200
|
||||
Subject: [PATCH] virtio_gen: remove derive Debug from packed struct
|
||||
|
||||
Bindgen automatically adds derive debug on virtio_net_ctrl_mac, a packed
|
||||
structure. This generates a warning while building.
|
||||
|
||||
Manually remove the Debug derive.
|
||||
|
||||
Signed-off-by: Andreea Florescu <fandree@amazon.com>
|
||||
---
|
||||
virtio_gen/src/virtio_net.rs | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/virtio_gen/src/virtio_net.rs b/virtio_gen/src/virtio_net.rs
|
||||
index 0b68d09..a1c9dca 100644
|
||||
--- a/virtio_gen/src/virtio_net.rs
|
||||
+++ b/virtio_gen/src/virtio_net.rs
|
||||
@@ -681,7 +681,7 @@ fn bindgen_test_layout_virtio_net_ctrl_hdr() {
|
||||
}
|
||||
pub type virtio_net_ctrl_ack = __u8;
|
||||
#[repr(C, packed)]
|
||||
-#[derive(Debug, Default)]
|
||||
+#[derive(Default)]
|
||||
pub struct virtio_net_ctrl_mac {
|
||||
pub entries: __virtio32,
|
||||
pub macs: __IncompleteArrayField<[__u8; 6usize]>,
|
||||
--
|
||||
2.7.4
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
pub mod virtio_blk;
|
||||
pub mod virtio_net;
|
||||
pub mod virtio_ring;
|
||||
@@ -1,486 +0,0 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* automatically generated by rust-bindgen */
|
||||
|
||||
pub const __BITS_PER_LONG: u32 = 64;
|
||||
pub const __FD_SETSIZE: u32 = 1024;
|
||||
pub const VIRTIO_ID_NET: u32 = 1;
|
||||
pub const VIRTIO_ID_BLOCK: u32 = 2;
|
||||
pub const VIRTIO_ID_CONSOLE: u32 = 3;
|
||||
pub const VIRTIO_ID_RNG: u32 = 4;
|
||||
pub const VIRTIO_ID_BALLOON: u32 = 5;
|
||||
pub const VIRTIO_ID_RPMSG: u32 = 7;
|
||||
pub const VIRTIO_ID_SCSI: u32 = 8;
|
||||
pub const VIRTIO_ID_9P: u32 = 9;
|
||||
pub const VIRTIO_ID_RPROC_SERIAL: u32 = 11;
|
||||
pub const VIRTIO_ID_CAIF: u32 = 12;
|
||||
pub const VIRTIO_ID_GPU: u32 = 16;
|
||||
pub const VIRTIO_ID_INPUT: u32 = 18;
|
||||
pub const VIRTIO_CONFIG_S_ACKNOWLEDGE: u32 = 1;
|
||||
pub const VIRTIO_CONFIG_S_DRIVER: u32 = 2;
|
||||
pub const VIRTIO_CONFIG_S_DRIVER_OK: u32 = 4;
|
||||
pub const VIRTIO_CONFIG_S_FEATURES_OK: u32 = 8;
|
||||
pub const VIRTIO_CONFIG_S_FAILED: u32 = 128;
|
||||
pub const VIRTIO_TRANSPORT_F_START: u32 = 28;
|
||||
pub const VIRTIO_TRANSPORT_F_END: u32 = 33;
|
||||
pub const VIRTIO_F_NOTIFY_ON_EMPTY: u32 = 24;
|
||||
pub const VIRTIO_F_ANY_LAYOUT: u32 = 27;
|
||||
pub const VIRTIO_F_VERSION_1: u32 = 32;
|
||||
pub const VIRTIO_BLK_F_SIZE_MAX: u32 = 1;
|
||||
pub const VIRTIO_BLK_F_SEG_MAX: u32 = 2;
|
||||
pub const VIRTIO_BLK_F_GEOMETRY: u32 = 4;
|
||||
pub const VIRTIO_BLK_F_RO: u32 = 5;
|
||||
pub const VIRTIO_BLK_F_BLK_SIZE: u32 = 6;
|
||||
pub const VIRTIO_BLK_F_TOPOLOGY: u32 = 10;
|
||||
pub const VIRTIO_BLK_F_MQ: u32 = 12;
|
||||
pub const VIRTIO_BLK_F_BARRIER: u32 = 0;
|
||||
pub const VIRTIO_BLK_F_SCSI: u32 = 7;
|
||||
pub const VIRTIO_BLK_F_FLUSH: u32 = 9;
|
||||
pub const VIRTIO_BLK_F_CONFIG_WCE: u32 = 11;
|
||||
pub const VIRTIO_BLK_F_WCE: u32 = 9;
|
||||
pub const VIRTIO_BLK_ID_BYTES: u32 = 20;
|
||||
pub const VIRTIO_BLK_T_IN: u32 = 0;
|
||||
pub const VIRTIO_BLK_T_OUT: u32 = 1;
|
||||
pub const VIRTIO_BLK_T_SCSI_CMD: u32 = 2;
|
||||
pub const VIRTIO_BLK_T_FLUSH: u32 = 4;
|
||||
pub const VIRTIO_BLK_T_GET_ID: u32 = 8;
|
||||
pub const VIRTIO_BLK_T_BARRIER: u32 = 2147483648;
|
||||
pub const VIRTIO_BLK_S_OK: u32 = 0;
|
||||
pub const VIRTIO_BLK_S_IOERR: u32 = 1;
|
||||
pub const VIRTIO_BLK_S_UNSUPP: u32 = 2;
|
||||
pub type __s8 = ::std::os::raw::c_schar;
|
||||
pub type __u8 = ::std::os::raw::c_uchar;
|
||||
pub type __s16 = ::std::os::raw::c_short;
|
||||
pub type __u16 = ::std::os::raw::c_ushort;
|
||||
pub type __s32 = ::std::os::raw::c_int;
|
||||
pub type __u32 = ::std::os::raw::c_uint;
|
||||
pub type __s64 = ::std::os::raw::c_longlong;
|
||||
pub type __u64 = ::std::os::raw::c_ulonglong;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct __kernel_fd_set {
|
||||
pub fds_bits: [::std::os::raw::c_ulong; 16usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout___kernel_fd_set() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<__kernel_fd_set>(),
|
||||
128usize,
|
||||
concat!("Size of: ", stringify!(__kernel_fd_set))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<__kernel_fd_set>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(__kernel_fd_set))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<__kernel_fd_set>())).fds_bits as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(__kernel_fd_set),
|
||||
"::",
|
||||
stringify!(fds_bits)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type __kernel_sighandler_t =
|
||||
::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
|
||||
pub type __kernel_key_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_mqd_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
|
||||
pub type __kernel_long_t = ::std::os::raw::c_long;
|
||||
pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
|
||||
pub type __kernel_ino_t = __kernel_ulong_t;
|
||||
pub type __kernel_mode_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_pid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_uid_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_gid_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_suseconds_t = __kernel_long_t;
|
||||
pub type __kernel_daddr_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_uid32_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_gid32_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_size_t = __kernel_ulong_t;
|
||||
pub type __kernel_ssize_t = __kernel_long_t;
|
||||
pub type __kernel_ptrdiff_t = __kernel_long_t;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct __kernel_fsid_t {
|
||||
pub val: [::std::os::raw::c_int; 2usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout___kernel_fsid_t() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<__kernel_fsid_t>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(__kernel_fsid_t))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<__kernel_fsid_t>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(__kernel_fsid_t))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<__kernel_fsid_t>())).val as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(__kernel_fsid_t),
|
||||
"::",
|
||||
stringify!(val)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type __kernel_off_t = __kernel_long_t;
|
||||
pub type __kernel_loff_t = ::std::os::raw::c_longlong;
|
||||
pub type __kernel_time_t = __kernel_long_t;
|
||||
pub type __kernel_clock_t = __kernel_long_t;
|
||||
pub type __kernel_timer_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_clockid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
|
||||
pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
|
||||
pub type __le16 = __u16;
|
||||
pub type __be16 = __u16;
|
||||
pub type __le32 = __u32;
|
||||
pub type __be32 = __u32;
|
||||
pub type __le64 = __u64;
|
||||
pub type __be64 = __u64;
|
||||
pub type __sum16 = __u16;
|
||||
pub type __wsum = __u32;
|
||||
pub type __virtio16 = __u16;
|
||||
pub type __virtio32 = __u32;
|
||||
pub type __virtio64 = __u64;
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_blk_config {
|
||||
pub capacity: __u64,
|
||||
pub size_max: __u32,
|
||||
pub seg_max: __u32,
|
||||
pub geometry: virtio_blk_config_virtio_blk_geometry,
|
||||
pub blk_size: __u32,
|
||||
pub physical_block_exp: __u8,
|
||||
pub alignment_offset: __u8,
|
||||
pub min_io_size: __u16,
|
||||
pub opt_io_size: __u32,
|
||||
pub wce: __u8,
|
||||
pub unused: __u8,
|
||||
pub num_queues: __u16,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_blk_config_virtio_blk_geometry {
|
||||
pub cylinders: __u16,
|
||||
pub heads: __u8,
|
||||
pub sectors: __u8,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_blk_config_virtio_blk_geometry() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_blk_config_virtio_blk_geometry>(),
|
||||
4usize,
|
||||
concat!(
|
||||
"Size of: ",
|
||||
stringify!(virtio_blk_config_virtio_blk_geometry)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_blk_config_virtio_blk_geometry>(),
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of ",
|
||||
stringify!(virtio_blk_config_virtio_blk_geometry)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_blk_config_virtio_blk_geometry>())).cylinders as *const _
|
||||
as usize
|
||||
},
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config_virtio_blk_geometry),
|
||||
"::",
|
||||
stringify!(cylinders)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_blk_config_virtio_blk_geometry>())).heads as *const _
|
||||
as usize
|
||||
},
|
||||
2usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config_virtio_blk_geometry),
|
||||
"::",
|
||||
stringify!(heads)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_blk_config_virtio_blk_geometry>())).sectors as *const _
|
||||
as usize
|
||||
},
|
||||
3usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config_virtio_blk_geometry),
|
||||
"::",
|
||||
stringify!(sectors)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_blk_config() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_blk_config>(),
|
||||
36usize,
|
||||
concat!("Size of: ", stringify!(virtio_blk_config))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_blk_config>(),
|
||||
1usize,
|
||||
concat!("Alignment of ", stringify!(virtio_blk_config))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).capacity as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(capacity)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).size_max as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(size_max)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).seg_max as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(seg_max)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).geometry as *const _ as usize },
|
||||
16usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(geometry)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).blk_size as *const _ as usize },
|
||||
20usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(blk_size)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_blk_config>())).physical_block_exp as *const _ as usize
|
||||
},
|
||||
24usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(physical_block_exp)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_blk_config>())).alignment_offset as *const _ as usize
|
||||
},
|
||||
25usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(alignment_offset)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).min_io_size as *const _ as usize },
|
||||
26usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(min_io_size)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).opt_io_size as *const _ as usize },
|
||||
28usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(opt_io_size)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).wce as *const _ as usize },
|
||||
32usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(wce)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).unused as *const _ as usize },
|
||||
33usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(unused)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).num_queues as *const _ as usize },
|
||||
34usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(num_queues)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_blk_outhdr {
|
||||
pub type_: __virtio32,
|
||||
pub ioprio: __virtio32,
|
||||
pub sector: __virtio64,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_blk_outhdr() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_blk_outhdr>(),
|
||||
16usize,
|
||||
concat!("Size of: ", stringify!(virtio_blk_outhdr))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_blk_outhdr>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(virtio_blk_outhdr))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_outhdr>())).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_outhdr),
|
||||
"::",
|
||||
stringify!(type_)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_outhdr>())).ioprio as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_outhdr),
|
||||
"::",
|
||||
stringify!(ioprio)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_outhdr>())).sector as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_outhdr),
|
||||
"::",
|
||||
stringify!(sector)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_scsi_inhdr {
|
||||
pub errors: __virtio32,
|
||||
pub data_len: __virtio32,
|
||||
pub sense_len: __virtio32,
|
||||
pub residual: __virtio32,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_scsi_inhdr() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_scsi_inhdr>(),
|
||||
16usize,
|
||||
concat!("Size of: ", stringify!(virtio_scsi_inhdr))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_scsi_inhdr>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(virtio_scsi_inhdr))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_scsi_inhdr>())).errors as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_scsi_inhdr),
|
||||
"::",
|
||||
stringify!(errors)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_scsi_inhdr>())).data_len as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_scsi_inhdr),
|
||||
"::",
|
||||
stringify!(data_len)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_scsi_inhdr>())).sense_len as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_scsi_inhdr),
|
||||
"::",
|
||||
stringify!(sense_len)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_scsi_inhdr>())).residual as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_scsi_inhdr),
|
||||
"::",
|
||||
stringify!(residual)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,734 +0,0 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* automatically generated by rust-bindgen */
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
|
||||
impl<T> __IncompleteArrayField<T> {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
__IncompleteArrayField(::std::marker::PhantomData, [])
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_ptr(&self) -> *const T {
|
||||
::std::mem::transmute(self)
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
|
||||
::std::mem::transmute(self)
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
|
||||
::std::slice::from_raw_parts(self.as_ptr(), len)
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
|
||||
::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
|
||||
}
|
||||
}
|
||||
impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
|
||||
fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
fmt.write_str("__IncompleteArrayField")
|
||||
}
|
||||
}
|
||||
impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
pub const __BITS_PER_LONG: u32 = 64;
|
||||
pub const __FD_SETSIZE: u32 = 1024;
|
||||
pub const VIRTIO_ID_NET: u32 = 1;
|
||||
pub const VIRTIO_ID_BLOCK: u32 = 2;
|
||||
pub const VIRTIO_ID_CONSOLE: u32 = 3;
|
||||
pub const VIRTIO_ID_RNG: u32 = 4;
|
||||
pub const VIRTIO_ID_BALLOON: u32 = 5;
|
||||
pub const VIRTIO_ID_RPMSG: u32 = 7;
|
||||
pub const VIRTIO_ID_SCSI: u32 = 8;
|
||||
pub const VIRTIO_ID_9P: u32 = 9;
|
||||
pub const VIRTIO_ID_RPROC_SERIAL: u32 = 11;
|
||||
pub const VIRTIO_ID_CAIF: u32 = 12;
|
||||
pub const VIRTIO_ID_GPU: u32 = 16;
|
||||
pub const VIRTIO_ID_INPUT: u32 = 18;
|
||||
pub const VIRTIO_CONFIG_S_ACKNOWLEDGE: u32 = 1;
|
||||
pub const VIRTIO_CONFIG_S_DRIVER: u32 = 2;
|
||||
pub const VIRTIO_CONFIG_S_DRIVER_OK: u32 = 4;
|
||||
pub const VIRTIO_CONFIG_S_FEATURES_OK: u32 = 8;
|
||||
pub const VIRTIO_CONFIG_S_FAILED: u32 = 128;
|
||||
pub const VIRTIO_TRANSPORT_F_START: u32 = 28;
|
||||
pub const VIRTIO_TRANSPORT_F_END: u32 = 33;
|
||||
pub const VIRTIO_F_NOTIFY_ON_EMPTY: u32 = 24;
|
||||
pub const VIRTIO_F_ANY_LAYOUT: u32 = 27;
|
||||
pub const VIRTIO_F_VERSION_1: u32 = 32;
|
||||
pub const ETH_ALEN: u32 = 6;
|
||||
pub const ETH_TLEN: u32 = 2;
|
||||
pub const ETH_HLEN: u32 = 14;
|
||||
pub const ETH_ZLEN: u32 = 60;
|
||||
pub const ETH_DATA_LEN: u32 = 1500;
|
||||
pub const ETH_FRAME_LEN: u32 = 1514;
|
||||
pub const ETH_FCS_LEN: u32 = 4;
|
||||
pub const ETH_P_LOOP: u32 = 96;
|
||||
pub const ETH_P_PUP: u32 = 512;
|
||||
pub const ETH_P_PUPAT: u32 = 513;
|
||||
pub const ETH_P_TSN: u32 = 8944;
|
||||
pub const ETH_P_IP: u32 = 2048;
|
||||
pub const ETH_P_X25: u32 = 2053;
|
||||
pub const ETH_P_ARP: u32 = 2054;
|
||||
pub const ETH_P_BPQ: u32 = 2303;
|
||||
pub const ETH_P_IEEEPUP: u32 = 2560;
|
||||
pub const ETH_P_IEEEPUPAT: u32 = 2561;
|
||||
pub const ETH_P_BATMAN: u32 = 17157;
|
||||
pub const ETH_P_DEC: u32 = 24576;
|
||||
pub const ETH_P_DNA_DL: u32 = 24577;
|
||||
pub const ETH_P_DNA_RC: u32 = 24578;
|
||||
pub const ETH_P_DNA_RT: u32 = 24579;
|
||||
pub const ETH_P_LAT: u32 = 24580;
|
||||
pub const ETH_P_DIAG: u32 = 24581;
|
||||
pub const ETH_P_CUST: u32 = 24582;
|
||||
pub const ETH_P_SCA: u32 = 24583;
|
||||
pub const ETH_P_TEB: u32 = 25944;
|
||||
pub const ETH_P_RARP: u32 = 32821;
|
||||
pub const ETH_P_ATALK: u32 = 32923;
|
||||
pub const ETH_P_AARP: u32 = 33011;
|
||||
pub const ETH_P_8021Q: u32 = 33024;
|
||||
pub const ETH_P_IPX: u32 = 33079;
|
||||
pub const ETH_P_IPV6: u32 = 34525;
|
||||
pub const ETH_P_PAUSE: u32 = 34824;
|
||||
pub const ETH_P_SLOW: u32 = 34825;
|
||||
pub const ETH_P_WCCP: u32 = 34878;
|
||||
pub const ETH_P_MPLS_UC: u32 = 34887;
|
||||
pub const ETH_P_MPLS_MC: u32 = 34888;
|
||||
pub const ETH_P_ATMMPOA: u32 = 34892;
|
||||
pub const ETH_P_PPP_DISC: u32 = 34915;
|
||||
pub const ETH_P_PPP_SES: u32 = 34916;
|
||||
pub const ETH_P_LINK_CTL: u32 = 34924;
|
||||
pub const ETH_P_ATMFATE: u32 = 34948;
|
||||
pub const ETH_P_PAE: u32 = 34958;
|
||||
pub const ETH_P_AOE: u32 = 34978;
|
||||
pub const ETH_P_8021AD: u32 = 34984;
|
||||
pub const ETH_P_802_EX1: u32 = 34997;
|
||||
pub const ETH_P_TIPC: u32 = 35018;
|
||||
pub const ETH_P_8021AH: u32 = 35047;
|
||||
pub const ETH_P_MVRP: u32 = 35061;
|
||||
pub const ETH_P_1588: u32 = 35063;
|
||||
pub const ETH_P_PRP: u32 = 35067;
|
||||
pub const ETH_P_FCOE: u32 = 35078;
|
||||
pub const ETH_P_TDLS: u32 = 35085;
|
||||
pub const ETH_P_FIP: u32 = 35092;
|
||||
pub const ETH_P_80221: u32 = 35095;
|
||||
pub const ETH_P_LOOPBACK: u32 = 36864;
|
||||
pub const ETH_P_QINQ1: u32 = 37120;
|
||||
pub const ETH_P_QINQ2: u32 = 37376;
|
||||
pub const ETH_P_QINQ3: u32 = 37632;
|
||||
pub const ETH_P_EDSA: u32 = 56026;
|
||||
pub const ETH_P_AF_IUCV: u32 = 64507;
|
||||
pub const ETH_P_802_3_MIN: u32 = 1536;
|
||||
pub const ETH_P_802_3: u32 = 1;
|
||||
pub const ETH_P_AX25: u32 = 2;
|
||||
pub const ETH_P_ALL: u32 = 3;
|
||||
pub const ETH_P_802_2: u32 = 4;
|
||||
pub const ETH_P_SNAP: u32 = 5;
|
||||
pub const ETH_P_DDCMP: u32 = 6;
|
||||
pub const ETH_P_WAN_PPP: u32 = 7;
|
||||
pub const ETH_P_PPP_MP: u32 = 8;
|
||||
pub const ETH_P_LOCALTALK: u32 = 9;
|
||||
pub const ETH_P_CAN: u32 = 12;
|
||||
pub const ETH_P_CANFD: u32 = 13;
|
||||
pub const ETH_P_PPPTALK: u32 = 16;
|
||||
pub const ETH_P_TR_802_2: u32 = 17;
|
||||
pub const ETH_P_MOBITEX: u32 = 21;
|
||||
pub const ETH_P_CONTROL: u32 = 22;
|
||||
pub const ETH_P_IRDA: u32 = 23;
|
||||
pub const ETH_P_ECONET: u32 = 24;
|
||||
pub const ETH_P_HDLC: u32 = 25;
|
||||
pub const ETH_P_ARCNET: u32 = 26;
|
||||
pub const ETH_P_DSA: u32 = 27;
|
||||
pub const ETH_P_TRAILER: u32 = 28;
|
||||
pub const ETH_P_PHONET: u32 = 245;
|
||||
pub const ETH_P_IEEE802154: u32 = 246;
|
||||
pub const ETH_P_CAIF: u32 = 247;
|
||||
pub const ETH_P_XDSA: u32 = 248;
|
||||
pub const VIRTIO_NET_F_CSUM: u32 = 0;
|
||||
pub const VIRTIO_NET_F_GUEST_CSUM: u32 = 1;
|
||||
pub const VIRTIO_NET_F_CTRL_GUEST_OFFLOADS: u32 = 2;
|
||||
pub const VIRTIO_NET_F_MTU: u32 = 3;
|
||||
pub const VIRTIO_NET_F_MAC: u32 = 5;
|
||||
pub const VIRTIO_NET_F_GUEST_TSO4: u32 = 7;
|
||||
pub const VIRTIO_NET_F_GUEST_TSO6: u32 = 8;
|
||||
pub const VIRTIO_NET_F_GUEST_ECN: u32 = 9;
|
||||
pub const VIRTIO_NET_F_GUEST_UFO: u32 = 10;
|
||||
pub const VIRTIO_NET_F_HOST_TSO4: u32 = 11;
|
||||
pub const VIRTIO_NET_F_HOST_TSO6: u32 = 12;
|
||||
pub const VIRTIO_NET_F_HOST_ECN: u32 = 13;
|
||||
pub const VIRTIO_NET_F_HOST_UFO: u32 = 14;
|
||||
pub const VIRTIO_NET_F_MRG_RXBUF: u32 = 15;
|
||||
pub const VIRTIO_NET_F_STATUS: u32 = 16;
|
||||
pub const VIRTIO_NET_F_CTRL_VQ: u32 = 17;
|
||||
pub const VIRTIO_NET_F_CTRL_RX: u32 = 18;
|
||||
pub const VIRTIO_NET_F_CTRL_VLAN: u32 = 19;
|
||||
pub const VIRTIO_NET_F_CTRL_RX_EXTRA: u32 = 20;
|
||||
pub const VIRTIO_NET_F_GUEST_ANNOUNCE: u32 = 21;
|
||||
pub const VIRTIO_NET_F_MQ: u32 = 22;
|
||||
pub const VIRTIO_NET_F_CTRL_MAC_ADDR: u32 = 23;
|
||||
pub const VIRTIO_NET_F_GSO: u32 = 6;
|
||||
pub const VIRTIO_NET_S_LINK_UP: u32 = 1;
|
||||
pub const VIRTIO_NET_S_ANNOUNCE: u32 = 2;
|
||||
pub const VIRTIO_NET_HDR_F_NEEDS_CSUM: u32 = 1;
|
||||
pub const VIRTIO_NET_HDR_F_DATA_VALID: u32 = 2;
|
||||
pub const VIRTIO_NET_HDR_GSO_NONE: u32 = 0;
|
||||
pub const VIRTIO_NET_HDR_GSO_TCPV4: u32 = 1;
|
||||
pub const VIRTIO_NET_HDR_GSO_UDP: u32 = 3;
|
||||
pub const VIRTIO_NET_HDR_GSO_TCPV6: u32 = 4;
|
||||
pub const VIRTIO_NET_HDR_GSO_ECN: u32 = 128;
|
||||
pub const VIRTIO_NET_OK: u32 = 0;
|
||||
pub const VIRTIO_NET_ERR: u32 = 1;
|
||||
pub const VIRTIO_NET_CTRL_RX: u32 = 0;
|
||||
pub const VIRTIO_NET_CTRL_RX_PROMISC: u32 = 0;
|
||||
pub const VIRTIO_NET_CTRL_RX_ALLMULTI: u32 = 1;
|
||||
pub const VIRTIO_NET_CTRL_RX_ALLUNI: u32 = 2;
|
||||
pub const VIRTIO_NET_CTRL_RX_NOMULTI: u32 = 3;
|
||||
pub const VIRTIO_NET_CTRL_RX_NOUNI: u32 = 4;
|
||||
pub const VIRTIO_NET_CTRL_RX_NOBCAST: u32 = 5;
|
||||
pub const VIRTIO_NET_CTRL_MAC: u32 = 1;
|
||||
pub const VIRTIO_NET_CTRL_MAC_TABLE_SET: u32 = 0;
|
||||
pub const VIRTIO_NET_CTRL_MAC_ADDR_SET: u32 = 1;
|
||||
pub const VIRTIO_NET_CTRL_VLAN: u32 = 2;
|
||||
pub const VIRTIO_NET_CTRL_VLAN_ADD: u32 = 0;
|
||||
pub const VIRTIO_NET_CTRL_VLAN_DEL: u32 = 1;
|
||||
pub const VIRTIO_NET_CTRL_ANNOUNCE: u32 = 3;
|
||||
pub const VIRTIO_NET_CTRL_ANNOUNCE_ACK: u32 = 0;
|
||||
pub const VIRTIO_NET_CTRL_MQ: u32 = 4;
|
||||
pub const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET: u32 = 0;
|
||||
pub const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN: u32 = 1;
|
||||
pub const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX: u32 = 32768;
|
||||
pub const VIRTIO_NET_CTRL_GUEST_OFFLOADS: u32 = 5;
|
||||
pub const VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET: u32 = 0;
|
||||
pub type __s8 = ::std::os::raw::c_schar;
|
||||
pub type __u8 = ::std::os::raw::c_uchar;
|
||||
pub type __s16 = ::std::os::raw::c_short;
|
||||
pub type __u16 = ::std::os::raw::c_ushort;
|
||||
pub type __s32 = ::std::os::raw::c_int;
|
||||
pub type __u32 = ::std::os::raw::c_uint;
|
||||
pub type __s64 = ::std::os::raw::c_longlong;
|
||||
pub type __u64 = ::std::os::raw::c_ulonglong;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct __kernel_fd_set {
|
||||
pub fds_bits: [::std::os::raw::c_ulong; 16usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout___kernel_fd_set() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<__kernel_fd_set>(),
|
||||
128usize,
|
||||
concat!("Size of: ", stringify!(__kernel_fd_set))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<__kernel_fd_set>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(__kernel_fd_set))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<__kernel_fd_set>())).fds_bits as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(__kernel_fd_set),
|
||||
"::",
|
||||
stringify!(fds_bits)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type __kernel_sighandler_t =
|
||||
::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
|
||||
pub type __kernel_key_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_mqd_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
|
||||
pub type __kernel_long_t = ::std::os::raw::c_long;
|
||||
pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
|
||||
pub type __kernel_ino_t = __kernel_ulong_t;
|
||||
pub type __kernel_mode_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_pid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_uid_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_gid_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_suseconds_t = __kernel_long_t;
|
||||
pub type __kernel_daddr_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_uid32_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_gid32_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_size_t = __kernel_ulong_t;
|
||||
pub type __kernel_ssize_t = __kernel_long_t;
|
||||
pub type __kernel_ptrdiff_t = __kernel_long_t;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct __kernel_fsid_t {
|
||||
pub val: [::std::os::raw::c_int; 2usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout___kernel_fsid_t() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<__kernel_fsid_t>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(__kernel_fsid_t))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<__kernel_fsid_t>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(__kernel_fsid_t))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<__kernel_fsid_t>())).val as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(__kernel_fsid_t),
|
||||
"::",
|
||||
stringify!(val)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type __kernel_off_t = __kernel_long_t;
|
||||
pub type __kernel_loff_t = ::std::os::raw::c_longlong;
|
||||
pub type __kernel_time_t = __kernel_long_t;
|
||||
pub type __kernel_clock_t = __kernel_long_t;
|
||||
pub type __kernel_timer_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_clockid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
|
||||
pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
|
||||
pub type __le16 = __u16;
|
||||
pub type __be16 = __u16;
|
||||
pub type __le32 = __u32;
|
||||
pub type __be32 = __u32;
|
||||
pub type __le64 = __u64;
|
||||
pub type __be64 = __u64;
|
||||
pub type __sum16 = __u16;
|
||||
pub type __wsum = __u32;
|
||||
pub type __virtio16 = __u16;
|
||||
pub type __virtio32 = __u32;
|
||||
pub type __virtio64 = __u64;
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct ethhdr {
|
||||
pub h_dest: [::std::os::raw::c_uchar; 6usize],
|
||||
pub h_source: [::std::os::raw::c_uchar; 6usize],
|
||||
pub h_proto: __be16,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_ethhdr() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<ethhdr>(),
|
||||
14usize,
|
||||
concat!("Size of: ", stringify!(ethhdr))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<ethhdr>(),
|
||||
1usize,
|
||||
concat!("Alignment of ", stringify!(ethhdr))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<ethhdr>())).h_dest as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(ethhdr),
|
||||
"::",
|
||||
stringify!(h_dest)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<ethhdr>())).h_source as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(ethhdr),
|
||||
"::",
|
||||
stringify!(h_source)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<ethhdr>())).h_proto as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(ethhdr),
|
||||
"::",
|
||||
stringify!(h_proto)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_net_config {
|
||||
pub mac: [__u8; 6usize],
|
||||
pub status: __u16,
|
||||
pub max_virtqueue_pairs: __u16,
|
||||
pub mtu: __u16,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_net_config() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_net_config>(),
|
||||
12usize,
|
||||
concat!("Size of: ", stringify!(virtio_net_config))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_net_config>(),
|
||||
1usize,
|
||||
concat!("Alignment of ", stringify!(virtio_net_config))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_config>())).mac as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_config),
|
||||
"::",
|
||||
stringify!(mac)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_config>())).status as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_config),
|
||||
"::",
|
||||
stringify!(status)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_net_config>())).max_virtqueue_pairs as *const _ as usize
|
||||
},
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_config),
|
||||
"::",
|
||||
stringify!(max_virtqueue_pairs)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_config>())).mtu as *const _ as usize },
|
||||
10usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_config),
|
||||
"::",
|
||||
stringify!(mtu)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_net_hdr_v1 {
|
||||
pub flags: __u8,
|
||||
pub gso_type: __u8,
|
||||
pub hdr_len: __virtio16,
|
||||
pub gso_size: __virtio16,
|
||||
pub csum_start: __virtio16,
|
||||
pub csum_offset: __virtio16,
|
||||
pub num_buffers: __virtio16,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_net_hdr_v1() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_net_hdr_v1>(),
|
||||
12usize,
|
||||
concat!("Size of: ", stringify!(virtio_net_hdr_v1))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_net_hdr_v1>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(virtio_net_hdr_v1))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).flags as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_v1),
|
||||
"::",
|
||||
stringify!(flags)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).gso_type as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_v1),
|
||||
"::",
|
||||
stringify!(gso_type)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).hdr_len as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_v1),
|
||||
"::",
|
||||
stringify!(hdr_len)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).gso_size as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_v1),
|
||||
"::",
|
||||
stringify!(gso_size)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).csum_start as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_v1),
|
||||
"::",
|
||||
stringify!(csum_start)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).csum_offset as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_v1),
|
||||
"::",
|
||||
stringify!(csum_offset)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).num_buffers as *const _ as usize },
|
||||
10usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_v1),
|
||||
"::",
|
||||
stringify!(num_buffers)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_net_hdr {
|
||||
pub flags: __u8,
|
||||
pub gso_type: __u8,
|
||||
pub hdr_len: __virtio16,
|
||||
pub gso_size: __virtio16,
|
||||
pub csum_start: __virtio16,
|
||||
pub csum_offset: __virtio16,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_net_hdr() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_net_hdr>(),
|
||||
10usize,
|
||||
concat!("Size of: ", stringify!(virtio_net_hdr))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_net_hdr>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(virtio_net_hdr))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).flags as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr),
|
||||
"::",
|
||||
stringify!(flags)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).gso_type as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr),
|
||||
"::",
|
||||
stringify!(gso_type)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).hdr_len as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr),
|
||||
"::",
|
||||
stringify!(hdr_len)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).gso_size as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr),
|
||||
"::",
|
||||
stringify!(gso_size)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).csum_start as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr),
|
||||
"::",
|
||||
stringify!(csum_start)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).csum_offset as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr),
|
||||
"::",
|
||||
stringify!(csum_offset)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_net_hdr_mrg_rxbuf {
|
||||
pub hdr: virtio_net_hdr,
|
||||
pub num_buffers: __virtio16,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_net_hdr_mrg_rxbuf() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_net_hdr_mrg_rxbuf>(),
|
||||
12usize,
|
||||
concat!("Size of: ", stringify!(virtio_net_hdr_mrg_rxbuf))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_net_hdr_mrg_rxbuf>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(virtio_net_hdr_mrg_rxbuf))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_mrg_rxbuf>())).hdr as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_mrg_rxbuf),
|
||||
"::",
|
||||
stringify!(hdr)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_net_hdr_mrg_rxbuf>())).num_buffers as *const _ as usize
|
||||
},
|
||||
10usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_mrg_rxbuf),
|
||||
"::",
|
||||
stringify!(num_buffers)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_net_ctrl_hdr {
|
||||
pub class: __u8,
|
||||
pub cmd: __u8,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_net_ctrl_hdr() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_net_ctrl_hdr>(),
|
||||
2usize,
|
||||
concat!("Size of: ", stringify!(virtio_net_ctrl_hdr))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_net_ctrl_hdr>(),
|
||||
1usize,
|
||||
concat!("Alignment of ", stringify!(virtio_net_ctrl_hdr))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_ctrl_hdr>())).class as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_ctrl_hdr),
|
||||
"::",
|
||||
stringify!(class)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_ctrl_hdr>())).cmd as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_ctrl_hdr),
|
||||
"::",
|
||||
stringify!(cmd)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type virtio_net_ctrl_ack = __u8;
|
||||
#[repr(C, packed)]
|
||||
#[derive(Default)]
|
||||
pub struct virtio_net_ctrl_mac {
|
||||
pub entries: __virtio32,
|
||||
pub macs: __IncompleteArrayField<[__u8; 6usize]>,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_net_ctrl_mac() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_net_ctrl_mac>(),
|
||||
4usize,
|
||||
concat!("Size of: ", stringify!(virtio_net_ctrl_mac))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_net_ctrl_mac>(),
|
||||
1usize,
|
||||
concat!("Alignment of ", stringify!(virtio_net_ctrl_mac))
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_net_ctrl_mq {
|
||||
pub virtqueue_pairs: __virtio16,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_net_ctrl_mq() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_net_ctrl_mq>(),
|
||||
2usize,
|
||||
concat!("Size of: ", stringify!(virtio_net_ctrl_mq))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_net_ctrl_mq>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(virtio_net_ctrl_mq))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_net_ctrl_mq>())).virtqueue_pairs as *const _ as usize
|
||||
},
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_ctrl_mq),
|
||||
"::",
|
||||
stringify!(virtqueue_pairs)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,453 +0,0 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* automatically generated by rust-bindgen */
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
|
||||
impl<T> __IncompleteArrayField<T> {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
__IncompleteArrayField(::std::marker::PhantomData, [])
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_ptr(&self) -> *const T {
|
||||
::std::mem::transmute(self)
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
|
||||
::std::mem::transmute(self)
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
|
||||
::std::slice::from_raw_parts(self.as_ptr(), len)
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
|
||||
::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
|
||||
}
|
||||
}
|
||||
impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
|
||||
fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
fmt.write_str("__IncompleteArrayField")
|
||||
}
|
||||
}
|
||||
impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
pub const _STDINT_H: u32 = 1;
|
||||
pub const _FEATURES_H: u32 = 1;
|
||||
pub const _DEFAULT_SOURCE: u32 = 1;
|
||||
pub const __USE_ISOC11: u32 = 1;
|
||||
pub const __USE_ISOC99: u32 = 1;
|
||||
pub const __USE_ISOC95: u32 = 1;
|
||||
pub const __USE_POSIX_IMPLICITLY: u32 = 1;
|
||||
pub const _POSIX_SOURCE: u32 = 1;
|
||||
pub const _POSIX_C_SOURCE: u32 = 200809;
|
||||
pub const __USE_POSIX: u32 = 1;
|
||||
pub const __USE_POSIX2: u32 = 1;
|
||||
pub const __USE_POSIX199309: u32 = 1;
|
||||
pub const __USE_POSIX199506: u32 = 1;
|
||||
pub const __USE_XOPEN2K: u32 = 1;
|
||||
pub const __USE_XOPEN2K8: u32 = 1;
|
||||
pub const _ATFILE_SOURCE: u32 = 1;
|
||||
pub const __USE_MISC: u32 = 1;
|
||||
pub const __USE_ATFILE: u32 = 1;
|
||||
pub const __USE_FORTIFY_LEVEL: u32 = 0;
|
||||
pub const _STDC_PREDEF_H: u32 = 1;
|
||||
pub const __STDC_IEC_559__: u32 = 1;
|
||||
pub const __STDC_IEC_559_COMPLEX__: u32 = 1;
|
||||
pub const __STDC_ISO_10646__: u32 = 201505;
|
||||
pub const __STDC_NO_THREADS__: u32 = 1;
|
||||
pub const __GNU_LIBRARY__: u32 = 6;
|
||||
pub const __GLIBC__: u32 = 2;
|
||||
pub const __GLIBC_MINOR__: u32 = 23;
|
||||
pub const _SYS_CDEFS_H: u32 = 1;
|
||||
pub const __WORDSIZE: u32 = 64;
|
||||
pub const __WORDSIZE_TIME64_COMPAT32: u32 = 1;
|
||||
pub const __SYSCALL_WORDSIZE: u32 = 64;
|
||||
pub const _BITS_WCHAR_H: u32 = 1;
|
||||
pub const INT8_MIN: i32 = -128;
|
||||
pub const INT16_MIN: i32 = -32768;
|
||||
pub const INT32_MIN: i32 = -2147483648;
|
||||
pub const INT8_MAX: u32 = 127;
|
||||
pub const INT16_MAX: u32 = 32767;
|
||||
pub const INT32_MAX: u32 = 2147483647;
|
||||
pub const UINT8_MAX: u32 = 255;
|
||||
pub const UINT16_MAX: u32 = 65535;
|
||||
pub const UINT32_MAX: u32 = 4294967295;
|
||||
pub const INT_LEAST8_MIN: i32 = -128;
|
||||
pub const INT_LEAST16_MIN: i32 = -32768;
|
||||
pub const INT_LEAST32_MIN: i32 = -2147483648;
|
||||
pub const INT_LEAST8_MAX: u32 = 127;
|
||||
pub const INT_LEAST16_MAX: u32 = 32767;
|
||||
pub const INT_LEAST32_MAX: u32 = 2147483647;
|
||||
pub const UINT_LEAST8_MAX: u32 = 255;
|
||||
pub const UINT_LEAST16_MAX: u32 = 65535;
|
||||
pub const UINT_LEAST32_MAX: u32 = 4294967295;
|
||||
pub const INT_FAST8_MIN: i32 = -128;
|
||||
pub const INT_FAST16_MIN: i64 = -9223372036854775808;
|
||||
pub const INT_FAST32_MIN: i64 = -9223372036854775808;
|
||||
pub const INT_FAST8_MAX: u32 = 127;
|
||||
pub const INT_FAST16_MAX: u64 = 9223372036854775807;
|
||||
pub const INT_FAST32_MAX: u64 = 9223372036854775807;
|
||||
pub const UINT_FAST8_MAX: u32 = 255;
|
||||
pub const UINT_FAST16_MAX: i32 = -1;
|
||||
pub const UINT_FAST32_MAX: i32 = -1;
|
||||
pub const INTPTR_MIN: i64 = -9223372036854775808;
|
||||
pub const INTPTR_MAX: u64 = 9223372036854775807;
|
||||
pub const UINTPTR_MAX: i32 = -1;
|
||||
pub const PTRDIFF_MIN: i64 = -9223372036854775808;
|
||||
pub const PTRDIFF_MAX: u64 = 9223372036854775807;
|
||||
pub const SIG_ATOMIC_MIN: i32 = -2147483648;
|
||||
pub const SIG_ATOMIC_MAX: u32 = 2147483647;
|
||||
pub const SIZE_MAX: i32 = -1;
|
||||
pub const WINT_MIN: u32 = 0;
|
||||
pub const WINT_MAX: u32 = 4294967295;
|
||||
pub const __BITS_PER_LONG: u32 = 64;
|
||||
pub const __FD_SETSIZE: u32 = 1024;
|
||||
pub const VRING_DESC_F_NEXT: u32 = 1;
|
||||
pub const VRING_DESC_F_WRITE: u32 = 2;
|
||||
pub const VRING_DESC_F_INDIRECT: u32 = 4;
|
||||
pub const VRING_USED_F_NO_NOTIFY: u32 = 1;
|
||||
pub const VRING_AVAIL_F_NO_INTERRUPT: u32 = 1;
|
||||
pub const VIRTIO_RING_F_INDIRECT_DESC: u32 = 28;
|
||||
pub const VIRTIO_RING_F_EVENT_IDX: u32 = 29;
|
||||
pub const VRING_AVAIL_ALIGN_SIZE: u32 = 2;
|
||||
pub const VRING_USED_ALIGN_SIZE: u32 = 4;
|
||||
pub const VRING_DESC_ALIGN_SIZE: u32 = 16;
|
||||
pub type int_least8_t = ::std::os::raw::c_schar;
|
||||
pub type int_least16_t = ::std::os::raw::c_short;
|
||||
pub type int_least32_t = ::std::os::raw::c_int;
|
||||
pub type int_least64_t = ::std::os::raw::c_long;
|
||||
pub type uint_least8_t = ::std::os::raw::c_uchar;
|
||||
pub type uint_least16_t = ::std::os::raw::c_ushort;
|
||||
pub type uint_least32_t = ::std::os::raw::c_uint;
|
||||
pub type uint_least64_t = ::std::os::raw::c_ulong;
|
||||
pub type int_fast8_t = ::std::os::raw::c_schar;
|
||||
pub type int_fast16_t = ::std::os::raw::c_long;
|
||||
pub type int_fast32_t = ::std::os::raw::c_long;
|
||||
pub type int_fast64_t = ::std::os::raw::c_long;
|
||||
pub type uint_fast8_t = ::std::os::raw::c_uchar;
|
||||
pub type uint_fast16_t = ::std::os::raw::c_ulong;
|
||||
pub type uint_fast32_t = ::std::os::raw::c_ulong;
|
||||
pub type uint_fast64_t = ::std::os::raw::c_ulong;
|
||||
pub type intmax_t = ::std::os::raw::c_long;
|
||||
pub type uintmax_t = ::std::os::raw::c_ulong;
|
||||
pub type __s8 = ::std::os::raw::c_schar;
|
||||
pub type __u8 = ::std::os::raw::c_uchar;
|
||||
pub type __s16 = ::std::os::raw::c_short;
|
||||
pub type __u16 = ::std::os::raw::c_ushort;
|
||||
pub type __s32 = ::std::os::raw::c_int;
|
||||
pub type __u32 = ::std::os::raw::c_uint;
|
||||
pub type __s64 = ::std::os::raw::c_longlong;
|
||||
pub type __u64 = ::std::os::raw::c_ulonglong;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct __kernel_fd_set {
|
||||
pub fds_bits: [::std::os::raw::c_ulong; 16usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout___kernel_fd_set() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<__kernel_fd_set>(),
|
||||
128usize,
|
||||
concat!("Size of: ", stringify!(__kernel_fd_set))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<__kernel_fd_set>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(__kernel_fd_set))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<__kernel_fd_set>())).fds_bits as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(__kernel_fd_set),
|
||||
"::",
|
||||
stringify!(fds_bits)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type __kernel_sighandler_t =
|
||||
::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
|
||||
pub type __kernel_key_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_mqd_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
|
||||
pub type __kernel_long_t = ::std::os::raw::c_long;
|
||||
pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
|
||||
pub type __kernel_ino_t = __kernel_ulong_t;
|
||||
pub type __kernel_mode_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_pid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_uid_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_gid_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_suseconds_t = __kernel_long_t;
|
||||
pub type __kernel_daddr_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_uid32_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_gid32_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_size_t = __kernel_ulong_t;
|
||||
pub type __kernel_ssize_t = __kernel_long_t;
|
||||
pub type __kernel_ptrdiff_t = __kernel_long_t;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct __kernel_fsid_t {
|
||||
pub val: [::std::os::raw::c_int; 2usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout___kernel_fsid_t() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<__kernel_fsid_t>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(__kernel_fsid_t))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<__kernel_fsid_t>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(__kernel_fsid_t))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<__kernel_fsid_t>())).val as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(__kernel_fsid_t),
|
||||
"::",
|
||||
stringify!(val)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type __kernel_off_t = __kernel_long_t;
|
||||
pub type __kernel_loff_t = ::std::os::raw::c_longlong;
|
||||
pub type __kernel_time_t = __kernel_long_t;
|
||||
pub type __kernel_clock_t = __kernel_long_t;
|
||||
pub type __kernel_timer_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_clockid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
|
||||
pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
|
||||
pub type __le16 = __u16;
|
||||
pub type __be16 = __u16;
|
||||
pub type __le32 = __u32;
|
||||
pub type __be32 = __u32;
|
||||
pub type __le64 = __u64;
|
||||
pub type __be64 = __u64;
|
||||
pub type __sum16 = __u16;
|
||||
pub type __wsum = __u32;
|
||||
pub type __virtio16 = __u16;
|
||||
pub type __virtio32 = __u32;
|
||||
pub type __virtio64 = __u64;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct vring_desc {
|
||||
pub addr: __virtio64,
|
||||
pub len: __virtio32,
|
||||
pub flags: __virtio16,
|
||||
pub next: __virtio16,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_vring_desc() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vring_desc>(),
|
||||
16usize,
|
||||
concat!("Size of: ", stringify!(vring_desc))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vring_desc>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vring_desc))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring_desc>())).addr as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring_desc),
|
||||
"::",
|
||||
stringify!(addr)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring_desc>())).len as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring_desc),
|
||||
"::",
|
||||
stringify!(len)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring_desc>())).flags as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring_desc),
|
||||
"::",
|
||||
stringify!(flags)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring_desc>())).next as *const _ as usize },
|
||||
14usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring_desc),
|
||||
"::",
|
||||
stringify!(next)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct vring_avail {
|
||||
pub flags: __virtio16,
|
||||
pub idx: __virtio16,
|
||||
pub ring: __IncompleteArrayField<__virtio16>,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_vring_avail() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vring_avail>(),
|
||||
4usize,
|
||||
concat!("Size of: ", stringify!(vring_avail))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vring_avail>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(vring_avail))
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct vring_used_elem {
|
||||
pub id: __virtio32,
|
||||
pub len: __virtio32,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_vring_used_elem() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vring_used_elem>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(vring_used_elem))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vring_used_elem>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(vring_used_elem))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring_used_elem>())).id as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring_used_elem),
|
||||
"::",
|
||||
stringify!(id)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring_used_elem>())).len as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring_used_elem),
|
||||
"::",
|
||||
stringify!(len)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct vring_used {
|
||||
pub flags: __virtio16,
|
||||
pub idx: __virtio16,
|
||||
pub ring: __IncompleteArrayField<vring_used_elem>,
|
||||
pub __bindgen_align: [u32; 0usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_vring_used() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vring_used>(),
|
||||
4usize,
|
||||
concat!("Size of: ", stringify!(vring_used))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vring_used>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(vring_used))
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct vring {
|
||||
pub num: ::std::os::raw::c_uint,
|
||||
pub desc: *mut vring_desc,
|
||||
pub avail: *mut vring_avail,
|
||||
pub used: *mut vring_used,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_vring() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vring>(),
|
||||
32usize,
|
||||
concat!("Size of: ", stringify!(vring))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vring>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vring))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring>())).num as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring),
|
||||
"::",
|
||||
stringify!(num)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring>())).desc as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring),
|
||||
"::",
|
||||
stringify!(desc)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring>())).avail as *const _ as usize },
|
||||
16usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring),
|
||||
"::",
|
||||
stringify!(avail)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring>())).used as *const _ as usize },
|
||||
24usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring),
|
||||
"::",
|
||||
stringify!(used)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Default for vring {
|
||||
fn default() -> Self {
|
||||
unsafe { ::std::mem::zeroed() }
|
||||
}
|
||||
}
|
||||
7
vm-device/Cargo.toml
Normal file
7
vm-device/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "vm-device"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
11
vm-device/src/lib.rs
Normal file
11
vm-device/src/lib.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
/// Trait meant for triggering the DMA mapping update related to an external
|
||||
/// device not managed fully through virtio. It is dedicated to virtio-iommu
|
||||
/// in order to trigger the map update anytime the mapping is updated from the
|
||||
/// guest.
|
||||
pub trait ExternalDmaMapping: Send + Sync {
|
||||
/// Map a memory range
|
||||
fn map(&self, iova: u64, gpa: u64, size: u64) -> std::result::Result<(), std::io::Error>;
|
||||
|
||||
/// Unmap a memory range
|
||||
fn unmap(&self, iova: u64, size: u64) -> std::result::Result<(), std::io::Error>;
|
||||
}
|
||||
@@ -4,6 +4,11 @@ version = "0.1.0"
|
||||
authors = ["Samuel Ortiz <sameo@linux.intel.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
pci_support = ["pci"]
|
||||
mmio_support = []
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3.2"
|
||||
devices = { path = "../devices" }
|
||||
@@ -12,11 +17,12 @@ libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
net_gen = { path = "../net_gen" }
|
||||
net_util = { path = "../net_util" }
|
||||
pci = { path = "../pci" }
|
||||
pci = { path = "../pci", optional = true }
|
||||
tempfile = "3.1.0"
|
||||
virtio-bindings = { path = "../virtio-bindings" }
|
||||
virtio-bindings = { git = "https://github.com/rust-vmm/virtio-bindings", version = "0.1", features = ["virtio-v5_0_0"]}
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
[dependencies.vhost_rs]
|
||||
path = "../vhost_rs"
|
||||
|
||||
@@ -26,7 +26,7 @@ use super::{
|
||||
VirtioDeviceType, VirtioInterruptType,
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use virtio_bindings::virtio_blk::*;
|
||||
use virtio_bindings::bindings::virtio_blk::*;
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
@@ -503,6 +503,7 @@ impl<T: DiskFile> Block<T> {
|
||||
mut disk_image: T,
|
||||
disk_path: PathBuf,
|
||||
is_disk_read_only: bool,
|
||||
iommu: bool,
|
||||
) -> io::Result<Block<T>> {
|
||||
let disk_size = disk_image.seek(SeekFrom::End(0))? as u64;
|
||||
if disk_size % SECTOR_SIZE != 0 {
|
||||
@@ -515,6 +516,10 @@ impl<T: DiskFile> Block<T> {
|
||||
|
||||
let mut avail_features = (1u64 << VIRTIO_F_VERSION_1) | (1u64 << VIRTIO_BLK_F_FLUSH);
|
||||
|
||||
if iommu {
|
||||
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
||||
}
|
||||
|
||||
if is_disk_read_only {
|
||||
avail_features |= 1u64 << VIRTIO_BLK_F_RO;
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::cmp;
|
||||
use std::collections::VecDeque;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::ops::DerefMut;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
@@ -16,7 +17,7 @@ use std::thread;
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
|
||||
VirtioInterruptType, VIRTIO_F_VERSION_1,
|
||||
VirtioInterruptType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
@@ -57,7 +58,7 @@ struct ConsoleEpollHandler {
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
in_buffer: Arc<Mutex<VecDeque<u8>>>,
|
||||
out: Box<dyn io::Write + Send>,
|
||||
out: Arc<Mutex<Box<dyn io::Write + Send + Sync + 'static>>>,
|
||||
input_queue_evt: EventFd,
|
||||
output_queue_evt: EventFd,
|
||||
input_evt: EventFd,
|
||||
@@ -130,8 +131,13 @@ impl ConsoleEpollHandler {
|
||||
let mem = self.mem.read().unwrap();
|
||||
for avail_desc in trans_queue.iter(&mem) {
|
||||
let len;
|
||||
let _ = mem.write_to(avail_desc.addr, &mut self.out, avail_desc.len as usize);
|
||||
let _ = self.out.flush();
|
||||
let mut out = self.out.lock().unwrap();
|
||||
let _ = mem.write_to(
|
||||
avail_desc.addr,
|
||||
&mut out.deref_mut(),
|
||||
avail_desc.len as usize,
|
||||
);
|
||||
let _ = out.flush();
|
||||
|
||||
len = avail_desc.len;
|
||||
used_desc_heads[used_count] = (avail_desc.index, len);
|
||||
@@ -321,17 +327,24 @@ pub struct Console {
|
||||
acked_features: u64,
|
||||
config: Arc<Mutex<VirtioConsoleConfig>>,
|
||||
input: Arc<ConsoleInput>,
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
out: Arc<Mutex<Box<dyn io::Write + Send + Sync + 'static>>>,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
}
|
||||
|
||||
impl Console {
|
||||
/// Create a new virtio console device that gets random data from /dev/urandom.
|
||||
pub fn new(
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
out: Box<dyn io::Write + Send + Sync + 'static>,
|
||||
cols: u16,
|
||||
rows: u16,
|
||||
iommu: bool,
|
||||
) -> io::Result<(Console, Arc<ConsoleInput>)> {
|
||||
let avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_CONSOLE_F_SIZE;
|
||||
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_CONSOLE_F_SIZE;
|
||||
|
||||
if iommu {
|
||||
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
||||
}
|
||||
|
||||
let input_evt = EventFd::new(EFD_NONBLOCK).unwrap();
|
||||
let config_evt = EventFd::new(EFD_NONBLOCK).unwrap();
|
||||
@@ -351,7 +364,9 @@ impl Console {
|
||||
acked_features: 0u64,
|
||||
config: console_config,
|
||||
input: console_input.clone(),
|
||||
out,
|
||||
out: Arc::new(Mutex::new(out)),
|
||||
queue_evts: None,
|
||||
interrupt_cb: None,
|
||||
},
|
||||
console_input,
|
||||
))
|
||||
@@ -456,6 +471,21 @@ impl VirtioDevice for Console {
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
// Save the interrupt EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||
|
||||
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
|
||||
for queue_evt in queue_evts.iter() {
|
||||
// Save the queue EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
|
||||
error!("failed to clone queue EventFd: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?);
|
||||
}
|
||||
self.queue_evts = Some(tmp_queue_evts);
|
||||
|
||||
self.input
|
||||
.acked_features
|
||||
.store(self.acked_features, Ordering::Relaxed);
|
||||
@@ -466,31 +496,41 @@ impl VirtioDevice for Console {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(out) = self.out.take() {
|
||||
let mut handler = ConsoleEpollHandler {
|
||||
queues,
|
||||
mem,
|
||||
interrupt_cb,
|
||||
in_buffer: self.input.in_buffer.clone(),
|
||||
out,
|
||||
input_queue_evt: queue_evts.remove(0),
|
||||
output_queue_evt: queue_evts.remove(0),
|
||||
input_evt: self.input.input_evt.try_clone().unwrap(),
|
||||
config_evt: self.input.config_evt.try_clone().unwrap(),
|
||||
kill_evt,
|
||||
};
|
||||
let mut handler = ConsoleEpollHandler {
|
||||
queues,
|
||||
mem,
|
||||
interrupt_cb,
|
||||
in_buffer: self.input.in_buffer.clone(),
|
||||
out: self.out.clone(),
|
||||
input_queue_evt: queue_evts.remove(0),
|
||||
output_queue_evt: queue_evts.remove(0),
|
||||
input_evt: self.input.input_evt.try_clone().unwrap(),
|
||||
config_evt: self.input.config_evt.try_clone().unwrap(),
|
||||
kill_evt,
|
||||
};
|
||||
|
||||
let worker_result = thread::Builder::new()
|
||||
.name("virtio_console".to_string())
|
||||
.spawn(move || handler.run());
|
||||
let worker_result = thread::Builder::new()
|
||||
.name("virtio_console".to_string())
|
||||
.spawn(move || handler.run());
|
||||
|
||||
if let Err(e) = worker_result {
|
||||
error!("failed to spawn virtio_console worker: {}", e);
|
||||
return Err(ActivateError::BadActivate);;
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
if let Err(e) = worker_result {
|
||||
error!("failed to spawn virtio_console worker: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
Err(ActivateError::BadActivate)
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
// Return the interrupt and queue EventFDs
|
||||
Some((
|
||||
self.interrupt_cb.take().unwrap(),
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ pub type VirtioInterrupt = Box<
|
||||
+ Sync,
|
||||
>;
|
||||
|
||||
pub type VirtioIommuRemapping =
|
||||
Box<dyn Fn(u64) -> std::result::Result<u64, std::io::Error> + Send + Sync>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VirtioSharedMemory {
|
||||
pub offset: u64,
|
||||
@@ -83,4 +86,18 @@ pub trait VirtioDevice: Send {
|
||||
fn get_shm_regions(&self) -> Option<VirtioSharedMemoryList> {
|
||||
None
|
||||
}
|
||||
|
||||
fn iommu_translate(&self, addr: u64) -> u64 {
|
||||
addr
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait providing address translation the same way a physical DMA remapping
|
||||
/// table would provide translation between an IOVA and a physical address.
|
||||
/// The goal of this trait is to be used by virtio devices to perform the
|
||||
/// address translation before they try to read from the guest physical address.
|
||||
/// On the other side, the implementation itself should be provided by the code
|
||||
/// emulating the IOMMU for the guest.
|
||||
pub trait DmaRemapping: Send + Sync {
|
||||
fn translate(&self, id: u32, addr: u64) -> std::result::Result<u64, std::io::Error>;
|
||||
}
|
||||
|
||||
914
vm-virtio/src/iommu.rs
Normal file
914
vm-virtio/src/iommu.rs
Normal file
@@ -0,0 +1,914 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use epoll;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std::cmp;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{self, Display};
|
||||
use std::io::{self, Write};
|
||||
use std::mem::size_of;
|
||||
use std::ops::Bound::Included;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::result;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
|
||||
VirtioDeviceType, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::{DmaRemapping, VirtioInterrupt, VirtioInterruptType};
|
||||
use vm_device::ExternalDmaMapping;
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// Queues sizes
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
const NUM_QUEUES: usize = 2;
|
||||
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
|
||||
|
||||
/// New descriptors are pending on the request queue.
|
||||
/// "requestq" is meant to be used anytime an action is required to be
|
||||
/// performed on behalf of the guest driver.
|
||||
const REQUEST_Q_EVENT: DeviceEventT = 0;
|
||||
/// New descriptors are pending on the event queue.
|
||||
/// "eventq" lets the device report any fault or other asynchronous event to
|
||||
/// the guest driver.
|
||||
const EVENT_Q_EVENT: DeviceEventT = 1;
|
||||
/// The device has been dropped.
|
||||
const KILL_EVENT: DeviceEventT = 2;
|
||||
|
||||
/// Virtio IOMMU features
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_F_INPUT_RANGE: u32 = 0;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_F_DOMAIN_BITS: u32 = 1;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_F_MAP_UNMAP: u32 = 2;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_F_BYPASS: u32 = 3;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_F_PROBE: u32 = 4;
|
||||
|
||||
// Support 2MiB and 4KiB page sizes.
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_PAGE_SIZE_MASK: u64 = (2 << 20) | (4 << 10);
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuRange {
|
||||
start: u64,
|
||||
end: u64,
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuRange {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuConfig {
|
||||
page_size_mask: u64,
|
||||
input_range: VirtioIommuRange,
|
||||
domain_bits: u8,
|
||||
padding: [u8; 3],
|
||||
probe_size: u32,
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuConfig {}
|
||||
|
||||
/// Virtio IOMMU request type
|
||||
const VIRTIO_IOMMU_T_ATTACH: u8 = 1;
|
||||
const VIRTIO_IOMMU_T_DETACH: u8 = 2;
|
||||
const VIRTIO_IOMMU_T_MAP: u8 = 3;
|
||||
const VIRTIO_IOMMU_T_UNMAP: u8 = 4;
|
||||
const VIRTIO_IOMMU_T_PROBE: u8 = 5;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuReqHead {
|
||||
type_: u8,
|
||||
reserved: [u8; 3],
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuReqHead {}
|
||||
|
||||
/// Virtio IOMMU request status
|
||||
const VIRTIO_IOMMU_S_OK: u8 = 0;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_S_IOERR: u8 = 1;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_S_UNSUPP: u8 = 2;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_S_DEVERR: u8 = 3;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_S_INVAL: u8 = 4;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_S_RANGE: u8 = 5;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_S_NOENT: u8 = 6;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_S_FAULT: u8 = 7;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuReqTail {
|
||||
status: u8,
|
||||
reserved: [u8; 3],
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuReqTail {}
|
||||
|
||||
/// ATTACH request
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuReqAttach {
|
||||
domain: u32,
|
||||
endpoint: u32,
|
||||
reserved: [u8; 8],
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuReqAttach {}
|
||||
|
||||
/// DETACH request
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuReqDetach {
|
||||
domain: u32,
|
||||
endpoint: u32,
|
||||
reserved: [u8; 8],
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuReqDetach {}
|
||||
|
||||
/// Virtio IOMMU request MAP flags
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_MAP_F_READ: u32 = 1;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_MAP_F_WRITE: u32 = 1 << 1;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_MAP_F_EXEC: u32 = 1 << 2;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_MAP_F_MMIO: u32 = 1 << 3;
|
||||
|
||||
/// MAP request
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuReqMap {
|
||||
domain: u32,
|
||||
virt_start: u64,
|
||||
virt_end: u64,
|
||||
phys_start: u64,
|
||||
flags: u32,
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuReqMap {}
|
||||
|
||||
/// UNMAP request
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuReqUnmap {
|
||||
domain: u32,
|
||||
virt_start: u64,
|
||||
virt_end: u64,
|
||||
reserved: [u8; 4],
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuReqUnmap {}
|
||||
|
||||
/// Virtio IOMMU request PROBE types
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_PROBE_T_MASK: u32 = 0xfff;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_PROBE_T_NONE: u32 = 0;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_PROBE_T_RESV_MEM: u32 = 1;
|
||||
|
||||
/// PROBE request
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuReqProbe {
|
||||
endpoint: u32,
|
||||
reserved: [u64; 8],
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuReqProbe {}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuProbeProperty {
|
||||
type_: u16,
|
||||
length: u16,
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuProbeProperty {}
|
||||
|
||||
/// Virtio IOMMU request PROBE property RESV_MEM subtypes
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_RESV_MEM_T_RESERVED: u32 = 0;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_RESV_MEM_T_MSI: u32 = 1;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuProbeResvMem {
|
||||
head: VirtioIommuProbeProperty,
|
||||
subtype: u8,
|
||||
reserved: [u8; 3],
|
||||
start: u64,
|
||||
end: u64,
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuProbeResvMem {}
|
||||
|
||||
/// Virtio IOMMU fault flags
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_FAULT_F_READ: u32 = 1;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_FAULT_F_WRITE: u32 = 1 << 1;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_FAULT_F_EXEC: u32 = 1 << 2;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_FAULT_F_ADDRESS: u32 = 1 << 8;
|
||||
|
||||
/// Virtio IOMMU fault reasons
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_FAULT_R_UNKNOWN: u32 = 0;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_FAULT_R_DOMAIN: u32 = 1;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_FAULT_R_MAPPING: u32 = 2;
|
||||
|
||||
/// Fault reporting through eventq
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuFault {
|
||||
reason: u8,
|
||||
reserved: [u8; 3],
|
||||
flags: u32,
|
||||
endpoint: u32,
|
||||
reserved1: u32,
|
||||
address: u64,
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuFault {}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
/// Guest gave us bad memory addresses.
|
||||
GuestMemory(GuestMemoryError),
|
||||
/// Guest gave us a write only descriptor that protocol says to read from.
|
||||
UnexpectedWriteOnlyDescriptor,
|
||||
/// Guest gave us a read only descriptor that protocol says to write to.
|
||||
UnexpectedReadOnlyDescriptor,
|
||||
/// Guest gave us too few descriptors in a descriptor chain.
|
||||
DescriptorChainTooShort,
|
||||
/// Guest gave us a buffer that was too short to use.
|
||||
BufferLengthTooSmall,
|
||||
/// Guest sent us invalid request.
|
||||
InvalidRequest,
|
||||
/// Guest sent us invalid ATTACH request.
|
||||
InvalidAttachRequest,
|
||||
/// Guest sent us invalid DETACH request.
|
||||
InvalidDetachRequest,
|
||||
/// Guest sent us invalid MAP request.
|
||||
InvalidMapRequest,
|
||||
/// Guest sent us invalid UNMAP request.
|
||||
InvalidUnmapRequest,
|
||||
/// Guest sent us invalid PROBE request.
|
||||
InvalidProbeRequest,
|
||||
/// Failed to performing external mapping.
|
||||
ExternalMapping(io::Error),
|
||||
/// Failed to performing external unmapping.
|
||||
ExternalUnmapping(io::Error),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
match self {
|
||||
BufferLengthTooSmall => write!(f, "buffer length too small"),
|
||||
DescriptorChainTooShort => write!(f, "descriptor chain too short"),
|
||||
GuestMemory(e) => write!(f, "bad guest memory address: {}", e),
|
||||
InvalidRequest => write!(f, "invalid request"),
|
||||
InvalidAttachRequest => write!(f, "invalid attach request"),
|
||||
InvalidDetachRequest => write!(f, "invalid detach request"),
|
||||
InvalidMapRequest => write!(f, "invalid map request"),
|
||||
InvalidUnmapRequest => write!(f, "invalid unmap request"),
|
||||
InvalidProbeRequest => write!(f, "invalid probe request"),
|
||||
UnexpectedReadOnlyDescriptor => write!(f, "unexpected read-only descriptor"),
|
||||
UnexpectedWriteOnlyDescriptor => write!(f, "unexpected write-only descriptor"),
|
||||
ExternalMapping(e) => write!(f, "failed performing external mapping: {}", e),
|
||||
ExternalUnmapping(e) => write!(f, "failed performing external unmapping: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum RequestType {
|
||||
Attach,
|
||||
Detach,
|
||||
Map,
|
||||
Unmap,
|
||||
Probe,
|
||||
}
|
||||
|
||||
struct Request {
|
||||
#[allow(unused)]
|
||||
type_: RequestType,
|
||||
status_addr: GuestAddress,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
// Parse the available vring buffer. Based on the hashmap table of external
|
||||
// mappings required from various devices such as VFIO or vhost-user ones,
|
||||
// this function might update the hashmap table of external mappings per
|
||||
// domain.
|
||||
// Basically, the VMM knows about the device_id <=> mapping relationship
|
||||
// before running the VM, but at runtime, a new domain <=> mapping hashmap
|
||||
// is created based on the information provided from the guest driver for
|
||||
// virtio-iommu (giving the link device_id <=> domain).
|
||||
fn parse(
|
||||
avail_desc: &DescriptorChain,
|
||||
mem: &GuestMemoryMmap,
|
||||
mapping: &Arc<IommuMapping>,
|
||||
ext_mapping: &BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
|
||||
ext_domain_mapping: &mut BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
|
||||
) -> result::Result<Request, Error> {
|
||||
// The head contains the request type which MUST be readable.
|
||||
if avail_desc.is_write_only() {
|
||||
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
||||
}
|
||||
|
||||
if (avail_desc.len as usize) < size_of::<VirtioIommuReqHead>() {
|
||||
return Err(Error::InvalidRequest);
|
||||
}
|
||||
|
||||
let req_head: VirtioIommuReqHead =
|
||||
mem.read_obj(avail_desc.addr).map_err(Error::GuestMemory)?;
|
||||
let req_offset = size_of::<VirtioIommuReqHead>();
|
||||
let desc_size_left = (avail_desc.len as usize) - req_offset;
|
||||
let req_addr = if let Some(addr) = avail_desc.addr.checked_add(req_offset as u64) {
|
||||
addr
|
||||
} else {
|
||||
return Err(Error::InvalidRequest);
|
||||
};
|
||||
|
||||
let request_type = match req_head.type_ {
|
||||
VIRTIO_IOMMU_T_ATTACH => {
|
||||
if desc_size_left != size_of::<VirtioIommuReqAttach>() {
|
||||
return Err(Error::InvalidAttachRequest);
|
||||
}
|
||||
|
||||
let req: VirtioIommuReqAttach = mem
|
||||
.read_obj(req_addr as GuestAddress)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
debug!("Attach request {:?}", req);
|
||||
|
||||
// Copy the value to use it as a proper reference.
|
||||
let domain = req.domain;
|
||||
let endpoint = req.endpoint;
|
||||
|
||||
// Add endpoint associated with specific domain
|
||||
mapping.endpoints.write().unwrap().insert(endpoint, domain);
|
||||
|
||||
// If the endpoint is part of the list of devices with an
|
||||
// external mapping, insert a new entry for the corresponding
|
||||
// domain, with the same reference to the trait.
|
||||
if let Some(map) = ext_mapping.get(&endpoint) {
|
||||
ext_domain_mapping.insert(domain, map.clone());
|
||||
}
|
||||
|
||||
// Add new domain with no mapping if the entry didn't exist yet
|
||||
let mut mappings = mapping.mappings.write().unwrap();
|
||||
if !mappings.contains_key(&domain) {
|
||||
mappings.insert(domain, BTreeMap::new());
|
||||
}
|
||||
|
||||
RequestType::Attach
|
||||
}
|
||||
VIRTIO_IOMMU_T_DETACH => {
|
||||
if desc_size_left != size_of::<VirtioIommuReqDetach>() {
|
||||
return Err(Error::InvalidDetachRequest);
|
||||
}
|
||||
|
||||
let req: VirtioIommuReqDetach = mem
|
||||
.read_obj(req_addr as GuestAddress)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
debug!("Detach request {:?}", req);
|
||||
|
||||
// Copy the value to use it as a proper reference.
|
||||
let domain = req.domain;
|
||||
let endpoint = req.endpoint;
|
||||
|
||||
// If the endpoint is part of the list of devices with an
|
||||
// external mapping, remove the entry for the corresponding
|
||||
// domain.
|
||||
if ext_mapping.contains_key(&endpoint) {
|
||||
ext_domain_mapping.remove(&domain);
|
||||
}
|
||||
|
||||
// Remove endpoint associated with specific domain
|
||||
mapping.endpoints.write().unwrap().remove(&endpoint);
|
||||
|
||||
RequestType::Detach
|
||||
}
|
||||
VIRTIO_IOMMU_T_MAP => {
|
||||
if desc_size_left != size_of::<VirtioIommuReqMap>() {
|
||||
return Err(Error::InvalidMapRequest);
|
||||
}
|
||||
|
||||
let req: VirtioIommuReqMap = mem
|
||||
.read_obj(req_addr as GuestAddress)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
debug!("Map request {:?}", req);
|
||||
|
||||
// Copy the value to use it as a proper reference.
|
||||
let domain = req.domain;
|
||||
|
||||
// Trigger external mapping if necessary.
|
||||
if let Some(ext_map) = ext_domain_mapping.get(&domain) {
|
||||
let size = req.virt_end - req.virt_start + 1;
|
||||
ext_map
|
||||
.map(req.virt_start, req.phys_start, size)
|
||||
.map_err(Error::ExternalMapping)?;
|
||||
}
|
||||
|
||||
// Add new mapping associated with the domain
|
||||
if let Some(entry) = mapping.mappings.write().unwrap().get_mut(&domain) {
|
||||
entry.insert(
|
||||
req.virt_start,
|
||||
Mapping {
|
||||
gpa: req.phys_start,
|
||||
size: req.virt_end - req.virt_start + 1,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return Err(Error::InvalidMapRequest);
|
||||
}
|
||||
|
||||
RequestType::Map
|
||||
}
|
||||
VIRTIO_IOMMU_T_UNMAP => {
|
||||
if desc_size_left != size_of::<VirtioIommuReqUnmap>() {
|
||||
return Err(Error::InvalidUnmapRequest);
|
||||
}
|
||||
|
||||
let req: VirtioIommuReqUnmap = mem
|
||||
.read_obj(req_addr as GuestAddress)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
debug!("Unmap request {:?}", req);
|
||||
|
||||
// Copy the value to use it as a proper reference.
|
||||
let domain = req.domain;
|
||||
let virt_start = req.virt_start;
|
||||
|
||||
// Trigger external unmapping if necessary.
|
||||
if let Some(ext_map) = ext_domain_mapping.get(&domain) {
|
||||
let size = req.virt_end - virt_start + 1;
|
||||
ext_map
|
||||
.unmap(virt_start, size)
|
||||
.map_err(Error::ExternalUnmapping)?;
|
||||
}
|
||||
|
||||
// Add new mapping associated with the domain
|
||||
if let Some(entry) = mapping.mappings.write().unwrap().get_mut(&domain) {
|
||||
entry.remove(&virt_start);
|
||||
}
|
||||
|
||||
RequestType::Unmap
|
||||
}
|
||||
VIRTIO_IOMMU_T_PROBE => {
|
||||
if desc_size_left != size_of::<VirtioIommuReqProbe>() {
|
||||
return Err(Error::InvalidProbeRequest);
|
||||
}
|
||||
|
||||
let req: VirtioIommuReqProbe = mem
|
||||
.read_obj(req_addr as GuestAddress)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
debug!("Probe request {:?}", req);
|
||||
|
||||
RequestType::Probe
|
||||
}
|
||||
_ => return Err(Error::InvalidRequest),
|
||||
};
|
||||
|
||||
let status_desc = avail_desc
|
||||
.next_descriptor()
|
||||
.ok_or(Error::DescriptorChainTooShort)?;
|
||||
|
||||
// The status MUST always be writable
|
||||
if !status_desc.is_write_only() {
|
||||
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||
}
|
||||
|
||||
if (status_desc.len as usize) < size_of::<VirtioIommuReqTail>() {
|
||||
return Err(Error::BufferLengthTooSmall);
|
||||
}
|
||||
|
||||
Ok(Request {
|
||||
type_: request_type,
|
||||
status_addr: status_desc.addr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct IommuEpollHandler {
|
||||
queues: Vec<Queue>,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
kill_evt: EventFd,
|
||||
mapping: Arc<IommuMapping>,
|
||||
ext_mapping: BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
|
||||
ext_domain_mapping: BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
|
||||
}
|
||||
|
||||
impl IommuEpollHandler {
|
||||
fn request_queue(&mut self) -> bool {
|
||||
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
|
||||
let mut used_count = 0;
|
||||
let mem = self.mem.read().unwrap();
|
||||
for avail_desc in self.queues[0].iter(&mem) {
|
||||
let len = match Request::parse(
|
||||
&avail_desc,
|
||||
&mem,
|
||||
&self.mapping,
|
||||
&self.ext_mapping,
|
||||
&mut self.ext_domain_mapping,
|
||||
) {
|
||||
Ok(ref req) => {
|
||||
let reply = VirtioIommuReqTail {
|
||||
status: VIRTIO_IOMMU_S_OK,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match mem.write_obj(reply, req.status_addr) {
|
||||
Ok(_) => size_of::<VirtioIommuReqTail>() as u32,
|
||||
Err(e) => {
|
||||
error!("bad guest memory address: {}", e);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to parse available descriptor chain: {:?}", e);
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
used_desc_heads[used_count] = (avail_desc.index, len);
|
||||
used_count += 1;
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
self.queues[0].add_used(&mem, desc_index, len);
|
||||
}
|
||||
used_count > 0
|
||||
}
|
||||
|
||||
fn event_queue(&mut self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> {
|
||||
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(queue)).map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&mut self) -> result::Result<(), DeviceError> {
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
||||
|
||||
// Add events
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.queue_evts[0].as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(REQUEST_Q_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.queue_evts[1].as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(EVENT_Q_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.kill_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
|
||||
const EPOLL_EVENTS_LEN: usize = 100;
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
// It's well defined from the epoll_wait() syscall
|
||||
// documentation that the epoll loop can be interrupted
|
||||
// before any of the requested events occurred or the
|
||||
// timeout expired. In both those cases, epoll_wait()
|
||||
// returns an error of type EINTR, but this should not
|
||||
// be considered as a regular error. Instead it is more
|
||||
// appropriate to retry, by calling into epoll_wait().
|
||||
continue;
|
||||
}
|
||||
return Err(DeviceError::EpollWait(e));
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let ev_type = event.data as u16;
|
||||
|
||||
match ev_type {
|
||||
REQUEST_Q_EVENT => {
|
||||
if let Err(e) = self.queue_evts[0].read() {
|
||||
error!("Failed to get queue event: {:?}", e);
|
||||
break 'epoll;
|
||||
} else if self.request_queue() {
|
||||
if let Err(e) = self.signal_used_queue(&self.queues[0]) {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
break 'epoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
EVENT_Q_EVENT => {
|
||||
if let Err(e) = self.queue_evts[1].read() {
|
||||
error!("Failed to get queue event: {:?}", e);
|
||||
break 'epoll;
|
||||
} else if self.event_queue() {
|
||||
if let Err(e) = self.signal_used_queue(&self.queues[1]) {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
break 'epoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
KILL_EVENT => {
|
||||
debug!("kill_evt received, stopping epoll loop");
|
||||
break 'epoll;
|
||||
}
|
||||
_ => {
|
||||
error!("Unknown event for virtio-iommu");
|
||||
break 'epoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("Exit epoll loop");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Mapping {
|
||||
gpa: u64,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
pub struct IommuMapping {
|
||||
// Domain related to an endpoint.
|
||||
endpoints: Arc<RwLock<BTreeMap<u32, u32>>>,
|
||||
// List of mappings per domain.
|
||||
mappings: Arc<RwLock<BTreeMap<u32, BTreeMap<u64, Mapping>>>>,
|
||||
}
|
||||
|
||||
impl DmaRemapping for IommuMapping {
|
||||
fn translate(&self, id: u32, addr: u64) -> std::result::Result<u64, std::io::Error> {
|
||||
debug!("Translate addr 0x{:x}", addr);
|
||||
if let Some(domain) = self.endpoints.read().unwrap().get(&id) {
|
||||
if let Some(mapping) = self.mappings.read().unwrap().get(domain) {
|
||||
let range_start = if VIRTIO_IOMMU_PAGE_SIZE_MASK > addr {
|
||||
0
|
||||
} else {
|
||||
addr - VIRTIO_IOMMU_PAGE_SIZE_MASK
|
||||
};
|
||||
for (&key, &value) in mapping.range((Included(&range_start), Included(&addr))) {
|
||||
if addr >= key && addr < key + value.size {
|
||||
let new_addr = addr - key + value.gpa;
|
||||
debug!("Into new_addr 0x{:x}", new_addr);
|
||||
return Ok(new_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Into same addr...");
|
||||
Ok(addr)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Iommu {
|
||||
kill_evt: Option<EventFd>,
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
config: VirtioIommuConfig,
|
||||
mapping: Arc<IommuMapping>,
|
||||
ext_mapping: BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
}
|
||||
|
||||
impl Iommu {
|
||||
pub fn new() -> io::Result<(Self, Arc<IommuMapping>)> {
|
||||
let config = VirtioIommuConfig {
|
||||
page_size_mask: VIRTIO_IOMMU_PAGE_SIZE_MASK,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mapping = Arc::new(IommuMapping {
|
||||
endpoints: Arc::new(RwLock::new(BTreeMap::new())),
|
||||
mappings: Arc::new(RwLock::new(BTreeMap::new())),
|
||||
});
|
||||
|
||||
Ok((
|
||||
Iommu {
|
||||
kill_evt: None,
|
||||
avail_features: 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_IOMMU_F_MAP_UNMAP,
|
||||
acked_features: 0u64,
|
||||
config,
|
||||
mapping: mapping.clone(),
|
||||
ext_mapping: BTreeMap::new(),
|
||||
queue_evts: None,
|
||||
interrupt_cb: None,
|
||||
},
|
||||
mapping,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn add_external_mapping(&mut self, device_id: u32, mapping: Arc<dyn ExternalDmaMapping>) {
|
||||
self.ext_mapping.insert(device_id, mapping);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Iommu {
|
||||
fn drop(&mut self) {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioDevice for Iommu {
|
||||
fn device_type(&self) -> u32 {
|
||||
VirtioDeviceType::TYPE_IOMMU as u32
|
||||
}
|
||||
|
||||
fn queue_max_sizes(&self) -> &[u16] {
|
||||
QUEUE_SIZES
|
||||
}
|
||||
|
||||
fn features(&self, page: u32) -> u32 {
|
||||
match page {
|
||||
// Get the lower 32-bits of the features bitfield.
|
||||
0 => self.avail_features as u32,
|
||||
// Get the upper 32-bits of the features bitfield.
|
||||
1 => (self.avail_features >> 32) as u32,
|
||||
_ => {
|
||||
warn!("Received request for unknown features page.");
|
||||
0u32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ack_features(&mut self, page: u32, value: u32) {
|
||||
let mut v = match page {
|
||||
0 => u64::from(value),
|
||||
1 => u64::from(value) << 32,
|
||||
_ => {
|
||||
warn!("Cannot acknowledge unknown features page.");
|
||||
0u64
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the guest is ACK'ing a feature that we didn't claim to have.
|
||||
let unrequested_features = v & !self.avail_features;
|
||||
if unrequested_features != 0 {
|
||||
warn!("Received acknowledge request for unknown feature.");
|
||||
|
||||
// Don't count these features as acked.
|
||||
v &= !unrequested_features;
|
||||
}
|
||||
self.acked_features |= v;
|
||||
}
|
||||
|
||||
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
|
||||
let config_slice = self.config.as_slice();
|
||||
let config_len = config_slice.len() as u64;
|
||||
if offset >= config_len {
|
||||
error!("Failed to read config space");
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(end) = offset.checked_add(data.len() as u64) {
|
||||
// This write can't fail, offset and end are checked against config_len.
|
||||
data.write_all(&config_slice[offset as usize..cmp::min(end, config_len) as usize])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn write_config(&mut self, _offset: u64, _data: &[u8]) {
|
||||
warn!("virtio-iommu device configuration is read-only");
|
||||
}
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES {
|
||||
error!(
|
||||
"Cannot perform activate. Expected {} queue(s), got {}",
|
||||
NUM_QUEUES,
|
||||
queues.len()
|
||||
);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
let (self_kill_evt, kill_evt) =
|
||||
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed creating kill EventFd pair: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
// Save the interrupt EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||
|
||||
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
|
||||
for queue_evt in queue_evts.iter() {
|
||||
// Save the queue EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
|
||||
error!("failed to clone queue EventFd: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?);
|
||||
}
|
||||
self.queue_evts = Some(tmp_queue_evts);
|
||||
|
||||
let mut handler = IommuEpollHandler {
|
||||
queues,
|
||||
mem,
|
||||
interrupt_cb,
|
||||
queue_evts,
|
||||
kill_evt,
|
||||
mapping: self.mapping.clone(),
|
||||
ext_mapping: self.ext_mapping.clone(),
|
||||
ext_domain_mapping: BTreeMap::new(),
|
||||
};
|
||||
|
||||
let worker_result = thread::Builder::new()
|
||||
.name("virtio-iommu".to_string())
|
||||
.spawn(move || handler.run());
|
||||
|
||||
if let Err(e) = worker_result {
|
||||
error!("failed to spawn virtio-iommu worker: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
// Return the interrupt and queue EventFDs
|
||||
Some((
|
||||
self.interrupt_cb.take().unwrap(),
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
extern crate epoll;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg(feature = "pci_support")]
|
||||
extern crate pci;
|
||||
extern crate vhost_rs;
|
||||
extern crate virtio_bindings;
|
||||
@@ -23,10 +24,12 @@ use std::io;
|
||||
mod block;
|
||||
mod console;
|
||||
mod device;
|
||||
mod iommu;
|
||||
pub mod net;
|
||||
mod pmem;
|
||||
mod queue;
|
||||
mod rng;
|
||||
pub mod vsock;
|
||||
|
||||
pub mod transport;
|
||||
pub mod vhost_user;
|
||||
@@ -34,10 +37,12 @@ pub mod vhost_user;
|
||||
pub use self::block::*;
|
||||
pub use self::console::*;
|
||||
pub use self::device::*;
|
||||
pub use self::iommu::*;
|
||||
pub use self::net::*;
|
||||
pub use self::pmem::*;
|
||||
pub use self::queue::*;
|
||||
pub use self::rng::*;
|
||||
pub use self::vsock::*;
|
||||
|
||||
const DEVICE_INIT: u32 = 0x00;
|
||||
const DEVICE_ACKNOWLEDGE: u32 = 0x01;
|
||||
@@ -47,6 +52,8 @@ const DEVICE_FEATURES_OK: u32 = 0x08;
|
||||
const DEVICE_FAILED: u32 = 0x80;
|
||||
|
||||
const VIRTIO_F_VERSION_1: u32 = 32;
|
||||
const VIRTIO_F_IOMMU_PLATFORM: u32 = 33;
|
||||
const VIRTIO_F_IN_ORDER: u32 = 35;
|
||||
|
||||
// Types taken from linux/virtio_ids.h
|
||||
#[derive(Copy, Clone)]
|
||||
@@ -63,6 +70,7 @@ enum VirtioDeviceType {
|
||||
TYPE_GPU = 16,
|
||||
TYPE_INPUT = 18,
|
||||
TYPE_VSOCK = 19,
|
||||
TYPE_IOMMU = 23,
|
||||
TYPE_FS = 26,
|
||||
TYPE_PMEM = 27,
|
||||
TYPE_UNKNOWN = 0xFF,
|
||||
@@ -79,6 +87,7 @@ impl From<u32> for VirtioDeviceType {
|
||||
16 => VirtioDeviceType::TYPE_GPU,
|
||||
18 => VirtioDeviceType::TYPE_INPUT,
|
||||
19 => VirtioDeviceType::TYPE_VSOCK,
|
||||
23 => VirtioDeviceType::TYPE_IOMMU,
|
||||
26 => VirtioDeviceType::TYPE_FS,
|
||||
27 => VirtioDeviceType::TYPE_PMEM,
|
||||
_ => VirtioDeviceType::TYPE_UNKNOWN,
|
||||
@@ -126,6 +135,10 @@ pub enum ActivateError {
|
||||
VhostUserSetup(vhost_user::Error),
|
||||
/// Failed to setup vhost-user daemon.
|
||||
VhostUserNetSetup(vhost_user::Error),
|
||||
/// Failed to setup vhost-user daemon.
|
||||
VhostUserBlkSetup(vhost_user::Error),
|
||||
/// Failed to reset vhost-user daemon.
|
||||
VhostUserReset(vhost_user::Error),
|
||||
}
|
||||
|
||||
pub type ActivateResult = std::result::Result<(), ActivateError>;
|
||||
|
||||
@@ -28,7 +28,7 @@ use super::{
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use net_util::{MacAddr, Tap, TapError, MAC_ADDR_LEN};
|
||||
use virtio_bindings::virtio_net::*;
|
||||
use virtio_bindings::bindings::virtio_net::*;
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
@@ -439,11 +439,13 @@ pub struct Net {
|
||||
// The config space will only consist of the MAC address specified by the user,
|
||||
// or nothing, if no such address if provided.
|
||||
config_space: Vec<u8>,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
}
|
||||
|
||||
impl Net {
|
||||
/// Create a new virtio network device with the given TAP interface.
|
||||
pub fn new_with_tap(tap: Tap, guest_mac: Option<&MacAddr>) -> Result<Self> {
|
||||
pub fn new_with_tap(tap: Tap, guest_mac: Option<&MacAddr>, iommu: bool) -> Result<Self> {
|
||||
// Set offload flags to match the virtio features below.
|
||||
tap.set_offload(
|
||||
net_gen::TUN_F_CSUM | net_gen::TUN_F_UFO | net_gen::TUN_F_TSO4 | net_gen::TUN_F_TSO6,
|
||||
@@ -462,6 +464,10 @@ impl Net {
|
||||
| 1 << VIRTIO_NET_F_HOST_UFO
|
||||
| 1 << VIRTIO_F_VERSION_1;
|
||||
|
||||
if iommu {
|
||||
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
||||
}
|
||||
|
||||
let mut config_space;
|
||||
if let Some(mac) = guest_mac {
|
||||
config_space = Vec::with_capacity(MAC_ADDR_LEN);
|
||||
@@ -481,18 +487,25 @@ impl Net {
|
||||
avail_features,
|
||||
acked_features: 0u64,
|
||||
config_space,
|
||||
queue_evts: None,
|
||||
interrupt_cb: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new virtio network device with the given IP address and
|
||||
/// netmask.
|
||||
pub fn new(ip_addr: Ipv4Addr, netmask: Ipv4Addr, guest_mac: Option<&MacAddr>) -> Result<Self> {
|
||||
pub fn new(
|
||||
ip_addr: Ipv4Addr,
|
||||
netmask: Ipv4Addr,
|
||||
guest_mac: Option<&MacAddr>,
|
||||
iommu: bool,
|
||||
) -> Result<Self> {
|
||||
let tap = Tap::new().map_err(Error::TapOpen)?;
|
||||
tap.set_ip_addr(ip_addr).map_err(Error::TapSetIp)?;
|
||||
tap.set_netmask(netmask).map_err(Error::TapSetNetmask)?;
|
||||
tap.enable().map_err(Error::TapEnable)?;
|
||||
|
||||
Self::new_with_tap(tap, guest_mac)
|
||||
Self::new_with_tap(tap, guest_mac, iommu)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,7 +610,22 @@ impl VirtioDevice for Net {
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
if let Some(tap) = self.tap.take() {
|
||||
if let Some(tap) = self.tap.clone() {
|
||||
// Save the interrupt EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||
|
||||
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
|
||||
for queue_evt in queue_evts.iter() {
|
||||
// Save the queue EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
|
||||
error!("failed to clone queue EventFd: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?);
|
||||
}
|
||||
self.queue_evts = Some(tmp_queue_evts);
|
||||
|
||||
let rx_queue = queues.remove(0);
|
||||
let tx_queue = queues.remove(0);
|
||||
let rx_queue_evt = queue_evts.remove(0);
|
||||
@@ -626,4 +654,17 @@ impl VirtioDevice for Net {
|
||||
}
|
||||
Err(ActivateError::BadActivate)
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
// Return the interrupt and queue EventFDs
|
||||
Some((
|
||||
self.interrupt_cb.take().unwrap(),
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ use std::thread;
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
|
||||
VirtioDeviceType, VIRTIO_F_VERSION_1,
|
||||
VirtioDeviceType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::{VirtioInterrupt, VirtioInterruptType};
|
||||
use vm_memory::{
|
||||
@@ -291,21 +291,31 @@ pub struct Pmem {
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
config: VirtioPmemConfig,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
}
|
||||
|
||||
impl Pmem {
|
||||
pub fn new(disk: File, addr: GuestAddress, size: GuestUsize) -> io::Result<Pmem> {
|
||||
pub fn new(disk: File, addr: GuestAddress, size: GuestUsize, iommu: bool) -> io::Result<Pmem> {
|
||||
let config = VirtioPmemConfig {
|
||||
start: addr.raw_value().to_le(),
|
||||
size: size.to_le(),
|
||||
};
|
||||
|
||||
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
|
||||
|
||||
if iommu {
|
||||
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
||||
}
|
||||
|
||||
Ok(Pmem {
|
||||
kill_evt: None,
|
||||
disk: Some(disk),
|
||||
avail_features: 1u64 << VIRTIO_F_VERSION_1,
|
||||
avail_features,
|
||||
acked_features: 0u64,
|
||||
config,
|
||||
queue_evts: None,
|
||||
interrupt_cb: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -407,7 +417,26 @@ impl VirtioDevice for Pmem {
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
if let Some(disk) = self.disk.take() {
|
||||
// Save the interrupt EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||
|
||||
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
|
||||
for queue_evt in queue_evts.iter() {
|
||||
// Save the queue EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
|
||||
error!("failed to clone queue EventFd: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?);
|
||||
}
|
||||
self.queue_evts = Some(tmp_queue_evts);
|
||||
|
||||
if let Some(disk) = self.disk.as_ref() {
|
||||
let disk = disk.try_clone().map_err(|e| {
|
||||
error!("failed cloning pmem disk: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?;
|
||||
let mut handler = PmemEpollHandler {
|
||||
queue: queues.remove(0),
|
||||
mem,
|
||||
@@ -423,11 +452,24 @@ impl VirtioDevice for Pmem {
|
||||
|
||||
if let Err(e) = worker_result {
|
||||
error!("failed to spawn virtio_pmem worker: {}", e);
|
||||
return Err(ActivateError::BadActivate);;
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Err(ActivateError::BadActivate)
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
// Return the interrupt and queue EventFDs
|
||||
Some((
|
||||
self.interrupt_cb.take().unwrap(),
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
use std::cmp::min;
|
||||
use std::num::Wrapping;
|
||||
use std::sync::atomic::{fence, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::device::VirtioIommuRemapping;
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
|
||||
};
|
||||
@@ -41,10 +43,13 @@ unsafe impl ByteValued for Descriptor {}
|
||||
|
||||
/// A virtio descriptor chain.
|
||||
pub struct DescriptorChain<'a> {
|
||||
mem: &'a GuestMemoryMmap,
|
||||
desc_table: GuestAddress,
|
||||
queue_size: u16,
|
||||
ttl: u16, // used to prevent infinite chain cycles
|
||||
iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
|
||||
|
||||
/// Reference to guest memory
|
||||
pub mem: &'a GuestMemoryMmap,
|
||||
|
||||
/// Index into the descriptor table
|
||||
pub index: u16,
|
||||
@@ -69,6 +74,7 @@ impl<'a> DescriptorChain<'a> {
|
||||
desc_table: GuestAddress,
|
||||
queue_size: u16,
|
||||
index: u16,
|
||||
iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
|
||||
) -> Option<DescriptorChain> {
|
||||
if index >= queue_size {
|
||||
return None;
|
||||
@@ -89,16 +95,25 @@ impl<'a> DescriptorChain<'a> {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Translate address if necessary
|
||||
let desc_addr = if let Some(iommu_mapping_cb) = &iommu_mapping_cb {
|
||||
(iommu_mapping_cb)(desc.addr).unwrap()
|
||||
} else {
|
||||
desc.addr
|
||||
};
|
||||
|
||||
let chain = DescriptorChain {
|
||||
mem,
|
||||
desc_table,
|
||||
queue_size,
|
||||
ttl: queue_size,
|
||||
index,
|
||||
addr: GuestAddress(desc.addr),
|
||||
addr: GuestAddress(desc_addr),
|
||||
len: desc.len,
|
||||
flags: desc.flags,
|
||||
next: desc.next,
|
||||
iommu_mapping_cb,
|
||||
};
|
||||
|
||||
if chain.is_valid() {
|
||||
@@ -135,12 +150,17 @@ impl<'a> DescriptorChain<'a> {
|
||||
/// the head of the next _available_ descriptor chain.
|
||||
pub fn next_descriptor(&self) -> Option<DescriptorChain<'a>> {
|
||||
if self.has_next() {
|
||||
DescriptorChain::checked_new(self.mem, self.desc_table, self.queue_size, self.next).map(
|
||||
|mut c| {
|
||||
c.ttl = self.ttl - 1;
|
||||
c
|
||||
},
|
||||
DescriptorChain::checked_new(
|
||||
self.mem,
|
||||
self.desc_table,
|
||||
self.queue_size,
|
||||
self.next,
|
||||
self.iommu_mapping_cb.clone(),
|
||||
)
|
||||
.map(|mut c| {
|
||||
c.ttl = self.ttl - 1;
|
||||
c
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -156,6 +176,7 @@ pub struct AvailIter<'a, 'b> {
|
||||
last_index: Wrapping<u16>,
|
||||
queue_size: u16,
|
||||
next_avail: &'b mut Wrapping<u16>,
|
||||
iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> AvailIter<'a, 'b> {
|
||||
@@ -168,6 +189,7 @@ impl<'a, 'b> AvailIter<'a, 'b> {
|
||||
last_index: Wrapping(0),
|
||||
queue_size: 0,
|
||||
next_avail: q_next_avail,
|
||||
iommu_mapping_cb: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,8 +219,13 @@ impl<'a, 'b> Iterator for AvailIter<'a, 'b> {
|
||||
|
||||
self.next_index += Wrapping(1);
|
||||
|
||||
let ret =
|
||||
DescriptorChain::checked_new(self.mem, self.desc_table, self.queue_size, desc_index);
|
||||
let ret = DescriptorChain::checked_new(
|
||||
self.mem,
|
||||
self.desc_table,
|
||||
self.queue_size,
|
||||
desc_index,
|
||||
self.iommu_mapping_cb.clone(),
|
||||
);
|
||||
if ret.is_some() {
|
||||
*self.next_avail += Wrapping(1);
|
||||
}
|
||||
@@ -230,8 +257,10 @@ pub struct Queue {
|
||||
/// Guest physical address of the used ring
|
||||
pub used_ring: GuestAddress,
|
||||
|
||||
next_avail: Wrapping<u16>,
|
||||
next_used: Wrapping<u16>,
|
||||
pub next_avail: Wrapping<u16>,
|
||||
pub next_used: Wrapping<u16>,
|
||||
|
||||
pub iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
@@ -247,6 +276,7 @@ impl Queue {
|
||||
used_ring: GuestAddress(0),
|
||||
next_avail: Wrapping(0),
|
||||
next_used: Wrapping(0),
|
||||
iommu_mapping_cb: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +284,26 @@ impl Queue {
|
||||
self.max_size
|
||||
}
|
||||
|
||||
pub fn enable(&mut self, set: bool) {
|
||||
self.ready = set;
|
||||
|
||||
if set {
|
||||
// Translate address of descriptor table and vrings.
|
||||
if let Some(iommu_mapping_cb) = &self.iommu_mapping_cb {
|
||||
self.desc_table =
|
||||
GuestAddress((iommu_mapping_cb)(self.desc_table.raw_value()).unwrap());
|
||||
self.avail_ring =
|
||||
GuestAddress((iommu_mapping_cb)(self.avail_ring.raw_value()).unwrap());
|
||||
self.used_ring =
|
||||
GuestAddress((iommu_mapping_cb)(self.used_ring.raw_value()).unwrap());
|
||||
}
|
||||
} else {
|
||||
self.desc_table = GuestAddress(0);
|
||||
self.avail_ring = GuestAddress(0);
|
||||
self.used_ring = GuestAddress(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the actual size of the queue, as the driver may not set up a
|
||||
/// queue as big as the device allows.
|
||||
pub fn actual_size(&self) -> u16 {
|
||||
@@ -352,6 +402,7 @@ impl Queue {
|
||||
last_index: Wrapping(last_index),
|
||||
queue_size,
|
||||
next_avail: &mut self.next_avail,
|
||||
iommu_mapping_cb: self.iommu_mapping_cb.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,14 +696,16 @@ pub(crate) mod tests {
|
||||
assert!(vq.end().0 < 0x1000);
|
||||
|
||||
// index >= queue_size
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 16).is_none());
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 16, None).is_none());
|
||||
|
||||
// desc_table address is way off
|
||||
assert!(DescriptorChain::checked_new(m, GuestAddress(0x00ff_ffff_ffff), 16, 0).is_none());
|
||||
assert!(
|
||||
DescriptorChain::checked_new(m, GuestAddress(0x00ff_ffff_ffff), 16, 0, None).is_none()
|
||||
);
|
||||
|
||||
// the addr field of the descriptor is way off
|
||||
vq.dtable[0].addr.set(0x0fff_ffff_ffff);
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0).is_none());
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0, None).is_none());
|
||||
|
||||
// let's create some invalid chains
|
||||
|
||||
@@ -661,7 +714,7 @@ pub(crate) mod tests {
|
||||
vq.dtable[0].addr.set(0x1000);
|
||||
// ...but the length is too large
|
||||
vq.dtable[0].len.set(0xffff_ffff);
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0).is_none());
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0, None).is_none());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -671,7 +724,7 @@ pub(crate) mod tests {
|
||||
//..but the the index of the next descriptor is too large
|
||||
vq.dtable[0].next.set(16);
|
||||
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0).is_none());
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0, None).is_none());
|
||||
}
|
||||
|
||||
// finally, let's test an ok chain
|
||||
@@ -680,7 +733,7 @@ pub(crate) mod tests {
|
||||
vq.dtable[0].next.set(1);
|
||||
vq.dtable[1].set(0x2000, 0x1000, 0, 0);
|
||||
|
||||
let c = DescriptorChain::checked_new(m, vq.start(), 16, 0).unwrap();
|
||||
let c = DescriptorChain::checked_new(m, vq.start(), 16, 0, None).unwrap();
|
||||
|
||||
assert_eq!(c.mem as *const GuestMemoryMmap, m as *const GuestMemoryMmap);
|
||||
assert_eq!(c.desc_table, vq.start());
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::thread;
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
|
||||
VIRTIO_F_VERSION_1,
|
||||
VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::{VirtioInterrupt, VirtioInterruptType};
|
||||
use vm_memory::{Bytes, GuestMemoryMmap};
|
||||
@@ -158,19 +158,27 @@ pub struct Rng {
|
||||
random_file: Option<File>,
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
}
|
||||
|
||||
impl Rng {
|
||||
/// Create a new virtio rng device that gets random data from /dev/urandom.
|
||||
pub fn new(path: &str) -> io::Result<Rng> {
|
||||
pub fn new(path: &str, iommu: bool) -> io::Result<Rng> {
|
||||
let random_file = File::open(path)?;
|
||||
let avail_features = 1u64 << VIRTIO_F_VERSION_1;
|
||||
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
|
||||
|
||||
if iommu {
|
||||
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
||||
}
|
||||
|
||||
Ok(Rng {
|
||||
kill_evt: None,
|
||||
random_file: Some(random_file),
|
||||
avail_features,
|
||||
acked_features: 0u64,
|
||||
queue_evts: None,
|
||||
interrupt_cb: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -261,7 +269,26 @@ impl VirtioDevice for Rng {
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
if let Some(random_file) = self.random_file.take() {
|
||||
// Save the interrupt EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||
|
||||
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
|
||||
for queue_evt in queue_evts.iter() {
|
||||
// Save the queue EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
|
||||
error!("failed to clone queue EventFd: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?);
|
||||
}
|
||||
self.queue_evts = Some(tmp_queue_evts);
|
||||
|
||||
if let Some(file) = self.random_file.as_ref() {
|
||||
let random_file = file.try_clone().map_err(|e| {
|
||||
error!("failed cloning rng source: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?;
|
||||
let mut handler = RngEpollHandler {
|
||||
queues,
|
||||
mem,
|
||||
@@ -277,11 +304,24 @@ impl VirtioDevice for Rng {
|
||||
|
||||
if let Err(e) = worker_result {
|
||||
error!("failed to spawn virtio_rng worker: {}", e);
|
||||
return Err(ActivateError::BadActivate);;
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Err(ActivateError::BadActivate)
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
// Return the interrupt and queue EventFDs
|
||||
Some((
|
||||
self.interrupt_cb.take().unwrap(),
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
248
vm-virtio/src/transport/mmio.rs
Normal file
248
vm-virtio/src/transport/mmio.rs
Normal file
@@ -0,0 +1,248 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use libc::EFD_NONBLOCK;
|
||||
|
||||
use crate::{
|
||||
Queue, VirtioDevice, VirtioInterrupt, VirtioInterruptType, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER,
|
||||
DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK, DEVICE_INIT,
|
||||
INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING,
|
||||
};
|
||||
use devices::{BusDevice, Interrupt};
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
use vmm_sys_util::{errno::Result, eventfd::EventFd};
|
||||
|
||||
const VENDOR_ID: u32 = 0;
|
||||
|
||||
const MMIO_MAGIC_VALUE: u32 = 0x7472_6976;
|
||||
const MMIO_VERSION: u32 = 2;
|
||||
|
||||
/// Implements the
|
||||
/// [MMIO](http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html#x1-1090002)
|
||||
/// transport for virtio devices.
|
||||
///
|
||||
/// This requires 3 points of installation to work with a VM:
|
||||
///
|
||||
/// 1. Mmio reads and writes must be sent to this device at what is referred to here as MMIO base.
|
||||
/// 1. `Mmio::queue_evts` must be installed at `virtio::NOTIFY_REG_OFFSET` offset from the MMIO
|
||||
/// base. Each event in the array must be signaled if the index is written at that offset.
|
||||
/// 1. `Mmio::interrupt_evt` must signal an interrupt that the guest driver is listening to when it
|
||||
/// is written to.
|
||||
///
|
||||
/// Typically one page (4096 bytes) of MMIO address space is sufficient to handle this transport
|
||||
/// and inner virtio device.
|
||||
pub struct MmioDevice {
|
||||
device: Box<dyn VirtioDevice>,
|
||||
device_activated: bool,
|
||||
|
||||
features_select: u32,
|
||||
acked_features_select: u32,
|
||||
queue_select: u32,
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
driver_status: u32,
|
||||
config_generation: u32,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
mem: Option<Arc<RwLock<GuestMemoryMmap>>>,
|
||||
}
|
||||
|
||||
impl MmioDevice {
|
||||
/// Constructs a new MMIO transport for the given virtio device.
|
||||
pub fn new(
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
device: Box<dyn VirtioDevice>,
|
||||
) -> Result<MmioDevice> {
|
||||
let mut queue_evts = Vec::new();
|
||||
for _ in device.queue_max_sizes().iter() {
|
||||
queue_evts.push(EventFd::new(EFD_NONBLOCK)?)
|
||||
}
|
||||
let queues = device
|
||||
.queue_max_sizes()
|
||||
.iter()
|
||||
.map(|&s| Queue::new(s))
|
||||
.collect();
|
||||
Ok(MmioDevice {
|
||||
device,
|
||||
device_activated: false,
|
||||
features_select: 0,
|
||||
acked_features_select: 0,
|
||||
queue_select: 0,
|
||||
interrupt_status: Arc::new(AtomicUsize::new(0)),
|
||||
interrupt_cb: None,
|
||||
driver_status: DEVICE_INIT,
|
||||
config_generation: 0,
|
||||
queues,
|
||||
queue_evts,
|
||||
mem: Some(mem),
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the list of queue events that must be triggered whenever the VM writes to
|
||||
/// `virtio::NOTIFY_REG_OFFSET` past the MMIO base. Each event must be triggered when the
|
||||
/// value being written equals the index of the event in this list.
|
||||
pub fn queue_evts(&self) -> &[EventFd] {
|
||||
self.queue_evts.as_slice()
|
||||
}
|
||||
|
||||
fn is_driver_ready(&self) -> bool {
|
||||
let ready_bits = DEVICE_ACKNOWLEDGE | DEVICE_DRIVER | DEVICE_DRIVER_OK | DEVICE_FEATURES_OK;
|
||||
self.driver_status == ready_bits && self.driver_status & DEVICE_FAILED == 0
|
||||
}
|
||||
|
||||
fn are_queues_valid(&self) -> bool {
|
||||
if let Some(mem) = self.mem.as_ref() {
|
||||
self.queues.iter().all(|q| q.is_valid(&mem.read().unwrap()))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn with_queue<U, F>(&self, d: U, f: F) -> U
|
||||
where
|
||||
F: FnOnce(&Queue) -> U,
|
||||
{
|
||||
match self.queues.get(self.queue_select as usize) {
|
||||
Some(queue) => f(queue),
|
||||
None => d,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_queue_mut<F: FnOnce(&mut Queue)>(&mut self, f: F) -> bool {
|
||||
if let Some(queue) = self.queues.get_mut(self.queue_select as usize) {
|
||||
f(queue);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assign_interrupt(&mut self, interrupt: Box<dyn Interrupt>) {
|
||||
let interrupt_status = self.interrupt_status.clone();
|
||||
let cb = Arc::new(Box::new(
|
||||
move |int_type: &VirtioInterruptType, _queue: Option<&Queue>| {
|
||||
let status = match int_type {
|
||||
VirtioInterruptType::Config => INTERRUPT_STATUS_CONFIG_CHANGED,
|
||||
VirtioInterruptType::Queue => INTERRUPT_STATUS_USED_RING,
|
||||
};
|
||||
interrupt_status.fetch_or(status as usize, Ordering::SeqCst);
|
||||
|
||||
interrupt.deliver()
|
||||
},
|
||||
) as VirtioInterrupt);
|
||||
|
||||
self.interrupt_cb = Some(cb);
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for MmioDevice {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
match offset {
|
||||
0x00..=0xff if data.len() == 4 => {
|
||||
let v = match offset {
|
||||
0x0 => MMIO_MAGIC_VALUE,
|
||||
0x04 => MMIO_VERSION,
|
||||
0x08 => self.device.device_type(),
|
||||
0x0c => VENDOR_ID, // vendor id
|
||||
0x10 => {
|
||||
self.device.features(self.features_select)
|
||||
| if self.features_select == 1 { 0x1 } else { 0x0 }
|
||||
}
|
||||
0x34 => self.with_queue(0, |q| u32::from(q.get_max_size())),
|
||||
0x44 => self.with_queue(0, |q| q.ready as u32),
|
||||
0x60 => self.interrupt_status.load(Ordering::SeqCst) as u32,
|
||||
0x70 => self.driver_status,
|
||||
0xfc => self.config_generation,
|
||||
_ => {
|
||||
warn!("unknown virtio mmio register read: 0x{:x}", offset);
|
||||
return;
|
||||
}
|
||||
};
|
||||
LittleEndian::write_u32(data, v);
|
||||
}
|
||||
0x100..=0xfff => self.device.read_config(offset - 0x100, data),
|
||||
_ => {
|
||||
warn!(
|
||||
"invalid virtio mmio read: 0x{:x}:0x{:x}",
|
||||
offset,
|
||||
data.len()
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
fn hi(v: &mut GuestAddress, x: u32) {
|
||||
*v = (*v & 0xffff_ffff) | (u64::from(x) << 32)
|
||||
}
|
||||
|
||||
fn lo(v: &mut GuestAddress, x: u32) {
|
||||
*v = (*v & !0xffff_ffff) | u64::from(x)
|
||||
}
|
||||
|
||||
let mut mut_q = false;
|
||||
match offset {
|
||||
0x00..=0xff if data.len() == 4 => {
|
||||
let v = LittleEndian::read_u32(data);
|
||||
match offset {
|
||||
0x14 => self.features_select = v,
|
||||
0x20 => self.device.ack_features(self.acked_features_select, v),
|
||||
0x24 => self.acked_features_select = v,
|
||||
0x30 => self.queue_select = v,
|
||||
0x38 => mut_q = self.with_queue_mut(|q| q.size = v as u16),
|
||||
0x44 => mut_q = self.with_queue_mut(|q| q.ready = v == 1),
|
||||
0x64 => {
|
||||
self.interrupt_status
|
||||
.fetch_and(!(v as usize), Ordering::SeqCst);
|
||||
}
|
||||
0x70 => self.driver_status = v,
|
||||
0x80 => mut_q = self.with_queue_mut(|q| lo(&mut q.desc_table, v)),
|
||||
0x84 => mut_q = self.with_queue_mut(|q| hi(&mut q.desc_table, v)),
|
||||
0x90 => mut_q = self.with_queue_mut(|q| lo(&mut q.avail_ring, v)),
|
||||
0x94 => mut_q = self.with_queue_mut(|q| hi(&mut q.avail_ring, v)),
|
||||
0xa0 => mut_q = self.with_queue_mut(|q| lo(&mut q.used_ring, v)),
|
||||
0xa4 => mut_q = self.with_queue_mut(|q| hi(&mut q.used_ring, v)),
|
||||
_ => {
|
||||
warn!("unknown virtio mmio register write: 0x{:x}", offset);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
0x100..=0xfff => return self.device.write_config(offset - 0x100, data),
|
||||
_ => {
|
||||
warn!(
|
||||
"invalid virtio mmio write: 0x{:x}:0x{:x}",
|
||||
offset,
|
||||
data.len()
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if self.device_activated && mut_q {
|
||||
warn!("virtio queue was changed after device was activated");
|
||||
}
|
||||
|
||||
if !self.device_activated && self.is_driver_ready() && self.are_queues_valid() {
|
||||
if let Some(interrupt_cb) = self.interrupt_cb.take() {
|
||||
if self.mem.is_some() {
|
||||
let mem = self.mem.as_ref().unwrap().clone();
|
||||
self.device
|
||||
.activate(
|
||||
mem,
|
||||
interrupt_cb,
|
||||
self.queues.clone(),
|
||||
self.queue_evts.split_off(0),
|
||||
)
|
||||
.expect("Failed to activate device");
|
||||
self.device_activated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,18 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(feature = "pci_support")]
|
||||
mod pci_common_config;
|
||||
#[cfg(feature = "pci_support")]
|
||||
mod pci_device;
|
||||
|
||||
#[cfg(feature = "pci_support")]
|
||||
pub use pci_common_config::VirtioPciCommonConfig;
|
||||
#[cfg(feature = "pci_support")]
|
||||
pub use pci_device::VirtioPciDevice;
|
||||
|
||||
#[cfg(feature = "mmio_support")]
|
||||
mod mmio;
|
||||
#[cfg(feature = "mmio_support")]
|
||||
pub use mmio::MmioDevice;
|
||||
#[cfg(feature = "mmio_support")]
|
||||
pub const NOTIFY_REG_OFFSET: u32 = 0x50;
|
||||
|
||||
@@ -149,7 +149,7 @@ impl VirtioPciCommonConfig {
|
||||
0x16 => self.queue_select = value,
|
||||
0x18 => self.with_queue_mut(queues, |q| q.size = value),
|
||||
0x1a => self.with_queue_mut(queues, |q| q.vector = value),
|
||||
0x1c => self.with_queue_mut(queues, |q| q.ready = value == 1),
|
||||
0x1c => self.with_queue_mut(queues, |q| q.enable(value == 1)),
|
||||
_ => {
|
||||
warn!("invalid virtio register word write: 0x{:x}", offset);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
extern crate devices;
|
||||
#[cfg(feature = "pci_support")]
|
||||
extern crate pci;
|
||||
extern crate vm_allocator;
|
||||
extern crate vm_memory;
|
||||
@@ -30,8 +31,8 @@ use vmm_sys_util::{errno::Result, eventfd::EventFd};
|
||||
use super::VirtioPciCommonConfig;
|
||||
use crate::{
|
||||
Queue, VirtioDevice, VirtioDeviceType, VirtioInterrupt, VirtioInterruptType,
|
||||
DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK,
|
||||
DEVICE_INIT, INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING,
|
||||
VirtioIommuRemapping, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, DEVICE_FAILED,
|
||||
DEVICE_FEATURES_OK, DEVICE_INIT, INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING,
|
||||
};
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
@@ -238,6 +239,9 @@ pub struct VirtioPciDevice {
|
||||
|
||||
// Setting PCI BAR
|
||||
settings_bar: u8,
|
||||
|
||||
// Whether to use 64-bit bar location or 32-bit
|
||||
use_64bit_bar: bool,
|
||||
}
|
||||
|
||||
impl VirtioPciDevice {
|
||||
@@ -246,6 +250,7 @@ impl VirtioPciDevice {
|
||||
memory: Arc<RwLock<GuestMemoryMmap>>,
|
||||
device: Box<dyn VirtioDevice>,
|
||||
msix_num: u16,
|
||||
iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
|
||||
) -> Result<Self> {
|
||||
let mut queue_evts = Vec::new();
|
||||
for _ in device.queue_max_sizes().iter() {
|
||||
@@ -254,7 +259,11 @@ impl VirtioPciDevice {
|
||||
let queues = device
|
||||
.queue_max_sizes()
|
||||
.iter()
|
||||
.map(|&s| Queue::new(s))
|
||||
.map(|&s| {
|
||||
let mut queue = Queue::new(s);
|
||||
queue.iommu_mapping_cb = iommu_mapping_cb.clone();
|
||||
queue
|
||||
})
|
||||
.collect();
|
||||
|
||||
let pci_device_id = VIRTIO_PCI_DEVICE_ID_BASE + device.device_type() as u16;
|
||||
@@ -267,15 +276,22 @@ impl VirtioPciDevice {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
// All device types *except* virtio block devices should be allocated a 64-bit bar
|
||||
// The block devices should be given a 32-bit BAR so that they are easily accessible
|
||||
// to firmware without requiring excessive identity mapping.
|
||||
let mut use_64bit_bar = true;
|
||||
let (class, subclass) = match VirtioDeviceType::from(device.device_type()) {
|
||||
VirtioDeviceType::TYPE_NET => (
|
||||
PciClassCode::NetworkController,
|
||||
&PciNetworkControllerSubclass::EthernetController as &dyn PciSubclass,
|
||||
),
|
||||
VirtioDeviceType::TYPE_BLOCK => (
|
||||
PciClassCode::MassStorage,
|
||||
&PciMassStorageSubclass::MassStorage as &dyn PciSubclass,
|
||||
),
|
||||
VirtioDeviceType::TYPE_BLOCK => {
|
||||
use_64bit_bar = false;
|
||||
(
|
||||
PciClassCode::MassStorage,
|
||||
&PciMassStorageSubclass::MassStorage as &dyn PciSubclass,
|
||||
)
|
||||
}
|
||||
_ => (
|
||||
PciClassCode::Other,
|
||||
&PciVirtioSubclass::NonTransitionalBase as &dyn PciSubclass,
|
||||
@@ -314,6 +330,7 @@ impl VirtioPciDevice {
|
||||
queue_evts,
|
||||
memory: Some(memory),
|
||||
settings_bar: 0,
|
||||
use_64bit_bar,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -530,24 +547,32 @@ impl PciDevice for VirtioPciDevice {
|
||||
|
||||
// Allocate the virtio-pci capability BAR.
|
||||
// See http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html#x1-740004
|
||||
let virtio_pci_bar_addr = allocator
|
||||
.allocate_mmio_addresses(None, CAPABILITY_BAR_SIZE, None)
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(CAPABILITY_BAR_SIZE))?;
|
||||
let (virtio_pci_bar_addr, region_type) = if self.use_64bit_bar {
|
||||
let region_type = PciBarRegionType::Memory64BitRegion;
|
||||
let addr = allocator
|
||||
.allocate_mmio_addresses(None, CAPABILITY_BAR_SIZE, None)
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(CAPABILITY_BAR_SIZE))?;
|
||||
ranges.push((addr, CAPABILITY_BAR_SIZE, region_type));
|
||||
(addr, region_type)
|
||||
} else {
|
||||
let region_type = PciBarRegionType::Memory32BitRegion;
|
||||
let addr = allocator
|
||||
.allocate_mmio_hole_addresses(None, CAPABILITY_BAR_SIZE, None)
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(CAPABILITY_BAR_SIZE))?;
|
||||
ranges.push((addr, CAPABILITY_BAR_SIZE, region_type));
|
||||
(addr, region_type)
|
||||
};
|
||||
|
||||
let config = PciBarConfiguration::default()
|
||||
.set_register_index(0)
|
||||
.set_address(virtio_pci_bar_addr.raw_value())
|
||||
.set_size(CAPABILITY_BAR_SIZE);
|
||||
.set_size(CAPABILITY_BAR_SIZE)
|
||||
.set_region_type(region_type);
|
||||
let virtio_pci_bar =
|
||||
self.configuration.add_pci_bar(&config).map_err(|e| {
|
||||
PciDeviceError::IoRegistrationFailed(virtio_pci_bar_addr.raw_value(), e)
|
||||
})? as u8;
|
||||
|
||||
ranges.push((
|
||||
virtio_pci_bar_addr,
|
||||
CAPABILITY_BAR_SIZE,
|
||||
PciBarRegionType::Memory64BitRegion,
|
||||
));
|
||||
|
||||
// Once the BARs are allocated, the capabilities can be added to the PCI configuration.
|
||||
self.add_pci_capabilities(virtio_pci_bar)?;
|
||||
|
||||
|
||||
290
vm-virtio/src/vhost_user/blk.rs
Normal file
290
vm-virtio/src/vhost_user/blk.rs
Normal file
@@ -0,0 +1,290 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use libc;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std::cmp;
|
||||
use std::io::Write;
|
||||
use std::ptr::null;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
use std::vec::Vec;
|
||||
|
||||
use crate::VirtioInterrupt;
|
||||
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::super::{ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType};
|
||||
use super::handler::*;
|
||||
use super::vu_common_ctrl::*;
|
||||
use super::{Error, Result};
|
||||
use std::mem;
|
||||
use vhost_rs::vhost_user::message::VhostUserConfigFlags;
|
||||
use vhost_rs::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
|
||||
use vhost_rs::vhost_user::{Master, VhostUserMaster, VhostUserMasterReqHandler};
|
||||
use vhost_rs::VhostBackend;
|
||||
use virtio_bindings::bindings::virtio_blk::*;
|
||||
|
||||
macro_rules! offset_of {
|
||||
($ty:ty, $field:ident) => {
|
||||
unsafe { &(*(null() as *const $ty)).$field as *const _ as usize }
|
||||
};
|
||||
}
|
||||
|
||||
struct SlaveReqHandler {}
|
||||
impl VhostUserMasterReqHandler for SlaveReqHandler {}
|
||||
|
||||
pub struct Blk {
|
||||
vhost_user_blk: Master,
|
||||
kill_evt: Option<EventFd>,
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
config_space: Vec<u8>,
|
||||
queue_sizes: Vec<u16>,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
}
|
||||
|
||||
impl Blk {
|
||||
/// Create a new vhost-user-blk device
|
||||
pub fn new(wce: bool, vu_cfg: VhostUserConfig) -> Result<Blk> {
|
||||
let mut vhost_user_blk = Master::connect(&vu_cfg.sock, vu_cfg.num_queues as u64)
|
||||
.map_err(Error::VhostUserCreateMaster)?;
|
||||
|
||||
// Filling device and vring features VMM supports.
|
||||
let mut avail_features = 1 << VIRTIO_BLK_F_SEG_MAX
|
||||
| 1 << VIRTIO_BLK_F_RO
|
||||
| 1 << VIRTIO_BLK_F_BLK_SIZE
|
||||
| 1 << VIRTIO_BLK_F_FLUSH
|
||||
| 1 << VIRTIO_BLK_F_TOPOLOGY
|
||||
| 1 << VIRTIO_F_VERSION_1
|
||||
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
|
||||
if wce {
|
||||
avail_features |= 1 << VIRTIO_BLK_F_CONFIG_WCE;
|
||||
}
|
||||
|
||||
// Set vhost-user owner.
|
||||
vhost_user_blk
|
||||
.set_owner()
|
||||
.map_err(Error::VhostUserSetOwner)?;
|
||||
|
||||
// Get features from backend, do negotiation to get a feature collection which
|
||||
// both VMM and backend support.
|
||||
let backend_features = vhost_user_blk
|
||||
.get_features()
|
||||
.map_err(Error::VhostUserGetFeatures)?;
|
||||
avail_features &= backend_features;
|
||||
// Set features back is required by the vhost crate mechanism, since the
|
||||
// later vhost call will check if features is filled in master before execution.
|
||||
vhost_user_blk
|
||||
.set_features(avail_features)
|
||||
.map_err(Error::VhostUserSetFeatures)?;
|
||||
|
||||
// Identify if protocol features are supported by the slave.
|
||||
let mut acked_features = 0;
|
||||
if avail_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() != 0 {
|
||||
acked_features |= VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
|
||||
let mut protocol_features = vhost_user_blk
|
||||
.get_protocol_features()
|
||||
.map_err(Error::VhostUserGetProtocolFeatures)?;
|
||||
protocol_features |= VhostUserProtocolFeatures::MQ;
|
||||
protocol_features &= !VhostUserProtocolFeatures::INFLIGHT_SHMFD;
|
||||
vhost_user_blk
|
||||
.set_protocol_features(protocol_features)
|
||||
.map_err(Error::VhostUserSetProtocolFeatures)?;
|
||||
}
|
||||
|
||||
let config_len = mem::size_of::<virtio_blk_config>();
|
||||
let config_space: Vec<u8> = vec![0u8; config_len as usize];
|
||||
|
||||
let (_, mut config_space) = vhost_user_blk
|
||||
.get_config(
|
||||
0,
|
||||
config_len as u32,
|
||||
VhostUserConfigFlags::WRITABLE,
|
||||
config_space.as_slice(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let queue_num_offset = offset_of!(virtio_blk_config, num_queues);
|
||||
// Only set num_queues value(u16).
|
||||
let num_queues_slice = (vu_cfg.num_queues as u16).to_le_bytes();
|
||||
config_space[queue_num_offset..queue_num_offset + mem::size_of::<u16>()]
|
||||
.copy_from_slice(&num_queues_slice);
|
||||
|
||||
Ok(Blk {
|
||||
vhost_user_blk,
|
||||
kill_evt: None,
|
||||
avail_features,
|
||||
acked_features,
|
||||
config_space,
|
||||
queue_sizes: vec![vu_cfg.queue_size; vu_cfg.num_queues],
|
||||
queue_evts: None,
|
||||
interrupt_cb: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Blk {
|
||||
fn drop(&mut self) {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
if let Err(e) = kill_evt.write(1) {
|
||||
error!("failed to kill vhost-user-blk: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioDevice for Blk {
|
||||
fn device_type(&self) -> u32 {
|
||||
VirtioDeviceType::TYPE_BLOCK as u32
|
||||
}
|
||||
|
||||
fn queue_max_sizes(&self) -> &[u16] {
|
||||
&self.queue_sizes
|
||||
}
|
||||
|
||||
fn features(&self, page: u32) -> u32 {
|
||||
match page {
|
||||
0 => self.avail_features as u32,
|
||||
1 => (self.avail_features >> 32) as u32,
|
||||
_ => {
|
||||
warn!("Received request for unknown features page: {}", page);
|
||||
0u32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ack_features(&mut self, page: u32, value: u32) {
|
||||
let mut v = match page {
|
||||
0 => u64::from(value),
|
||||
1 => u64::from(value) << 32,
|
||||
_ => {
|
||||
warn!("Cannot acknowledge unknown features page: {}", page);
|
||||
0u64
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the guest is ACK'ing a feature that we didn't claim to have.
|
||||
let unrequested_features = v & !self.avail_features;
|
||||
if unrequested_features != 0 {
|
||||
warn!("Received acknowledge request for unknown feature: {:x}", v);
|
||||
// Don't count these features as acked.
|
||||
v &= !unrequested_features;
|
||||
}
|
||||
self.acked_features |= v;
|
||||
}
|
||||
|
||||
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
|
||||
let config_len = self.config_space.len() as u64;
|
||||
if offset >= config_len {
|
||||
error!("Failed to read config space");
|
||||
return;
|
||||
}
|
||||
if let Some(end) = offset.checked_add(data.len() as u64) {
|
||||
// This write can't fail, offset and end are checked against config_len.
|
||||
data.write_all(&self.config_space[offset as usize..cmp::min(end, config_len) as usize])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn write_config(&mut self, offset: u64, data: &[u8]) {
|
||||
let data_len = data.len() as u64;
|
||||
let config_len = self.config_space.len() as u64;
|
||||
if offset + data_len > config_len {
|
||||
error!("Failed to write config space");
|
||||
return;
|
||||
}
|
||||
// In fact, write_config() only handle wce value in vhost-user-blk.
|
||||
// so, we can only set wce value here.
|
||||
if self.config_space[offset as usize] == data[0] {
|
||||
return;
|
||||
}
|
||||
self.vhost_user_blk
|
||||
.set_config(offset as u32, VhostUserConfigFlags::WRITABLE, data)
|
||||
.expect("Failed to set config");
|
||||
self.config_space[offset as usize] = data[0];
|
||||
}
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
let (self_kill_evt, kill_evt) =
|
||||
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed creating kill EventFd pair: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
// Save the interrupt EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||
|
||||
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
|
||||
for queue_evt in queue_evts.iter() {
|
||||
// Save the queue EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
|
||||
error!("failed to clone queue EventFd: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?);
|
||||
}
|
||||
self.queue_evts = Some(tmp_queue_evts);
|
||||
|
||||
let vu_interrupt_list = setup_vhost_user(
|
||||
&mut self.vhost_user_blk,
|
||||
&mem.read().unwrap(),
|
||||
queues,
|
||||
queue_evts,
|
||||
self.acked_features,
|
||||
)
|
||||
.map_err(ActivateError::VhostUserBlkSetup)?;
|
||||
|
||||
let mut handler = VhostUserEpollHandler::<SlaveReqHandler>::new(VhostUserEpollConfig {
|
||||
interrupt_cb,
|
||||
kill_evt,
|
||||
vu_interrupt_list,
|
||||
slave_req_handler: None,
|
||||
});
|
||||
|
||||
let handler_result = thread::Builder::new()
|
||||
.name("vhost_user_blk".to_string())
|
||||
.spawn(move || {
|
||||
if let Err(e) = handler.run() {
|
||||
error!("net worker thread exited with error {:?}!", e);
|
||||
}
|
||||
});
|
||||
if let Err(e) = handler_result {
|
||||
error!("vhost-user blk thread create failed with error {:?}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
|
||||
if let Err(e) = reset_vhost_user(&mut self.vhost_user_blk, self.queue_sizes.len()) {
|
||||
error!("Failed to reset vhost-user daemon: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
// Return the interrupt and queue EventFDs
|
||||
Some((
|
||||
self.interrupt_cb.take().unwrap(),
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::vu_common_ctrl::setup_vhost_user;
|
||||
use super::vu_common_ctrl::{reset_vhost_user, setup_vhost_user};
|
||||
use super::{Error, Result};
|
||||
use crate::vhost_user::handler::{VhostUserEpollConfig, VhostUserEpollHandler};
|
||||
use crate::{
|
||||
@@ -118,6 +118,8 @@ pub struct Fs {
|
||||
kill_evt: Option<EventFd>,
|
||||
cache: Option<(VirtioSharedMemoryList, u64)>,
|
||||
slave_req_support: bool,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
}
|
||||
|
||||
impl Fs {
|
||||
@@ -199,6 +201,8 @@ impl Fs {
|
||||
kill_evt: None,
|
||||
cache,
|
||||
slave_req_support,
|
||||
queue_evts: None,
|
||||
interrupt_cb: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -305,6 +309,21 @@ impl VirtioDevice for Fs {
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
// Save the interrupt EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||
|
||||
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
|
||||
for queue_evt in queue_evts.iter() {
|
||||
// Save the queue EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
|
||||
error!("failed to clone queue EventFd: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?);
|
||||
}
|
||||
self.queue_evts = Some(tmp_queue_evts);
|
||||
|
||||
let vu_call_evt_queue_list = setup_vhost_user(
|
||||
&mut self.vu,
|
||||
&mem.read().unwrap(),
|
||||
@@ -361,6 +380,24 @@ impl VirtioDevice for Fs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
|
||||
if let Err(e) = reset_vhost_user(&mut self.vu, self.queue_sizes.len()) {
|
||||
error!("Failed to reset vhost-user daemon: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
// Return the interrupt and queue EventFDs
|
||||
Some((
|
||||
self.interrupt_cb.take().unwrap(),
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
fn get_shm_regions(&self) -> Option<VirtioSharedMemoryList> {
|
||||
if let Some(cache) = self.cache.clone() {
|
||||
Some(cache.0)
|
||||
|
||||
@@ -12,11 +12,13 @@ use std::io;
|
||||
use vhost_rs::Error as VhostError;
|
||||
use vm_memory::Error as MmapError;
|
||||
|
||||
pub mod blk;
|
||||
pub mod fs;
|
||||
mod handler;
|
||||
pub mod net;
|
||||
pub mod vu_common_ctrl;
|
||||
|
||||
pub use self::blk::Blk;
|
||||
pub use self::fs::*;
|
||||
pub use self::net::Net;
|
||||
pub use self::vu_common_ctrl::VhostUserConfig;
|
||||
@@ -59,6 +61,8 @@ pub enum Error {
|
||||
VhostUserProtocolNotSupport,
|
||||
/// Set owner failed.
|
||||
VhostUserSetOwner(VhostError),
|
||||
/// Reset owner failed.
|
||||
VhostUserResetOwner(VhostError),
|
||||
/// Set features failed.
|
||||
VhostUserSetFeatures(VhostError),
|
||||
/// Set protocol features failed.
|
||||
@@ -91,5 +95,7 @@ pub enum Error {
|
||||
VhostUserSetSlaveRequestFd(vhost_rs::Error),
|
||||
/// Invalid used address.
|
||||
UsedAddress,
|
||||
/// Invalid features provided from vhost-user backend
|
||||
InvalidFeatures,
|
||||
}
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
@@ -19,33 +19,33 @@ use super::super::{ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDev
|
||||
use super::handler::*;
|
||||
use super::vu_common_ctrl::*;
|
||||
use super::{Error, Result};
|
||||
use vhost_rs::vhost_user::message::VhostUserVirtioFeatures;
|
||||
use vhost_rs::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
|
||||
use vhost_rs::vhost_user::{Master, VhostUserMaster, VhostUserMasterReqHandler};
|
||||
use vhost_rs::VhostBackend;
|
||||
use virtio_bindings::virtio_net;
|
||||
use virtio_bindings::virtio_ring;
|
||||
use virtio_bindings::bindings::virtio_net;
|
||||
use virtio_bindings::bindings::virtio_ring;
|
||||
|
||||
struct SlaveReqHandler {}
|
||||
impl VhostUserMasterReqHandler for SlaveReqHandler {}
|
||||
|
||||
pub struct Net {
|
||||
vhost_user_net: Master,
|
||||
kill_evt: EventFd,
|
||||
kill_evt: Option<EventFd>,
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
backend_features: u64,
|
||||
config_space: Vec<u8>,
|
||||
queue_sizes: Vec<u16>,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
}
|
||||
|
||||
impl<'a> Net {
|
||||
impl Net {
|
||||
/// Create a new vhost-user-net device
|
||||
pub fn new(mac_addr: MacAddr, vu_cfg: VhostUserConfig<'a>) -> Result<Net> {
|
||||
let mut vhost_user_net = Master::connect(vu_cfg.sock, vu_cfg.num_queues as u64)
|
||||
pub fn new(mac_addr: MacAddr, vu_cfg: VhostUserConfig) -> Result<Net> {
|
||||
let mut vhost_user_net = Master::connect(&vu_cfg.sock, vu_cfg.num_queues as u64)
|
||||
.map_err(Error::VhostUserCreateMaster)?;
|
||||
|
||||
let kill_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?;
|
||||
|
||||
// Filling device and vring features VMM supports.
|
||||
let mut avail_features = 1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM
|
||||
| 1 << virtio_net::VIRTIO_NET_F_CSUM
|
||||
@@ -82,9 +82,13 @@ impl<'a> Net {
|
||||
let mut acked_features = 0;
|
||||
if avail_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() != 0 {
|
||||
acked_features |= VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
vhost_user_net
|
||||
let mut protocol_features = vhost_user_net
|
||||
.get_protocol_features()
|
||||
.map_err(Error::VhostUserGetProtocolFeatures)?;
|
||||
protocol_features &= VhostUserProtocolFeatures::MQ;
|
||||
vhost_user_net
|
||||
.set_protocol_features(protocol_features)
|
||||
.map_err(Error::VhostUserSetProtocolFeatures)?;
|
||||
} else {
|
||||
return Err(Error::VhostUserProtocolNotSupport);
|
||||
}
|
||||
@@ -104,20 +108,24 @@ impl<'a> Net {
|
||||
|
||||
Ok(Net {
|
||||
vhost_user_net,
|
||||
kill_evt,
|
||||
kill_evt: None,
|
||||
avail_features,
|
||||
acked_features,
|
||||
backend_features,
|
||||
config_space,
|
||||
queue_sizes: vec![vu_cfg.queue_size; vu_cfg.num_queues],
|
||||
queue_evts: None,
|
||||
interrupt_cb: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Net {
|
||||
fn drop(&mut self) {
|
||||
if let Err(_e) = self.kill_evt.write(1) {
|
||||
error!("failed to kill vhost-user-net with error {}", _e);
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
if let Err(e) = kill_evt.write(1) {
|
||||
error!("failed to kill vhost-user-net: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,10 +201,30 @@ impl VirtioDevice for Net {
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
let handler_kill_evt = self
|
||||
.kill_evt
|
||||
.try_clone()
|
||||
.map_err(|_| ActivateError::CloneKillEventFd)?;
|
||||
let (self_kill_evt, kill_evt) =
|
||||
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed creating kill EventFd pair: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
// Save the interrupt EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||
|
||||
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
|
||||
for queue_evt in queue_evts.iter() {
|
||||
// Save the queue EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
|
||||
error!("failed to clone queue EventFd: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?);
|
||||
}
|
||||
self.queue_evts = Some(tmp_queue_evts);
|
||||
|
||||
let vu_interrupt_list = setup_vhost_user(
|
||||
&mut self.vhost_user_net,
|
||||
@@ -209,7 +237,7 @@ impl VirtioDevice for Net {
|
||||
|
||||
let mut handler = VhostUserEpollHandler::<SlaveReqHandler>::new(VhostUserEpollConfig {
|
||||
interrupt_cb,
|
||||
kill_evt: handler_kill_evt,
|
||||
kill_evt,
|
||||
vu_interrupt_list,
|
||||
slave_req_handler: None,
|
||||
});
|
||||
@@ -226,4 +254,22 @@ impl VirtioDevice for Net {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
|
||||
if let Err(e) = reset_vhost_user(&mut self.vhost_user_net, self.queue_sizes.len()) {
|
||||
error!("Failed to reset vhost-user daemon: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
// Return the interrupt and queue EventFDs
|
||||
Some((
|
||||
self.interrupt_cb.take().unwrap(),
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ use super::{Error, Result};
|
||||
use vhost_rs::vhost_user::{Master, VhostUserMaster};
|
||||
use vhost_rs::{VhostBackend, VhostUserMemoryRegionInfo, VringConfigData};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct VhostUserConfig<'a> {
|
||||
pub sock: &'a str,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VhostUserConfig {
|
||||
pub sock: String,
|
||||
pub num_queues: usize,
|
||||
pub queue_size: u16,
|
||||
}
|
||||
@@ -54,7 +54,7 @@ pub fn setup_vhost_user_vring(
|
||||
let mut vu_interrupt_list = Vec::new();
|
||||
|
||||
for (queue_index, queue) in queues.into_iter().enumerate() {
|
||||
vu.set_vring_num(queue_index, queue.get_max_size())
|
||||
vu.set_vring_num(queue_index, queue.actual_size())
|
||||
.map_err(Error::VhostUserSetVringNum)?;
|
||||
|
||||
let config_data = VringConfigData {
|
||||
@@ -106,3 +106,18 @@ pub fn setup_vhost_user(
|
||||
|
||||
setup_vhost_user_vring(vu, mem, queues, queue_evts)
|
||||
}
|
||||
|
||||
pub fn reset_vhost_user(vu: &mut Master, num_queues: usize) -> Result<()> {
|
||||
for queue_index in 0..num_queues {
|
||||
// Disable the vrings.
|
||||
vu.set_vring_enable(queue_index, false)
|
||||
.map_err(Error::VhostUserSetVringEnable)?;
|
||||
|
||||
// Stop the vrings.
|
||||
vu.get_vring_base(queue_index)
|
||||
.map_err(Error::VhostUserSetFeatures)?;
|
||||
}
|
||||
|
||||
// Reset the owner.
|
||||
vu.reset_owner().map_err(Error::VhostUserResetOwner)
|
||||
}
|
||||
|
||||
1186
vm-virtio/src/vsock/csm/connection.rs
Normal file
1186
vm-virtio/src/vsock/csm/connection.rs
Normal file
File diff suppressed because it is too large
Load Diff
129
vm-virtio/src/vsock/csm/mod.rs
Normal file
129
vm-virtio/src/vsock/csm/mod.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
/// This module implements our vsock connection state machine. The heavy lifting is done by
|
||||
/// `connection::VsockConnection`, while this file only defines some constants and helper structs.
|
||||
///
|
||||
mod connection;
|
||||
mod txbuf;
|
||||
|
||||
pub use connection::VsockConnection;
|
||||
|
||||
pub mod defs {
|
||||
/// Vsock connection TX buffer capacity.
|
||||
pub const CONN_TX_BUF_SIZE: usize = 64 * 1024;
|
||||
|
||||
/// After the guest thinks it has filled our TX buffer up to this limit (in bytes), we'll send
|
||||
/// them a credit update packet, to let them know we can handle more.
|
||||
pub const CONN_CREDIT_UPDATE_THRESHOLD: usize = CONN_TX_BUF_SIZE - 4 * 4 * 1024;
|
||||
|
||||
/// Connection request timeout, in millis.
|
||||
pub const CONN_REQUEST_TIMEOUT_MS: u64 = 2000;
|
||||
|
||||
/// Connection graceful shutdown timeout, in millis.
|
||||
pub const CONN_SHUTDOWN_TIMEOUT_MS: u64 = 2000;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Attempted to push data to a full TX buffer.
|
||||
TxBufFull,
|
||||
/// An I/O error occurred, when attempting to flush the connection TX buffer.
|
||||
TxBufFlush(std::io::Error),
|
||||
/// An I/O error occurred, when attempting to write data to the host-side stream.
|
||||
StreamWrite(std::io::Error),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// A vsock connection state.
|
||||
///
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ConnState {
|
||||
/// The connection has been initiated by the host end, but is yet to be confirmed by the guest.
|
||||
LocalInit,
|
||||
/// The connection has been initiated by the guest, but we are yet to confirm it, by sending
|
||||
/// a response packet (VSOCK_OP_RESPONSE).
|
||||
PeerInit,
|
||||
/// The connection handshake has been performed successfully, and data can now be exchanged.
|
||||
Established,
|
||||
/// The host (AF_UNIX) socket was closed.
|
||||
LocalClosed,
|
||||
/// A VSOCK_OP_SHUTDOWN packet was received from the guest. The tuple represents the guest R/W
|
||||
/// indication: (will_not_recv_anymore_data, will_not_send_anymore_data).
|
||||
PeerClosed(bool, bool),
|
||||
/// The connection is scheduled to be forcefully terminated as soon as possible.
|
||||
Killed,
|
||||
}
|
||||
|
||||
/// An RX indication, used by `VsockConnection` to schedule future `recv_pkt()` responses.
|
||||
/// For instance, after being notified that there is available data to be read from the host stream
|
||||
/// (via `notify()`), the connection will store a `PendingRx::Rw` to be later inspected by
|
||||
/// `recv_pkt()`.
|
||||
///
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum PendingRx {
|
||||
/// We need to yield a connection request packet (VSOCK_OP_REQUEST).
|
||||
Request = 0,
|
||||
/// We need to yield a connection response packet (VSOCK_OP_RESPONSE).
|
||||
Response = 1,
|
||||
/// We need to yield a forceful connection termination packet (VSOCK_OP_RST).
|
||||
Rst = 2,
|
||||
/// We need to yield a data packet (VSOCK_OP_RW), by reading from the AF_UNIX socket.
|
||||
Rw = 3,
|
||||
/// We need to yield a credit update packet (VSOCK_OP_CREDIT_UPDATE).
|
||||
CreditUpdate = 4,
|
||||
}
|
||||
impl PendingRx {
|
||||
/// Transform the enum value into a bitmask, that can be used for set operations.
|
||||
///
|
||||
fn into_mask(self) -> u16 {
|
||||
1u16 << (self as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of RX indications (`PendingRx` items).
|
||||
///
|
||||
struct PendingRxSet {
|
||||
data: u16,
|
||||
}
|
||||
|
||||
impl PendingRxSet {
|
||||
/// Insert an item into the set.
|
||||
///
|
||||
fn insert(&mut self, it: PendingRx) {
|
||||
self.data |= it.into_mask();
|
||||
}
|
||||
|
||||
/// Remove an item from the set and return:
|
||||
/// - true, if the item was in the set; or
|
||||
/// - false, if the item wasn't in the set.
|
||||
///
|
||||
fn remove(&mut self, it: PendingRx) -> bool {
|
||||
let ret = self.contains(it);
|
||||
self.data &= !it.into_mask();
|
||||
ret
|
||||
}
|
||||
|
||||
/// Check if an item is present in this set.
|
||||
///
|
||||
fn contains(&self, it: PendingRx) -> bool {
|
||||
self.data & it.into_mask() != 0
|
||||
}
|
||||
|
||||
/// Check if the set is empty.
|
||||
///
|
||||
fn is_empty(&self) -> bool {
|
||||
self.data == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a set containing only one item.
|
||||
///
|
||||
impl From<PendingRx> for PendingRxSet {
|
||||
fn from(it: PendingRx) -> Self {
|
||||
Self {
|
||||
data: it.into_mask(),
|
||||
}
|
||||
}
|
||||
}
|
||||
279
vm-virtio/src/vsock/csm/txbuf.rs
Normal file
279
vm-virtio/src/vsock/csm/txbuf.rs
Normal file
@@ -0,0 +1,279 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::io::Write;
|
||||
use std::mem;
|
||||
use std::num::Wrapping;
|
||||
|
||||
use super::defs;
|
||||
use super::{Error, Result};
|
||||
|
||||
/// A simple ring-buffer implementation, used by vsock connections to buffer TX (guest -> host)
|
||||
/// data. Memory for this buffer is allocated lazily, since buffering will only be needed when
|
||||
/// the host can't read fast enough.
|
||||
///
|
||||
pub struct TxBuf {
|
||||
/// The actual u8 buffer - only allocated after the first push.
|
||||
data: Option<Box<[u8; Self::SIZE]>>,
|
||||
/// Ring-buffer head offset - where new data is pushed to.
|
||||
head: Wrapping<u32>,
|
||||
/// Ring-buffer tail offset - where data is flushed from.
|
||||
tail: Wrapping<u32>,
|
||||
}
|
||||
|
||||
impl TxBuf {
|
||||
/// Total buffer size, in bytes.
|
||||
///
|
||||
const SIZE: usize = defs::CONN_TX_BUF_SIZE;
|
||||
|
||||
/// Ring-buffer constructor.
|
||||
///
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: None,
|
||||
head: Wrapping(0),
|
||||
tail: Wrapping(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the used length of this buffer - number of bytes that have been pushed in, but not
|
||||
/// yet flushed out.
|
||||
///
|
||||
pub fn len(&self) -> usize {
|
||||
(self.head - self.tail).0 as usize
|
||||
}
|
||||
|
||||
/// Push a byte slice onto the ring-buffer.
|
||||
///
|
||||
/// Either the entire source slice will be pushed to the ring-buffer, or none of it, if
|
||||
/// there isn't enough room, in which case `Err(Error::TxBufFull)` is returned.
|
||||
///
|
||||
pub fn push(&mut self, src: &[u8]) -> Result<()> {
|
||||
// Error out if there's no room to push the entire slice.
|
||||
if self.len() + src.len() > Self::SIZE {
|
||||
return Err(Error::TxBufFull);
|
||||
}
|
||||
|
||||
// We're using a closure here to return the boxed slice, instead of a value (i.e.
|
||||
// `get_or_insert_with()` instead of `get_or_insert()`), because we only want the box
|
||||
// created when `self.data` is None. If we were to use `get_or_insert(box)`, the box
|
||||
// argument would always get evaluated (which implies a heap allocation), even though
|
||||
// it would later be discarded (when `self.data.is_some()`). Apparently, clippy fails
|
||||
// to see this, and insists on issuing some warning.
|
||||
#[allow(clippy::redundant_closure)]
|
||||
let data = self.data.get_or_insert_with(||
|
||||
// Using uninitialized memory here is quite safe, since we never read from any
|
||||
// area of the buffer before writing to it. First we push, then we flush only
|
||||
// what had been prviously pushed.
|
||||
Box::new(unsafe {mem::uninitialized::<[u8; Self::SIZE]>()}));
|
||||
|
||||
// Buffer head, as an offset into the data slice.
|
||||
let head_ofs = self.head.0 as usize % Self::SIZE;
|
||||
|
||||
// Pushing a slice to this buffer can take either one or two slice copies: - one copy,
|
||||
// if the slice fits between `head_ofs` and `Self::SIZE`; or - two copies, if the
|
||||
// ring-buffer head wraps around.
|
||||
|
||||
// First copy length: we can only go from the head offset up to the total buffer size.
|
||||
let len = std::cmp::min(Self::SIZE - head_ofs, src.len());
|
||||
data[head_ofs..(head_ofs + len)].copy_from_slice(&src[..len]);
|
||||
|
||||
// If the slice didn't fit, the buffer head will wrap around, and pushing continues
|
||||
// from the start of the buffer (`&self.data[0]`).
|
||||
if len < src.len() {
|
||||
data[..(src.len() - len)].copy_from_slice(&src[len..]);
|
||||
}
|
||||
|
||||
// Either way, we've just pushed exactly `src.len()` bytes, so that's the amount by
|
||||
// which the (wrapping) buffer head needs to move forward.
|
||||
self.head += Wrapping(src.len() as u32);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Flush the contents of the ring-buffer to a writable stream.
|
||||
///
|
||||
/// Return the number of bytes that have been transferred out of the ring-buffer and into
|
||||
/// the writable stream.
|
||||
///
|
||||
pub fn flush_to<W>(&mut self, sink: &mut W) -> Result<usize>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
// Nothing to do, if this buffer holds no data.
|
||||
if self.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Buffer tail, as an offset into the buffer data slice.
|
||||
let tail_ofs = self.tail.0 as usize % Self::SIZE;
|
||||
|
||||
// Flushing the buffer can take either one or two writes:
|
||||
// - one write, if the tail doesn't need to wrap around to reach the head; or
|
||||
// - two writes, if the tail would wrap around: tail to slice end, then slice end to
|
||||
// head.
|
||||
|
||||
// First write length: the lesser of tail to slice end, or tail to head.
|
||||
let len_to_write = std::cmp::min(Self::SIZE - tail_ofs, self.len());
|
||||
|
||||
// It's safe to unwrap here, since we've already checked if the buffer was empty.
|
||||
let data = self.data.as_ref().unwrap();
|
||||
|
||||
// Issue the first write and absorb any `WouldBlock` error (we can just try again
|
||||
// later).
|
||||
let written = sink
|
||||
.write(&data[tail_ofs..(tail_ofs + len_to_write)])
|
||||
.map_err(Error::TxBufFlush)?;
|
||||
|
||||
// Move the buffer tail ahead by the amount (of bytes) we were able to flush out.
|
||||
self.tail += Wrapping(written as u32);
|
||||
|
||||
// If we weren't able to flush out as much as we tried, there's no point in attempting
|
||||
// our second write.
|
||||
if written < len_to_write {
|
||||
return Ok(written);
|
||||
}
|
||||
|
||||
// Attempt our second write. This will return immediately if a second write isn't
|
||||
// needed, since checking for an empty buffer is the first thing we do in this
|
||||
// function.
|
||||
Ok(written + self.flush_to(sink)?)
|
||||
}
|
||||
|
||||
/// Check if the buffer holds any data that hasn't yet been flushed out.
|
||||
///
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::Error as IoError;
|
||||
use std::io::Result as IoResult;
|
||||
use std::io::{ErrorKind, Write};
|
||||
|
||||
struct TestSink {
|
||||
data: Vec<u8>,
|
||||
err: Option<IoError>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl TestSink {
|
||||
const DEFAULT_CAPACITY: usize = 2 * TxBuf::SIZE;
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
data: Vec::with_capacity(Self::DEFAULT_CAPACITY),
|
||||
err: None,
|
||||
capacity: Self::DEFAULT_CAPACITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for TestSink {
|
||||
fn write(&mut self, src: &[u8]) -> IoResult<usize> {
|
||||
if self.err.is_some() {
|
||||
return Err(self.err.take().unwrap());
|
||||
}
|
||||
let len_to_push = std::cmp::min(self.capacity - self.data.len(), src.len());
|
||||
self.data.extend_from_slice(&src[..len_to_push]);
|
||||
Ok(len_to_push)
|
||||
}
|
||||
fn flush(&mut self) -> IoResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TestSink {
|
||||
fn clear(&mut self) {
|
||||
self.data = Vec::with_capacity(self.capacity);
|
||||
self.err = None;
|
||||
}
|
||||
fn set_err(&mut self, err: IoError) {
|
||||
self.err = Some(err);
|
||||
}
|
||||
fn set_capacity(&mut self, capacity: usize) {
|
||||
self.capacity = capacity;
|
||||
if self.data.len() > self.capacity {
|
||||
self.data.resize(self.capacity, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_push_nowrap() {
|
||||
let mut txbuf = TxBuf::new();
|
||||
let mut sink = TestSink::new();
|
||||
assert!(txbuf.is_empty());
|
||||
|
||||
assert!(txbuf.data.is_none());
|
||||
txbuf.push(&[1, 2, 3, 4]).unwrap();
|
||||
txbuf.push(&[5, 6, 7, 8]).unwrap();
|
||||
txbuf.flush_to(&mut sink).unwrap();
|
||||
assert_eq!(sink.data, [1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_push_wrap() {
|
||||
let mut txbuf = TxBuf::new();
|
||||
let mut sink = TestSink::new();
|
||||
let mut tmp: Vec<u8> = Vec::new();
|
||||
|
||||
tmp.resize(TxBuf::SIZE - 2, 0);
|
||||
txbuf.push(tmp.as_slice()).unwrap();
|
||||
txbuf.flush_to(&mut sink).unwrap();
|
||||
sink.clear();
|
||||
|
||||
txbuf.push(&[1, 2, 3, 4]).unwrap();
|
||||
assert_eq!(txbuf.flush_to(&mut sink).unwrap(), 4);
|
||||
assert_eq!(sink.data, [1, 2, 3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_push_error() {
|
||||
let mut txbuf = TxBuf::new();
|
||||
let mut tmp = Vec::with_capacity(TxBuf::SIZE);
|
||||
|
||||
tmp.resize(TxBuf::SIZE - 1, 0);
|
||||
txbuf.push(tmp.as_slice()).unwrap();
|
||||
match txbuf.push(&[1, 2]) {
|
||||
Err(Error::TxBufFull) => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_incomplete_flush() {
|
||||
let mut txbuf = TxBuf::new();
|
||||
let mut sink = TestSink::new();
|
||||
|
||||
sink.set_capacity(2);
|
||||
txbuf.push(&[1, 2, 3, 4]).unwrap();
|
||||
assert_eq!(txbuf.flush_to(&mut sink).unwrap(), 2);
|
||||
assert_eq!(txbuf.len(), 2);
|
||||
assert_eq!(sink.data, [1, 2]);
|
||||
|
||||
sink.set_capacity(4);
|
||||
assert_eq!(txbuf.flush_to(&mut sink).unwrap(), 2);
|
||||
assert!(txbuf.is_empty());
|
||||
assert_eq!(sink.data, [1, 2, 3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_flush_error() {
|
||||
const EACCESS: i32 = 13;
|
||||
|
||||
let mut txbuf = TxBuf::new();
|
||||
let mut sink = TestSink::new();
|
||||
|
||||
txbuf.push(&[1, 2, 3, 4]).unwrap();
|
||||
let io_err = IoError::from_raw_os_error(EACCESS);
|
||||
sink.set_err(io_err);
|
||||
match txbuf.flush_to(&mut sink) {
|
||||
Err(Error::TxBufFlush(ref err)) if err.kind() == ErrorKind::PermissionDenied => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
885
vm-virtio/src/vsock/device.rs
Normal file
885
vm-virtio/src/vsock/device.rs
Normal file
@@ -0,0 +1,885 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
/// This is the `VirtioDevice` implementation for our vsock device. It handles the virtio-level
|
||||
/// device logic: feature negociation, device configuration, and device activation.
|
||||
/// The run-time device logic (i.e. event-driven data handling) is implemented by
|
||||
/// `super::epoll_handler::EpollHandler`.
|
||||
///
|
||||
/// We aim to conform to the VirtIO v1.1 spec:
|
||||
/// https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.html
|
||||
///
|
||||
/// The vsock device has two input parameters: a CID to identify the device, and a `VsockBackend`
|
||||
/// to use for offloading vsock traffic.
|
||||
///
|
||||
/// Upon its activation, the vsock device creates its `EpollHandler`, passes it the event-interested
|
||||
/// file descriptors, and registers these descriptors with the VMM `EpollContext`. Going forward,
|
||||
/// the `EpollHandler` will get notified whenever an event occurs on the just-registered FDs:
|
||||
/// - an RX queue FD;
|
||||
/// - a TX queue FD;
|
||||
/// - an event queue FD; and
|
||||
/// - a backend FD.
|
||||
///
|
||||
use epoll;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std;
|
||||
use std::io;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::result;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
|
||||
use super::{VsockBackend, VsockPacket};
|
||||
use crate::Error as DeviceError;
|
||||
use crate::VirtioInterrupt;
|
||||
use crate::{
|
||||
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
|
||||
VirtioInterruptType, VIRTIO_F_IN_ORDER, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
const NUM_QUEUES: usize = 3;
|
||||
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
|
||||
|
||||
// New descriptors are pending on the rx queue.
|
||||
pub const RX_QUEUE_EVENT: DeviceEventT = 0;
|
||||
// New descriptors are pending on the tx queue.
|
||||
pub const TX_QUEUE_EVENT: DeviceEventT = 1;
|
||||
// New descriptors are pending on the event queue.
|
||||
pub const EVT_QUEUE_EVENT: DeviceEventT = 2;
|
||||
// Notification coming from the backend.
|
||||
pub const BACKEND_EVENT: DeviceEventT = 3;
|
||||
// The device has been dropped.
|
||||
pub const KILL_EVENT: DeviceEventT = 4;
|
||||
pub const EVENTS_LEN: usize = 5;
|
||||
|
||||
/// The `VsockEpollHandler` implements the runtime logic of our vsock device:
|
||||
/// 1. Respond to TX queue events by wrapping virtio buffers into `VsockPacket`s, then sending those
|
||||
/// packets to the `VsockBackend`;
|
||||
/// 2. Forward backend FD event notifications to the `VsockBackend`;
|
||||
/// 3. Fetch incoming packets from the `VsockBackend` and place them into the virtio RX queue;
|
||||
/// 4. Whenever we have processed some virtio buffers (either TX or RX), let the driver know by
|
||||
/// raising our assigned IRQ.
|
||||
///
|
||||
/// In a nutshell, the `VsockEpollHandler` logic looks like this:
|
||||
/// - on TX queue event:
|
||||
/// - fetch all packets from the TX queue and send them to the backend; then
|
||||
/// - if the backend has queued up any incoming packets, fetch them into any available RX buffers.
|
||||
/// - on RX queue event:
|
||||
/// - fetch any incoming packets, queued up by the backend, into newly available RX buffers.
|
||||
/// - on backend event:
|
||||
/// - forward the event to the backend; then
|
||||
/// - again, attempt to fetch any incoming packets queued by the backend into virtio RX buffers.
|
||||
///
|
||||
pub struct VsockEpollHandler<B: VsockBackend> {
|
||||
pub mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
pub queues: Vec<Queue>,
|
||||
pub queue_evts: Vec<EventFd>,
|
||||
pub kill_evt: EventFd,
|
||||
pub interrupt_cb: Arc<VirtioInterrupt>,
|
||||
pub backend: Arc<RwLock<B>>,
|
||||
}
|
||||
|
||||
impl<B> VsockEpollHandler<B>
|
||||
where
|
||||
B: VsockBackend,
|
||||
{
|
||||
/// Signal the guest driver that we've used some virtio buffers that it had previously made
|
||||
/// available.
|
||||
///
|
||||
fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> {
|
||||
debug!("vsock: raising IRQ");
|
||||
|
||||
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(queue)).map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
}
|
||||
|
||||
/// Walk the driver-provided RX queue buffers and attempt to fill them up with any data that we
|
||||
/// have pending.
|
||||
///
|
||||
fn process_rx(&mut self) -> result::Result<(), DeviceError> {
|
||||
debug!("vsock: epoll_handler::process_rx()");
|
||||
|
||||
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
|
||||
let mut used_count = 0;
|
||||
let mem = self.mem.read().unwrap();
|
||||
for avail_desc in self.queues[0].iter(&mem) {
|
||||
let used_len = match VsockPacket::from_rx_virtq_head(&avail_desc) {
|
||||
Ok(mut pkt) => {
|
||||
if self.backend.write().unwrap().recv_pkt(&mut pkt).is_ok() {
|
||||
pkt.hdr().len() as u32 + pkt.len()
|
||||
} else {
|
||||
// We are using a consuming iterator over the virtio buffers, so, if we can't
|
||||
// fill in this buffer, we'll need to undo the last iterator step.
|
||||
self.queues[0].go_to_previous_position();
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("vsock: RX queue error: {:?}", e);
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
used_desc_heads[used_count] = (avail_desc.index, used_len);
|
||||
used_count += 1;
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
self.queues[0].add_used(&mem, desc_index, len);
|
||||
}
|
||||
|
||||
if used_count > 0 {
|
||||
self.signal_used_queue(&self.queues[0])
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Walk the driver-provided TX queue buffers, package them up as vsock packets, and send them to
|
||||
/// the backend for processing.
|
||||
///
|
||||
fn process_tx(&mut self) -> result::Result<(), DeviceError> {
|
||||
debug!("vsock: epoll_handler::process_tx()");
|
||||
|
||||
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
|
||||
let mut used_count = 0;
|
||||
let mem = self.mem.read().unwrap();
|
||||
for avail_desc in self.queues[1].iter(&mem) {
|
||||
let pkt = match VsockPacket::from_tx_virtq_head(&avail_desc) {
|
||||
Ok(pkt) => pkt,
|
||||
Err(e) => {
|
||||
error!("vsock: error reading TX packet: {:?}", e);
|
||||
used_desc_heads[used_count] = (avail_desc.index, 0);
|
||||
used_count += 1;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if self.backend.write().unwrap().send_pkt(&pkt).is_err() {
|
||||
self.queues[1].go_to_previous_position();
|
||||
break;
|
||||
}
|
||||
|
||||
used_desc_heads[used_count] = (avail_desc.index, 0);
|
||||
used_count += 1;
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
self.queues[1].add_used(&mem, desc_index, len);
|
||||
}
|
||||
|
||||
if used_count > 0 {
|
||||
self.signal_used_queue(&self.queues[1])
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&mut self) -> result::Result<(), DeviceError> {
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
||||
|
||||
// Add events
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.queue_evts[0].as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_QUEUE_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.queue_evts[1].as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(TX_QUEUE_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.queue_evts[2].as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(EVT_QUEUE_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.backend.read().unwrap().get_polled_fd(),
|
||||
epoll::Event::new(
|
||||
self.backend.read().unwrap().get_polled_evset(),
|
||||
u64::from(BACKEND_EVENT),
|
||||
),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.kill_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
// It's well defined from the epoll_wait() syscall
|
||||
// documentation that the epoll loop can be interrupted
|
||||
// before any of the requested events occurred or the
|
||||
// timeout expired. In both those cases, epoll_wait()
|
||||
// returns an error of type EINTR, but this should not
|
||||
// be considered as a regular error. Instead it is more
|
||||
// appropriate to retry, by calling into epoll_wait().
|
||||
continue;
|
||||
}
|
||||
return Err(DeviceError::EpollWait(e));
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let evset = match epoll::Events::from_bits(event.events) {
|
||||
Some(evset) => evset,
|
||||
None => {
|
||||
let evbits = event.events;
|
||||
warn!("epoll: ignoring unknown event set: 0x{:x}", evbits);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let ev_type = event.data as DeviceEventT;
|
||||
|
||||
if self.handle_event(ev_type, evset)? {
|
||||
break 'epoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_event(
|
||||
&mut self,
|
||||
device_event: DeviceEventT,
|
||||
evset: epoll::Events,
|
||||
) -> Result<bool, DeviceError> {
|
||||
match device_event {
|
||||
RX_QUEUE_EVENT => {
|
||||
debug!("vsock: RX queue event");
|
||||
if let Err(e) = self.queue_evts[0].read() {
|
||||
error!("Failed to get RX queue event: {:?}", e);
|
||||
return Err(DeviceError::FailedReadingQueue {
|
||||
event_type: "rx queue event",
|
||||
underlying: e,
|
||||
});
|
||||
} else if self.backend.read().unwrap().has_pending_rx() {
|
||||
self.process_rx()?;
|
||||
}
|
||||
}
|
||||
TX_QUEUE_EVENT => {
|
||||
debug!("vsock: TX queue event");
|
||||
if let Err(e) = self.queue_evts[1].read() {
|
||||
error!("Failed to get TX queue event: {:?}", e);
|
||||
return Err(DeviceError::FailedReadingQueue {
|
||||
event_type: "tx queue event",
|
||||
underlying: e,
|
||||
});
|
||||
} else {
|
||||
self.process_tx()?;
|
||||
// The backend may have queued up responses to the packets we sent during TX queue
|
||||
// processing. If that happened, we need to fetch those responses and place them
|
||||
// into RX buffers.
|
||||
if self.backend.read().unwrap().has_pending_rx() {
|
||||
self.process_rx()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
EVT_QUEUE_EVENT => {
|
||||
debug!("vsock: EVT queue event");
|
||||
if let Err(e) = self.queue_evts[2].read() {
|
||||
error!("Failed to get EVT queue event: {:?}", e);
|
||||
return Err(DeviceError::FailedReadingQueue {
|
||||
event_type: "evt queue event",
|
||||
underlying: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
BACKEND_EVENT => {
|
||||
debug!("vsock: backend event");
|
||||
self.backend.write().unwrap().notify(evset);
|
||||
// After the backend has been kicked, it might've freed up some resources, so we
|
||||
// can attempt to send it more data to process.
|
||||
// In particular, if `self.backend.send_pkt()` halted the TX queue processing (by
|
||||
// reurning an error) at some point in the past, now is the time to try walking the
|
||||
// TX queue again.
|
||||
self.process_tx()?;
|
||||
if self.backend.read().unwrap().has_pending_rx() {
|
||||
self.process_rx()?;
|
||||
}
|
||||
}
|
||||
KILL_EVENT => {
|
||||
debug!("KILL_EVENT received, stopping epoll loop");
|
||||
return Ok(true);
|
||||
}
|
||||
other => {
|
||||
error!("Unknown event for virtio-vsock");
|
||||
return Err(DeviceError::UnknownEvent {
|
||||
device: "vsock",
|
||||
event: other,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Virtio device exposing virtual socket to the guest.
|
||||
pub struct Vsock<B: VsockBackend> {
|
||||
cid: u64,
|
||||
backend: Arc<RwLock<B>>,
|
||||
kill_evt: Option<EventFd>,
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
}
|
||||
|
||||
impl<B> Vsock<B>
|
||||
where
|
||||
B: VsockBackend,
|
||||
{
|
||||
/// Create a new virtio-vsock device with the given VM CID and vsock
|
||||
/// backend.
|
||||
pub fn new(cid: u64, backend: B, iommu: bool) -> io::Result<Vsock<B>> {
|
||||
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_F_IN_ORDER;
|
||||
|
||||
if iommu {
|
||||
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
||||
}
|
||||
|
||||
Ok(Vsock {
|
||||
cid,
|
||||
backend: Arc::new(RwLock::new(backend)),
|
||||
kill_evt: None,
|
||||
avail_features,
|
||||
acked_features: 0u64,
|
||||
queue_evts: None,
|
||||
interrupt_cb: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Drop for Vsock<B>
|
||||
where
|
||||
B: VsockBackend,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> VirtioDevice for Vsock<B>
|
||||
where
|
||||
B: VsockBackend + Sync + 'static,
|
||||
{
|
||||
fn device_type(&self) -> u32 {
|
||||
VirtioDeviceType::TYPE_VSOCK as u32
|
||||
}
|
||||
|
||||
fn queue_max_sizes(&self) -> &[u16] {
|
||||
QUEUE_SIZES
|
||||
}
|
||||
|
||||
fn features(&self, page: u32) -> u32 {
|
||||
match page {
|
||||
// Get the lower 32-bits of the features bitfield.
|
||||
0 => self.avail_features as u32,
|
||||
// Get the upper 32-bits of the features bitfield.
|
||||
1 => (self.avail_features >> 32) as u32,
|
||||
_ => {
|
||||
warn!("Received request for unknown features page.");
|
||||
0u32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ack_features(&mut self, page: u32, value: u32) {
|
||||
let mut v = match page {
|
||||
0 => u64::from(value),
|
||||
1 => u64::from(value) << 32,
|
||||
_ => {
|
||||
warn!("Cannot acknowledge unknown features page.");
|
||||
0u64
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the guest is ACK'ing a feature that we didn't claim to have.
|
||||
let unrequested_features = v & !self.avail_features;
|
||||
if unrequested_features != 0 {
|
||||
warn!("Received acknowledge request for unknown feature.");
|
||||
|
||||
// Don't count these features as acked.
|
||||
v &= !unrequested_features;
|
||||
}
|
||||
self.acked_features |= v;
|
||||
}
|
||||
|
||||
fn read_config(&self, offset: u64, data: &mut [u8]) {
|
||||
match offset {
|
||||
0 if data.len() == 8 => LittleEndian::write_u64(data, self.cid),
|
||||
0 if data.len() == 4 => LittleEndian::write_u32(data, (self.cid & 0xffff_ffff) as u32),
|
||||
4 if data.len() == 4 => {
|
||||
LittleEndian::write_u32(data, ((self.cid >> 32) & 0xffff_ffff) as u32)
|
||||
}
|
||||
_ => warn!(
|
||||
"vsock: virtio-vsock received invalid read request of {} bytes at offset {}",
|
||||
data.len(),
|
||||
offset
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_config(&mut self, offset: u64, data: &[u8]) {
|
||||
warn!(
|
||||
"vsock: guest driver attempted to write device config (offset={:x}, len={:x})",
|
||||
offset,
|
||||
data.len()
|
||||
);
|
||||
}
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES {
|
||||
error!(
|
||||
"Cannot perform activate. Expected {} queue(s), got {}",
|
||||
NUM_QUEUES,
|
||||
queues.len()
|
||||
);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
let (self_kill_evt, kill_evt) =
|
||||
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed creating kill EventFd pair: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
// Save the interrupt EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||
|
||||
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
|
||||
for queue_evt in queue_evts.iter() {
|
||||
// Save the queue EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
|
||||
error!("failed to clone queue EventFd: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?);
|
||||
}
|
||||
self.queue_evts = Some(tmp_queue_evts);
|
||||
|
||||
let mut handler = VsockEpollHandler {
|
||||
mem,
|
||||
queues,
|
||||
queue_evts,
|
||||
kill_evt,
|
||||
interrupt_cb,
|
||||
backend: self.backend.clone(),
|
||||
};
|
||||
|
||||
let worker_result = thread::Builder::new()
|
||||
.name("virtio_vsock".to_string())
|
||||
.spawn(move || handler.run());
|
||||
|
||||
if let Err(e) = worker_result {
|
||||
error!("failed to spawn virtio_vsock worker: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
// Return the interrupt and queue EventFDs
|
||||
Some((
|
||||
self.interrupt_cb.take().unwrap(),
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::tests::TestContext;
|
||||
use super::super::*;
|
||||
use super::*;
|
||||
use crate::vsock::device::{BACKEND_EVENT, EVT_QUEUE_EVENT, RX_QUEUE_EVENT, TX_QUEUE_EVENT};
|
||||
|
||||
#[test]
|
||||
fn test_virtio_device() {
|
||||
let mut ctx = TestContext::new();
|
||||
let avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_F_IN_ORDER;
|
||||
let device_features = avail_features;
|
||||
let driver_features: u64 = avail_features | 1 | (1 << 32);
|
||||
let device_pages = [
|
||||
(device_features & 0xffff_ffff) as u32,
|
||||
(device_features >> 32) as u32,
|
||||
];
|
||||
let driver_pages = [
|
||||
(driver_features & 0xffff_ffff) as u32,
|
||||
(driver_features >> 32) as u32,
|
||||
];
|
||||
assert_eq!(
|
||||
ctx.device.device_type(),
|
||||
VirtioDeviceType::TYPE_VSOCK as u32
|
||||
);
|
||||
assert_eq!(ctx.device.queue_max_sizes(), QUEUE_SIZES);
|
||||
assert_eq!(ctx.device.features(0), device_pages[0]);
|
||||
assert_eq!(ctx.device.features(1), device_pages[1]);
|
||||
assert_eq!(ctx.device.features(2), 0);
|
||||
|
||||
// Ack device features, page 0.
|
||||
ctx.device.ack_features(0, driver_pages[0]);
|
||||
// Ack device features, page 1.
|
||||
ctx.device.ack_features(1, driver_pages[1]);
|
||||
// Ack some bogus page (i.e. 2). This should have no side effect.
|
||||
ctx.device.ack_features(2, 0);
|
||||
// Attempt to un-ack the first feature page. This should have no side effect.
|
||||
ctx.device.ack_features(0, !driver_pages[0]);
|
||||
// Check that no side effect are present, and that the acked features are exactly the same
|
||||
// as the device features.
|
||||
assert_eq!(ctx.device.acked_features, device_features & driver_features);
|
||||
|
||||
// Test reading 32-bit chunks.
|
||||
let mut data = [0u8; 8];
|
||||
ctx.device.read_config(0, &mut data[..4]);
|
||||
assert_eq!(
|
||||
u64::from(LittleEndian::read_u32(&data)),
|
||||
ctx.cid & 0xffff_ffff
|
||||
);
|
||||
ctx.device.read_config(4, &mut data[4..]);
|
||||
assert_eq!(
|
||||
u64::from(LittleEndian::read_u32(&data[4..])),
|
||||
(ctx.cid >> 32) & 0xffff_ffff
|
||||
);
|
||||
|
||||
// Test reading 64-bit.
|
||||
let mut data = [0u8; 8];
|
||||
ctx.device.read_config(0, &mut data);
|
||||
assert_eq!(LittleEndian::read_u64(&data), ctx.cid);
|
||||
|
||||
// Check that out-of-bounds reading doesn't mutate the destination buffer.
|
||||
let mut data = [0u8, 1, 2, 3, 4, 5, 6, 7];
|
||||
ctx.device.read_config(2, &mut data);
|
||||
assert_eq!(data, [0u8, 1, 2, 3, 4, 5, 6, 7]);
|
||||
|
||||
// Just covering lines here, since the vsock device has no writable config.
|
||||
// A warning is, however, logged, if the guest driver attempts to write any config data.
|
||||
ctx.device.write_config(0, &data[..4]);
|
||||
|
||||
// Test a bad activation.
|
||||
let bad_activate = ctx.device.activate(
|
||||
Arc::new(RwLock::new(ctx.mem.clone())),
|
||||
Arc::new(
|
||||
Box::new(move |_: &VirtioInterruptType, _: Option<&Queue>| Ok(()))
|
||||
as VirtioInterrupt,
|
||||
),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
);
|
||||
match bad_activate {
|
||||
Err(ActivateError::BadActivate) => (),
|
||||
other => panic!("{:?}", other),
|
||||
}
|
||||
|
||||
// Test a correct activation.
|
||||
ctx.device
|
||||
.activate(
|
||||
Arc::new(RwLock::new(ctx.mem.clone())),
|
||||
Arc::new(
|
||||
Box::new(move |_: &VirtioInterruptType, _: Option<&Queue>| Ok(()))
|
||||
as VirtioInterrupt,
|
||||
),
|
||||
vec![Queue::new(256), Queue::new(256), Queue::new(256)],
|
||||
vec![
|
||||
EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||
EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||
EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_irq() {
|
||||
// Test case: successful IRQ signaling.
|
||||
{
|
||||
let test_ctx = TestContext::new();
|
||||
let ctx = test_ctx.create_epoll_handler_context();
|
||||
|
||||
let queue = Queue::new(256);
|
||||
assert!(ctx.handler.signal_used_queue(&queue).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_txq_event() {
|
||||
// Test case:
|
||||
// - the driver has something to send (there's data in the TX queue); and
|
||||
// - the backend has no pending RX data.
|
||||
{
|
||||
let test_ctx = TestContext::new();
|
||||
let mut ctx = test_ctx.create_epoll_handler_context();
|
||||
|
||||
ctx.handler.backend.write().unwrap().set_pending_rx(false);
|
||||
ctx.signal_txq_event();
|
||||
|
||||
// The available TX descriptor should have been used.
|
||||
assert_eq!(ctx.guest_txvq.used.idx.get(), 1);
|
||||
// The available RX descriptor should be untouched.
|
||||
assert_eq!(ctx.guest_rxvq.used.idx.get(), 0);
|
||||
}
|
||||
|
||||
// Test case:
|
||||
// - the driver has something to send (there's data in the TX queue); and
|
||||
// - the backend also has some pending RX data.
|
||||
{
|
||||
let test_ctx = TestContext::new();
|
||||
let mut ctx = test_ctx.create_epoll_handler_context();
|
||||
|
||||
ctx.handler.backend.write().unwrap().set_pending_rx(true);
|
||||
ctx.signal_txq_event();
|
||||
|
||||
// Both available RX and TX descriptors should have been used.
|
||||
assert_eq!(ctx.guest_txvq.used.idx.get(), 1);
|
||||
assert_eq!(ctx.guest_rxvq.used.idx.get(), 1);
|
||||
}
|
||||
|
||||
// Test case:
|
||||
// - the driver has something to send (there's data in the TX queue); and
|
||||
// - the backend errors out and cannot process the TX queue.
|
||||
{
|
||||
let test_ctx = TestContext::new();
|
||||
let mut ctx = test_ctx.create_epoll_handler_context();
|
||||
|
||||
ctx.handler.backend.write().unwrap().set_pending_rx(false);
|
||||
ctx.handler
|
||||
.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_tx_err(Some(VsockError::NoData));
|
||||
ctx.signal_txq_event();
|
||||
|
||||
// Both RX and TX queues should be untouched.
|
||||
assert_eq!(ctx.guest_txvq.used.idx.get(), 0);
|
||||
assert_eq!(ctx.guest_rxvq.used.idx.get(), 0);
|
||||
}
|
||||
|
||||
// Test case:
|
||||
// - the driver supplied a malformed TX buffer.
|
||||
{
|
||||
let test_ctx = TestContext::new();
|
||||
let mut ctx = test_ctx.create_epoll_handler_context();
|
||||
|
||||
// Invalidate the packet header descriptor, by setting its length to 0.
|
||||
ctx.guest_txvq.dtable[0].len.set(0);
|
||||
ctx.signal_txq_event();
|
||||
|
||||
// The available descriptor should have been consumed, but no packet should have
|
||||
// reached the backend.
|
||||
assert_eq!(ctx.guest_txvq.used.idx.get(), 1);
|
||||
assert_eq!(ctx.handler.backend.read().unwrap().tx_ok_cnt, 0);
|
||||
}
|
||||
|
||||
// Test case: spurious TXQ_EVENT.
|
||||
{
|
||||
let test_ctx = TestContext::new();
|
||||
let mut ctx = test_ctx.create_epoll_handler_context();
|
||||
|
||||
match ctx
|
||||
.handler
|
||||
.handle_event(TX_QUEUE_EVENT, epoll::Events::EPOLLIN)
|
||||
{
|
||||
Err(DeviceError::FailedReadingQueue { .. }) => (),
|
||||
other => panic!("{:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rxq_event() {
|
||||
// Test case:
|
||||
// - there is pending RX data in the backend; and
|
||||
// - the driver makes RX buffers available; and
|
||||
// - the backend successfully places its RX data into the queue.
|
||||
{
|
||||
let test_ctx = TestContext::new();
|
||||
let mut ctx = test_ctx.create_epoll_handler_context();
|
||||
|
||||
ctx.handler.backend.write().unwrap().set_pending_rx(true);
|
||||
ctx.handler
|
||||
.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_rx_err(Some(VsockError::NoData));
|
||||
ctx.signal_rxq_event();
|
||||
|
||||
// The available RX buffer should've been left untouched.
|
||||
assert_eq!(ctx.guest_rxvq.used.idx.get(), 0);
|
||||
}
|
||||
|
||||
// Test case:
|
||||
// - there is pending RX data in the backend; and
|
||||
// - the driver makes RX buffers available; and
|
||||
// - the backend errors out, when attempting to receive data.
|
||||
{
|
||||
let test_ctx = TestContext::new();
|
||||
let mut ctx = test_ctx.create_epoll_handler_context();
|
||||
|
||||
ctx.handler.backend.write().unwrap().set_pending_rx(true);
|
||||
ctx.signal_rxq_event();
|
||||
|
||||
// The available RX buffer should have been used.
|
||||
assert_eq!(ctx.guest_rxvq.used.idx.get(), 1);
|
||||
}
|
||||
|
||||
// Test case: the driver provided a malformed RX descriptor chain.
|
||||
{
|
||||
let test_ctx = TestContext::new();
|
||||
let mut ctx = test_ctx.create_epoll_handler_context();
|
||||
|
||||
// Invalidate the packet header descriptor, by setting its length to 0.
|
||||
ctx.guest_rxvq.dtable[0].len.set(0);
|
||||
|
||||
// The chain should've been processed, without employing the backend.
|
||||
assert!(ctx.handler.process_rx().is_ok());
|
||||
assert_eq!(ctx.guest_rxvq.used.idx.get(), 1);
|
||||
assert_eq!(ctx.handler.backend.read().unwrap().rx_ok_cnt, 0);
|
||||
}
|
||||
|
||||
// Test case: spurious RXQ_EVENT.
|
||||
{
|
||||
let test_ctx = TestContext::new();
|
||||
let mut ctx = test_ctx.create_epoll_handler_context();
|
||||
ctx.handler.backend.write().unwrap().set_pending_rx(false);
|
||||
match ctx
|
||||
.handler
|
||||
.handle_event(RX_QUEUE_EVENT, epoll::Events::EPOLLIN)
|
||||
{
|
||||
Err(DeviceError::FailedReadingQueue { .. }) => (),
|
||||
other => panic!("{:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_evq_event() {
|
||||
// Test case: spurious EVQ_EVENT.
|
||||
{
|
||||
let test_ctx = TestContext::new();
|
||||
let mut ctx = test_ctx.create_epoll_handler_context();
|
||||
ctx.handler.backend.write().unwrap().set_pending_rx(false);
|
||||
match ctx
|
||||
.handler
|
||||
.handle_event(EVT_QUEUE_EVENT, epoll::Events::EPOLLIN)
|
||||
{
|
||||
Err(DeviceError::FailedReadingQueue { .. }) => (),
|
||||
other => panic!("{:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_backend_event() {
|
||||
// Test case:
|
||||
// - a backend event is received; and
|
||||
// - the backend has pending RX data.
|
||||
{
|
||||
let test_ctx = TestContext::new();
|
||||
let mut ctx = test_ctx.create_epoll_handler_context();
|
||||
|
||||
ctx.handler.backend.write().unwrap().set_pending_rx(true);
|
||||
ctx.handler
|
||||
.handle_event(BACKEND_EVENT, epoll::Events::EPOLLIN)
|
||||
.unwrap();
|
||||
|
||||
// The backend should've received this event.
|
||||
assert_eq!(
|
||||
ctx.handler.backend.read().unwrap().evset,
|
||||
Some(epoll::Events::EPOLLIN)
|
||||
);
|
||||
// TX queue processing should've been triggered.
|
||||
assert_eq!(ctx.guest_txvq.used.idx.get(), 1);
|
||||
// RX queue processing should've been triggered.
|
||||
assert_eq!(ctx.guest_rxvq.used.idx.get(), 1);
|
||||
}
|
||||
|
||||
// Test case:
|
||||
// - a backend event is received; and
|
||||
// - the backend doesn't have any pending RX data.
|
||||
{
|
||||
let test_ctx = TestContext::new();
|
||||
let mut ctx = test_ctx.create_epoll_handler_context();
|
||||
|
||||
ctx.handler.backend.write().unwrap().set_pending_rx(false);
|
||||
ctx.handler
|
||||
.handle_event(BACKEND_EVENT, epoll::Events::EPOLLIN)
|
||||
.unwrap();
|
||||
|
||||
// The backend should've received this event.
|
||||
assert_eq!(
|
||||
ctx.handler.backend.read().unwrap().evset,
|
||||
Some(epoll::Events::EPOLLIN)
|
||||
);
|
||||
// TX queue processing should've been triggered.
|
||||
assert_eq!(ctx.guest_txvq.used.idx.get(), 1);
|
||||
// The RX queue should've been left untouched.
|
||||
assert_eq!(ctx.guest_rxvq.used.idx.get(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unknown_event() {
|
||||
let test_ctx = TestContext::new();
|
||||
let mut ctx = test_ctx.create_epoll_handler_context();
|
||||
|
||||
match ctx.handler.handle_event(0xff, epoll::Events::EPOLLIN) {
|
||||
Err(DeviceError::UnknownEvent { .. }) => (),
|
||||
other => panic!("{:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
337
vm-virtio/src/vsock/mod.rs
Normal file
337
vm-virtio/src/vsock/mod.rs
Normal file
@@ -0,0 +1,337 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
mod csm;
|
||||
mod device;
|
||||
mod packet;
|
||||
mod unix;
|
||||
|
||||
pub use self::device::Vsock;
|
||||
pub use self::unix::VsockUnixBackend;
|
||||
pub use self::unix::VsockUnixError;
|
||||
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
use packet::VsockPacket;
|
||||
|
||||
mod defs {
|
||||
|
||||
/// Max vsock packet data/buffer size.
|
||||
pub const MAX_PKT_BUF_SIZE: usize = 64 * 1024;
|
||||
|
||||
pub mod uapi {
|
||||
|
||||
/// Vsock packet operation IDs.
|
||||
/// Defined in `/include/uapi/linux/virtio_vsock.h`.
|
||||
///
|
||||
/// Connection request.
|
||||
pub const VSOCK_OP_REQUEST: u16 = 1;
|
||||
/// Connection response.
|
||||
pub const VSOCK_OP_RESPONSE: u16 = 2;
|
||||
/// Connection reset.
|
||||
pub const VSOCK_OP_RST: u16 = 3;
|
||||
/// Connection clean shutdown.
|
||||
pub const VSOCK_OP_SHUTDOWN: u16 = 4;
|
||||
/// Connection data (read/write).
|
||||
pub const VSOCK_OP_RW: u16 = 5;
|
||||
/// Flow control credit update.
|
||||
pub const VSOCK_OP_CREDIT_UPDATE: u16 = 6;
|
||||
/// Flow control credit update request.
|
||||
pub const VSOCK_OP_CREDIT_REQUEST: u16 = 7;
|
||||
|
||||
/// Vsock packet flags.
|
||||
/// Defined in `/include/uapi/linux/virtio_vsock.h`.
|
||||
///
|
||||
/// Valid with a VSOCK_OP_SHUTDOWN packet: the packet sender will receive no more data.
|
||||
pub const VSOCK_FLAGS_SHUTDOWN_RCV: u32 = 1;
|
||||
/// Valid with a VSOCK_OP_SHUTDOWN packet: the packet sender will send no more data.
|
||||
pub const VSOCK_FLAGS_SHUTDOWN_SEND: u32 = 2;
|
||||
|
||||
/// Vsock packet type.
|
||||
/// Defined in `/include/uapi/linux/virtio_vsock.h`.
|
||||
///
|
||||
/// Stream / connection-oriented packet (the only currently valid type).
|
||||
pub const VSOCK_TYPE_STREAM: u16 = 1;
|
||||
|
||||
pub const VSOCK_HOST_CID: u64 = 2;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VsockError {
|
||||
/// The vsock data/buffer virtio descriptor length is smaller than expected.
|
||||
BufDescTooSmall,
|
||||
/// The vsock data/buffer virtio descriptor is expected, but missing.
|
||||
BufDescMissing,
|
||||
/// Chained GuestMemory error.
|
||||
GuestMemory,
|
||||
/// Bounds check failed on guest memory pointer.
|
||||
GuestMemoryBounds,
|
||||
/// The vsock header descriptor length is too small.
|
||||
HdrDescTooSmall(u32),
|
||||
/// The vsock header `len` field holds an invalid value.
|
||||
InvalidPktLen(u32),
|
||||
/// A data fetch was attempted when no data was available.
|
||||
NoData,
|
||||
/// A data buffer was expected for the provided packet, but it is missing.
|
||||
PktBufMissing,
|
||||
/// Encountered an unexpected write-only virtio descriptor.
|
||||
UnreadableDescriptor,
|
||||
/// Encountered an unexpected read-only virtio descriptor.
|
||||
UnwritableDescriptor,
|
||||
}
|
||||
type Result<T> = std::result::Result<T, VsockError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VsockEpollHandlerError {
|
||||
/// The vsock data/buffer virtio descriptor length is smaller than expected.
|
||||
BufDescTooSmall,
|
||||
/// The vsock data/buffer virtio descriptor is expected, but missing.
|
||||
BufDescMissing,
|
||||
/// Chained GuestMemory error.
|
||||
GuestMemory,
|
||||
/// Bounds check failed on guest memory pointer.
|
||||
GuestMemoryBounds,
|
||||
/// The vsock header descriptor length is too small.
|
||||
HdrDescTooSmall(u32),
|
||||
/// The vsock header `len` field holds an invalid value.
|
||||
InvalidPktLen(u32),
|
||||
/// A data fetch was attempted when no data was available.
|
||||
NoData,
|
||||
/// A data buffer was expected for the provided packet, but it is missing.
|
||||
PktBufMissing,
|
||||
/// Encountered an unexpected write-only virtio descriptor.
|
||||
UnreadableDescriptor,
|
||||
/// Encountered an unexpected read-only virtio descriptor.
|
||||
UnwritableDescriptor,
|
||||
}
|
||||
|
||||
/// A passive, event-driven object, that needs to be notified whenever an epoll-able event occurs.
|
||||
/// An event-polling control loop will use `get_polled_fd()` and `get_polled_evset()` to query
|
||||
/// the listener for the file descriptor and the set of events it's interested in. When such an
|
||||
/// event occurs, the control loop will route the event to the listener via `notify()`.
|
||||
///
|
||||
pub trait VsockEpollListener {
|
||||
/// Get the file descriptor the listener needs polled.
|
||||
fn get_polled_fd(&self) -> RawFd;
|
||||
|
||||
/// Get the set of events for which the listener wants to be notified.
|
||||
fn get_polled_evset(&self) -> epoll::Events;
|
||||
|
||||
/// Notify the listener that one ore more events have occurred.
|
||||
fn notify(&mut self, evset: epoll::Events);
|
||||
}
|
||||
|
||||
/// Any channel that handles vsock packet traffic: sending and receiving packets. Since we're
|
||||
/// implementing the device model here, our responsibility is to always process the sending of
|
||||
/// packets (i.e. the TX queue). So, any locally generated data, addressed to the driver (e.g.
|
||||
/// a connection response or RST), will have to be queued, until we get to processing the RX queue.
|
||||
///
|
||||
/// Note: `recv_pkt()` and `send_pkt()` are named analogous to `Read::read()` and `Write::write()`,
|
||||
/// respectively. I.e.
|
||||
/// - `recv_pkt(&mut pkt)` will read data from the channel, and place it into `pkt`; and
|
||||
/// - `send_pkt(&pkt)` will fetch data from `pkt`, and place it into the channel.
|
||||
pub trait VsockChannel {
|
||||
/// Read/receive an incoming packet from the channel.
|
||||
fn recv_pkt(&mut self, pkt: &mut VsockPacket) -> Result<()>;
|
||||
|
||||
/// Write/send a packet through the channel.
|
||||
fn send_pkt(&mut self, pkt: &VsockPacket) -> Result<()>;
|
||||
|
||||
/// Checks whether there is pending incoming data inside the channel, meaning that a subsequent
|
||||
/// call to `recv_pkt()` won't fail.
|
||||
fn has_pending_rx(&self) -> bool;
|
||||
}
|
||||
|
||||
/// The vsock backend, which is basically an epoll-event-driven vsock channel, that needs to be
|
||||
/// sendable through a mpsc channel (the latter due to how `vmm::EpollContext` works).
|
||||
/// Currently, the only implementation we have is `crate::virtio::unix::muxer::VsockMuxer`, which
|
||||
/// translates guest-side vsock connections to host-side Unix domain socket connections.
|
||||
pub trait VsockBackend: VsockChannel + VsockEpollListener + Send {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use libc::EFD_NONBLOCK;
|
||||
|
||||
use super::device::{VsockEpollHandler, RX_QUEUE_EVENT, TX_QUEUE_EVENT};
|
||||
use super::packet::VSOCK_PKT_HDR_SIZE;
|
||||
use super::*;
|
||||
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::device::{VirtioInterrupt, VirtioInterruptType};
|
||||
use crate::queue::tests::VirtQueue as GuestQ;
|
||||
use crate::queue::Queue;
|
||||
use crate::{VIRTQ_DESC_F_NEXT, VIRTQ_DESC_F_WRITE};
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
|
||||
pub struct TestBackend {
|
||||
pub evfd: EventFd,
|
||||
pub rx_err: Option<VsockError>,
|
||||
pub tx_err: Option<VsockError>,
|
||||
pub pending_rx: bool,
|
||||
pub rx_ok_cnt: usize,
|
||||
pub tx_ok_cnt: usize,
|
||||
pub evset: Option<epoll::Events>,
|
||||
}
|
||||
impl TestBackend {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
evfd: EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||
rx_err: None,
|
||||
tx_err: None,
|
||||
pending_rx: false,
|
||||
rx_ok_cnt: 0,
|
||||
tx_ok_cnt: 0,
|
||||
evset: None,
|
||||
}
|
||||
}
|
||||
pub fn set_rx_err(&mut self, err: Option<VsockError>) {
|
||||
self.rx_err = err;
|
||||
}
|
||||
pub fn set_tx_err(&mut self, err: Option<VsockError>) {
|
||||
self.tx_err = err;
|
||||
}
|
||||
pub fn set_pending_rx(&mut self, prx: bool) {
|
||||
self.pending_rx = prx;
|
||||
}
|
||||
}
|
||||
impl VsockChannel for TestBackend {
|
||||
fn recv_pkt(&mut self, _pkt: &mut VsockPacket) -> Result<()> {
|
||||
match self.rx_err.take() {
|
||||
None => {
|
||||
self.rx_ok_cnt += 1;
|
||||
Ok(())
|
||||
}
|
||||
Some(e) => Err(e),
|
||||
}
|
||||
}
|
||||
fn send_pkt(&mut self, _pkt: &VsockPacket) -> Result<()> {
|
||||
match self.tx_err.take() {
|
||||
None => {
|
||||
self.tx_ok_cnt += 1;
|
||||
Ok(())
|
||||
}
|
||||
Some(e) => Err(e),
|
||||
}
|
||||
}
|
||||
fn has_pending_rx(&self) -> bool {
|
||||
self.pending_rx
|
||||
}
|
||||
}
|
||||
impl VsockEpollListener for TestBackend {
|
||||
fn get_polled_fd(&self) -> RawFd {
|
||||
self.evfd.as_raw_fd()
|
||||
}
|
||||
fn get_polled_evset(&self) -> epoll::Events {
|
||||
epoll::Events::EPOLLIN
|
||||
}
|
||||
fn notify(&mut self, evset: epoll::Events) {
|
||||
self.evset = Some(evset);
|
||||
}
|
||||
}
|
||||
impl VsockBackend for TestBackend {}
|
||||
|
||||
pub struct TestContext {
|
||||
pub cid: u64,
|
||||
pub mem: GuestMemoryMmap,
|
||||
pub mem_size: usize,
|
||||
pub device: Vsock<TestBackend>,
|
||||
}
|
||||
|
||||
impl TestContext {
|
||||
pub fn new() -> Self {
|
||||
const CID: u64 = 52;
|
||||
const MEM_SIZE: usize = 1024 * 1024 * 128;
|
||||
Self {
|
||||
cid: CID,
|
||||
mem: GuestMemoryMmap::new(&[(GuestAddress(0), MEM_SIZE)]).unwrap(),
|
||||
mem_size: MEM_SIZE,
|
||||
device: Vsock::new(CID, TestBackend::new(), false).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_epoll_handler_context(&self) -> EpollHandlerContext {
|
||||
const QSIZE: u16 = 2;
|
||||
|
||||
let guest_rxvq = GuestQ::new(GuestAddress(0x0010_0000), &self.mem, QSIZE as u16);
|
||||
let guest_txvq = GuestQ::new(GuestAddress(0x0020_0000), &self.mem, QSIZE as u16);
|
||||
let guest_evvq = GuestQ::new(GuestAddress(0x0030_0000), &self.mem, QSIZE as u16);
|
||||
let rxvq = guest_rxvq.create_queue();
|
||||
let txvq = guest_txvq.create_queue();
|
||||
let evvq = guest_evvq.create_queue();
|
||||
|
||||
// Set up one available descriptor in the RX queue.
|
||||
guest_rxvq.dtable[0].set(
|
||||
0x0040_0000,
|
||||
VSOCK_PKT_HDR_SIZE as u32,
|
||||
VIRTQ_DESC_F_WRITE | VIRTQ_DESC_F_NEXT,
|
||||
1,
|
||||
);
|
||||
guest_rxvq.dtable[1].set(0x0040_1000, 4096, VIRTQ_DESC_F_WRITE, 0);
|
||||
guest_rxvq.avail.ring[0].set(0);
|
||||
guest_rxvq.avail.idx.set(1);
|
||||
|
||||
// Set up one available descriptor in the TX queue.
|
||||
guest_txvq.dtable[0].set(0x0050_0000, VSOCK_PKT_HDR_SIZE as u32, VIRTQ_DESC_F_NEXT, 1);
|
||||
guest_txvq.dtable[1].set(0x0050_1000, 4096, 0, 0);
|
||||
guest_txvq.avail.ring[0].set(0);
|
||||
guest_txvq.avail.idx.set(1);
|
||||
|
||||
let queues = vec![rxvq, txvq, evvq];
|
||||
let queue_evts = vec![
|
||||
EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||
EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||
EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||
];
|
||||
let interrupt_cb = Arc::new(Box::new(
|
||||
move |_: &VirtioInterruptType, _: Option<&Queue>| Ok(()),
|
||||
) as VirtioInterrupt);
|
||||
|
||||
EpollHandlerContext {
|
||||
guest_rxvq,
|
||||
guest_txvq,
|
||||
guest_evvq,
|
||||
handler: VsockEpollHandler {
|
||||
mem: Arc::new(RwLock::new(self.mem.clone())),
|
||||
queues,
|
||||
queue_evts,
|
||||
kill_evt: EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||
interrupt_cb,
|
||||
backend: Arc::new(RwLock::new(TestBackend::new())),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EpollHandlerContext<'a> {
|
||||
pub handler: VsockEpollHandler<TestBackend>,
|
||||
pub guest_rxvq: GuestQ<'a>,
|
||||
pub guest_txvq: GuestQ<'a>,
|
||||
pub guest_evvq: GuestQ<'a>,
|
||||
}
|
||||
|
||||
impl<'a> EpollHandlerContext<'a> {
|
||||
pub fn signal_txq_event(&mut self) {
|
||||
self.handler.queue_evts[1].write(1).unwrap();
|
||||
self.handler
|
||||
.handle_event(TX_QUEUE_EVENT, epoll::Events::EPOLLIN)
|
||||
.unwrap();
|
||||
}
|
||||
pub fn signal_rxq_event(&mut self) {
|
||||
self.handler.queue_evts[0].write(1).unwrap();
|
||||
self.handler
|
||||
.handle_event(RX_QUEUE_EVENT, epoll::Events::EPOLLIN)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
657
vm-virtio/src/vsock/packet.rs
Normal file
657
vm-virtio/src/vsock/packet.rs
Normal file
@@ -0,0 +1,657 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
/// `VsockPacket` provides a thin wrapper over the buffers exchanged via virtio queues.
|
||||
/// There are two components to a vsock packet, each using its own descriptor in a
|
||||
/// virtio queue:
|
||||
/// - the packet header; and
|
||||
/// - the packet data/buffer.
|
||||
/// There is a 1:1 relation between descriptor chains and packets: the first (chain head) holds
|
||||
/// the header, and an optional second descriptor holds the data. The second descriptor is only
|
||||
/// present for data packets (VSOCK_OP_RW).
|
||||
///
|
||||
/// `VsockPacket` wraps these two buffers and provides direct access to the data stored
|
||||
/// in guest memory. This is done to avoid unnecessarily copying data from guest memory
|
||||
/// to temporary buffers, before passing it on to the vsock backend.
|
||||
///
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
|
||||
use super::super::DescriptorChain;
|
||||
use super::defs;
|
||||
use super::{Result, VsockError};
|
||||
|
||||
// The vsock packet header is defined by the C struct:
|
||||
//
|
||||
// ```C
|
||||
// struct virtio_vsock_hdr {
|
||||
// le64 src_cid;
|
||||
// le64 dst_cid;
|
||||
// le32 src_port;
|
||||
// le32 dst_port;
|
||||
// le32 len;
|
||||
// le16 type;
|
||||
// le16 op;
|
||||
// le32 flags;
|
||||
// le32 buf_alloc;
|
||||
// le32 fwd_cnt;
|
||||
// };
|
||||
// ```
|
||||
//
|
||||
// This structed will occupy the buffer pointed to by the head descriptor. We'll be accessing it
|
||||
// as a byte slice. To that end, we define below the offsets for each field struct, as well as the
|
||||
// packed struct size, as a bunch of `usize` consts.
|
||||
// Note that these offsets are only used privately by the `VsockPacket` struct, the public interface
|
||||
// consisting of getter and setter methods, for each struct field, that will also handle the correct
|
||||
// endianess.
|
||||
|
||||
/// The vsock packet header struct size (when packed).
|
||||
pub const VSOCK_PKT_HDR_SIZE: usize = 44;
|
||||
|
||||
// Source CID.
|
||||
const HDROFF_SRC_CID: usize = 0;
|
||||
|
||||
// Destination CID.
|
||||
const HDROFF_DST_CID: usize = 8;
|
||||
|
||||
// Source port.
|
||||
const HDROFF_SRC_PORT: usize = 16;
|
||||
|
||||
// Destination port.
|
||||
const HDROFF_DST_PORT: usize = 20;
|
||||
|
||||
// Data length (in bytes) - may be 0, if there is no data buffer.
|
||||
const HDROFF_LEN: usize = 24;
|
||||
|
||||
// Socket type. Currently, only connection-oriented streams are defined by the vsock protocol.
|
||||
const HDROFF_TYPE: usize = 28;
|
||||
|
||||
// Operation ID - one of the VSOCK_OP_* values; e.g.
|
||||
// - VSOCK_OP_RW: a data packet;
|
||||
// - VSOCK_OP_REQUEST: connection request;
|
||||
// - VSOCK_OP_RST: forcefull connection termination;
|
||||
// etc (see `super::defs::uapi` for the full list).
|
||||
const HDROFF_OP: usize = 30;
|
||||
|
||||
// Additional options (flags) associated with the current operation (`op`).
|
||||
// Currently, only used with shutdown requests (VSOCK_OP_SHUTDOWN).
|
||||
const HDROFF_FLAGS: usize = 32;
|
||||
|
||||
// Size (in bytes) of the packet sender receive buffer (for the connection to which this packet
|
||||
// belongs).
|
||||
const HDROFF_BUF_ALLOC: usize = 36;
|
||||
|
||||
// Number of bytes the sender has received and consumed (for the connection to which this packet
|
||||
// belongs). For instance, for our Unix backend, this counter would be the total number of bytes
|
||||
// we have successfully written to a backing Unix socket.
|
||||
const HDROFF_FWD_CNT: usize = 40;
|
||||
|
||||
/// The vsock packet, implemented as a wrapper over a virtq descriptor chain:
|
||||
/// - the chain head, holding the packet header; and
|
||||
/// - (an optional) data/buffer descriptor, only present for data packets (VSOCK_OP_RW).
|
||||
///
|
||||
pub struct VsockPacket {
|
||||
hdr: *mut u8,
|
||||
buf: Option<*mut u8>,
|
||||
buf_size: usize,
|
||||
}
|
||||
|
||||
impl VsockPacket {
|
||||
/// Create the packet wrapper from a TX virtq chain head.
|
||||
///
|
||||
/// The chain head is expected to hold valid packet header data. A following packet buffer
|
||||
/// descriptor can optionally end the chain. Bounds and pointer checks are performed when
|
||||
/// creating the wrapper.
|
||||
///
|
||||
pub fn from_tx_virtq_head(head: &DescriptorChain) -> Result<Self> {
|
||||
// All buffers in the TX queue must be readable.
|
||||
//
|
||||
if head.is_write_only() {
|
||||
return Err(VsockError::UnreadableDescriptor);
|
||||
}
|
||||
|
||||
// The packet header should fit inside the head descriptor.
|
||||
if head.len < VSOCK_PKT_HDR_SIZE as u32 {
|
||||
return Err(VsockError::HdrDescTooSmall(head.len));
|
||||
}
|
||||
|
||||
let mut pkt = Self {
|
||||
hdr: head
|
||||
.mem
|
||||
.get_host_address(head.addr)
|
||||
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
|
||||
buf: None,
|
||||
buf_size: 0,
|
||||
};
|
||||
|
||||
// No point looking for a data/buffer descriptor, if the packet is zero-lengthed.
|
||||
if pkt.len() == 0 {
|
||||
return Ok(pkt);
|
||||
}
|
||||
|
||||
// Reject weirdly-sized packets.
|
||||
//
|
||||
if pkt.len() > defs::MAX_PKT_BUF_SIZE as u32 {
|
||||
return Err(VsockError::InvalidPktLen(pkt.len()));
|
||||
}
|
||||
|
||||
// If the packet header showed a non-zero length, there should be a data descriptor here.
|
||||
let buf_desc = head.next_descriptor().ok_or(VsockError::BufDescMissing)?;
|
||||
|
||||
// TX data should be read-only.
|
||||
if buf_desc.is_write_only() {
|
||||
return Err(VsockError::UnreadableDescriptor);
|
||||
}
|
||||
|
||||
// The data buffer should be large enough to fit the size of the data, as described by
|
||||
// the header descriptor.
|
||||
if buf_desc.len < pkt.len() {
|
||||
return Err(VsockError::BufDescTooSmall);
|
||||
}
|
||||
|
||||
pkt.buf_size = buf_desc.len as usize;
|
||||
pkt.buf = Some(
|
||||
buf_desc
|
||||
.mem
|
||||
.get_host_address(buf_desc.addr)
|
||||
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
|
||||
);
|
||||
|
||||
Ok(pkt)
|
||||
}
|
||||
|
||||
/// Create the packet wrapper from an RX virtq chain head.
|
||||
///
|
||||
/// There must be two descriptors in the chain, both writable: a header descriptor and a data
|
||||
/// descriptor. Bounds and pointer checks are performed when creating the wrapper.
|
||||
///
|
||||
pub fn from_rx_virtq_head(head: &DescriptorChain) -> Result<Self> {
|
||||
// All RX buffers must be writable.
|
||||
//
|
||||
if !head.is_write_only() {
|
||||
return Err(VsockError::UnwritableDescriptor);
|
||||
}
|
||||
|
||||
// The packet header should fit inside the head descriptor.
|
||||
if head.len < VSOCK_PKT_HDR_SIZE as u32 {
|
||||
return Err(VsockError::HdrDescTooSmall(head.len));
|
||||
}
|
||||
|
||||
// All RX descriptor chains should have a header and a data descriptor.
|
||||
if !head.has_next() {
|
||||
return Err(VsockError::BufDescMissing);
|
||||
}
|
||||
let buf_desc = head.next_descriptor().ok_or(VsockError::BufDescMissing)?;
|
||||
|
||||
Ok(Self {
|
||||
hdr: head
|
||||
.mem
|
||||
.get_host_address(head.addr)
|
||||
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
|
||||
buf: Some(
|
||||
buf_desc
|
||||
.mem
|
||||
.get_host_address(buf_desc.addr)
|
||||
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
|
||||
),
|
||||
buf_size: buf_desc.len as usize,
|
||||
})
|
||||
}
|
||||
|
||||
/// Provides in-place, byte-slice, access to the vsock packet header.
|
||||
///
|
||||
pub fn hdr(&self) -> &[u8] {
|
||||
// This is safe since bound checks have already been performed when creating the packet
|
||||
// from the virtq descriptor.
|
||||
unsafe { std::slice::from_raw_parts(self.hdr as *const u8, VSOCK_PKT_HDR_SIZE) }
|
||||
}
|
||||
|
||||
/// Provides in-place, byte-slice, mutable access to the vsock packet header.
|
||||
///
|
||||
pub fn hdr_mut(&mut self) -> &mut [u8] {
|
||||
// This is safe since bound checks have already been performed when creating the packet
|
||||
// from the virtq descriptor.
|
||||
unsafe { std::slice::from_raw_parts_mut(self.hdr, VSOCK_PKT_HDR_SIZE) }
|
||||
}
|
||||
|
||||
/// Provides in-place, byte-slice access to the vsock packet data buffer.
|
||||
///
|
||||
/// Note: control packets (e.g. connection request or reset) have no data buffer associated.
|
||||
/// For those packets, this method will return `None`.
|
||||
/// Also note: calling `len()` on the returned slice will yield the buffer size, which may be
|
||||
/// (and often is) larger than the length of the packet data. The packet data length
|
||||
/// is stored in the packet header, and accessible via `VsockPacket::len()`.
|
||||
pub fn buf(&self) -> Option<&[u8]> {
|
||||
self.buf.map(|ptr| {
|
||||
// This is safe since bound checks have already been performed when creating the packet
|
||||
// from the virtq descriptor.
|
||||
unsafe { std::slice::from_raw_parts(ptr as *const u8, self.buf_size) }
|
||||
})
|
||||
}
|
||||
|
||||
/// Provides in-place, byte-slice, mutable access to the vsock packet data buffer.
|
||||
///
|
||||
/// Note: control packets (e.g. connection request or reset) have no data buffer associated.
|
||||
/// For those packets, this method will return `None`.
|
||||
/// Also note: calling `len()` on the returned slice will yield the buffer size, which may be
|
||||
/// (and often is) larger than the length of the packet data. The packet data length
|
||||
/// is stored in the packet header, and accessible via `VsockPacket::len()`.
|
||||
pub fn buf_mut(&mut self) -> Option<&mut [u8]> {
|
||||
self.buf.map(|ptr| {
|
||||
// This is safe since bound checks have already been performed when creating the packet
|
||||
// from the virtq descriptor.
|
||||
unsafe { std::slice::from_raw_parts_mut(ptr, self.buf_size) }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn src_cid(&self) -> u64 {
|
||||
LittleEndian::read_u64(&self.hdr()[HDROFF_SRC_CID..])
|
||||
}
|
||||
|
||||
pub fn set_src_cid(&mut self, cid: u64) -> &mut Self {
|
||||
LittleEndian::write_u64(&mut self.hdr_mut()[HDROFF_SRC_CID..], cid);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn dst_cid(&self) -> u64 {
|
||||
LittleEndian::read_u64(&self.hdr()[HDROFF_DST_CID..])
|
||||
}
|
||||
|
||||
pub fn set_dst_cid(&mut self, cid: u64) -> &mut Self {
|
||||
LittleEndian::write_u64(&mut self.hdr_mut()[HDROFF_DST_CID..], cid);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn src_port(&self) -> u32 {
|
||||
LittleEndian::read_u32(&self.hdr()[HDROFF_SRC_PORT..])
|
||||
}
|
||||
|
||||
pub fn set_src_port(&mut self, port: u32) -> &mut Self {
|
||||
LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_SRC_PORT..], port);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn dst_port(&self) -> u32 {
|
||||
LittleEndian::read_u32(&self.hdr()[HDROFF_DST_PORT..])
|
||||
}
|
||||
|
||||
pub fn set_dst_port(&mut self, port: u32) -> &mut Self {
|
||||
LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_DST_PORT..], port);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn len(&self) -> u32 {
|
||||
LittleEndian::read_u32(&self.hdr()[HDROFF_LEN..])
|
||||
}
|
||||
|
||||
pub fn set_len(&mut self, len: u32) -> &mut Self {
|
||||
LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_LEN..], len);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn type_(&self) -> u16 {
|
||||
LittleEndian::read_u16(&self.hdr()[HDROFF_TYPE..])
|
||||
}
|
||||
|
||||
pub fn set_type(&mut self, type_: u16) -> &mut Self {
|
||||
LittleEndian::write_u16(&mut self.hdr_mut()[HDROFF_TYPE..], type_);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn op(&self) -> u16 {
|
||||
LittleEndian::read_u16(&self.hdr()[HDROFF_OP..])
|
||||
}
|
||||
|
||||
pub fn set_op(&mut self, op: u16) -> &mut Self {
|
||||
LittleEndian::write_u16(&mut self.hdr_mut()[HDROFF_OP..], op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> u32 {
|
||||
LittleEndian::read_u32(&self.hdr()[HDROFF_FLAGS..])
|
||||
}
|
||||
|
||||
pub fn set_flags(&mut self, flags: u32) -> &mut Self {
|
||||
LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_FLAGS..], flags);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_flag(&mut self, flag: u32) -> &mut Self {
|
||||
self.set_flags(self.flags() | flag);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn buf_alloc(&self) -> u32 {
|
||||
LittleEndian::read_u32(&self.hdr()[HDROFF_BUF_ALLOC..])
|
||||
}
|
||||
|
||||
pub fn set_buf_alloc(&mut self, buf_alloc: u32) -> &mut Self {
|
||||
LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_BUF_ALLOC..], buf_alloc);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fwd_cnt(&self) -> u32 {
|
||||
LittleEndian::read_u32(&self.hdr()[HDROFF_FWD_CNT..])
|
||||
}
|
||||
|
||||
pub fn set_fwd_cnt(&mut self, fwd_cnt: u32) -> &mut Self {
|
||||
LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_FWD_CNT..], fwd_cnt);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
|
||||
use super::super::tests::TestContext;
|
||||
use super::*;
|
||||
use crate::queue::tests::VirtqDesc as GuestQDesc;
|
||||
use crate::vsock::defs::MAX_PKT_BUF_SIZE;
|
||||
use crate::VIRTQ_DESC_F_WRITE;
|
||||
|
||||
macro_rules! create_context {
|
||||
($test_ctx:ident, $handler_ctx:ident) => {
|
||||
let $test_ctx = TestContext::new();
|
||||
let mut $handler_ctx = $test_ctx.create_epoll_handler_context();
|
||||
// For TX packets, hdr.len should be set to a valid value.
|
||||
set_pkt_len(1024, &$handler_ctx.guest_txvq.dtable[0], &$test_ctx.mem);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! expect_asm_error {
|
||||
(tx, $test_ctx:expr, $handler_ctx:expr, $err:pat) => {
|
||||
expect_asm_error!($test_ctx, $handler_ctx, $err, from_tx_virtq_head, 1);
|
||||
};
|
||||
(rx, $test_ctx:expr, $handler_ctx:expr, $err:pat) => {
|
||||
expect_asm_error!($test_ctx, $handler_ctx, $err, from_rx_virtq_head, 0);
|
||||
};
|
||||
($test_ctx:expr, $handler_ctx:expr, $err:pat, $ctor:ident, $vq:expr) => {
|
||||
match VsockPacket::$ctor(
|
||||
&$handler_ctx.handler.queues[$vq]
|
||||
.iter(&$test_ctx.mem)
|
||||
.next()
|
||||
.unwrap(),
|
||||
) {
|
||||
Err($err) => (),
|
||||
Ok(_) => panic!("Packet assembly should've failed!"),
|
||||
Err(other) => panic!("Packet assembly failed with: {:?}", other),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn set_pkt_len(len: u32, guest_desc: &GuestQDesc, mem: &GuestMemoryMmap) {
|
||||
let hdr_gpa = guest_desc.addr.get();
|
||||
let hdr_ptr = mem.get_host_address(GuestAddress(hdr_gpa)).unwrap() as *mut u8;
|
||||
let len_ptr = unsafe { hdr_ptr.add(HDROFF_LEN) };
|
||||
|
||||
LittleEndian::write_u32(unsafe { std::slice::from_raw_parts_mut(len_ptr, 4) }, len);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn test_tx_packet_assembly() {
|
||||
// Test case: successful TX packet assembly.
|
||||
{
|
||||
create_context!(test_ctx, handler_ctx);
|
||||
|
||||
let pkt = VsockPacket::from_tx_virtq_head(
|
||||
&handler_ctx.handler.queues[1]
|
||||
.iter(&test_ctx.mem)
|
||||
.next()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(pkt.hdr().len(), VSOCK_PKT_HDR_SIZE);
|
||||
assert_eq!(
|
||||
pkt.buf().unwrap().len(),
|
||||
handler_ctx.guest_txvq.dtable[1].len.get() as usize
|
||||
);
|
||||
}
|
||||
|
||||
// Test case: error on write-only hdr descriptor.
|
||||
{
|
||||
create_context!(test_ctx, handler_ctx);
|
||||
handler_ctx.guest_txvq.dtable[0]
|
||||
.flags
|
||||
.set(VIRTQ_DESC_F_WRITE);
|
||||
expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::UnreadableDescriptor);
|
||||
}
|
||||
|
||||
// Test case: header descriptor has insufficient space to hold the packet header.
|
||||
{
|
||||
create_context!(test_ctx, handler_ctx);
|
||||
handler_ctx.guest_txvq.dtable[0]
|
||||
.len
|
||||
.set(VSOCK_PKT_HDR_SIZE as u32 - 1);
|
||||
expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::HdrDescTooSmall(_));
|
||||
}
|
||||
|
||||
// Test case: zero-length TX packet.
|
||||
{
|
||||
create_context!(test_ctx, handler_ctx);
|
||||
set_pkt_len(0, &handler_ctx.guest_txvq.dtable[0], &test_ctx.mem);
|
||||
let mut pkt = VsockPacket::from_tx_virtq_head(
|
||||
&handler_ctx.handler.queues[1]
|
||||
.iter(&test_ctx.mem)
|
||||
.next()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(pkt.buf().is_none());
|
||||
assert!(pkt.buf_mut().is_none());
|
||||
}
|
||||
|
||||
// Test case: TX packet has more data than we can handle.
|
||||
{
|
||||
create_context!(test_ctx, handler_ctx);
|
||||
set_pkt_len(
|
||||
MAX_PKT_BUF_SIZE as u32 + 1,
|
||||
&handler_ctx.guest_txvq.dtable[0],
|
||||
&test_ctx.mem,
|
||||
);
|
||||
expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::InvalidPktLen(_));
|
||||
}
|
||||
|
||||
// Test case:
|
||||
// - packet header advertises some data length; and
|
||||
// - the data descriptor is missing.
|
||||
{
|
||||
create_context!(test_ctx, handler_ctx);
|
||||
set_pkt_len(1024, &handler_ctx.guest_txvq.dtable[0], &test_ctx.mem);
|
||||
handler_ctx.guest_txvq.dtable[0].flags.set(0);
|
||||
expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::BufDescMissing);
|
||||
}
|
||||
|
||||
// Test case: error on write-only buf descriptor.
|
||||
{
|
||||
create_context!(test_ctx, handler_ctx);
|
||||
handler_ctx.guest_txvq.dtable[1]
|
||||
.flags
|
||||
.set(VIRTQ_DESC_F_WRITE);
|
||||
expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::UnreadableDescriptor);
|
||||
}
|
||||
|
||||
// Test case: the buffer descriptor cannot fit all the data advertised by the the
|
||||
// packet header `len` field.
|
||||
{
|
||||
create_context!(test_ctx, handler_ctx);
|
||||
set_pkt_len(8 * 1024, &handler_ctx.guest_txvq.dtable[0], &test_ctx.mem);
|
||||
handler_ctx.guest_txvq.dtable[1].len.set(4 * 1024);
|
||||
expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::BufDescTooSmall);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rx_packet_assembly() {
|
||||
// Test case: successful RX packet assembly.
|
||||
{
|
||||
create_context!(test_ctx, handler_ctx);
|
||||
let pkt = VsockPacket::from_rx_virtq_head(
|
||||
&handler_ctx.handler.queues[0]
|
||||
.iter(&test_ctx.mem)
|
||||
.next()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(pkt.hdr().len(), VSOCK_PKT_HDR_SIZE);
|
||||
assert_eq!(
|
||||
pkt.buf().unwrap().len(),
|
||||
handler_ctx.guest_rxvq.dtable[1].len.get() as usize
|
||||
);
|
||||
}
|
||||
|
||||
// Test case: read-only RX packet header.
|
||||
{
|
||||
create_context!(test_ctx, handler_ctx);
|
||||
handler_ctx.guest_rxvq.dtable[0].flags.set(0);
|
||||
expect_asm_error!(rx, test_ctx, handler_ctx, VsockError::UnwritableDescriptor);
|
||||
}
|
||||
|
||||
// Test case: RX descriptor head cannot fit the entire packet header.
|
||||
{
|
||||
create_context!(test_ctx, handler_ctx);
|
||||
handler_ctx.guest_rxvq.dtable[0]
|
||||
.len
|
||||
.set(VSOCK_PKT_HDR_SIZE as u32 - 1);
|
||||
expect_asm_error!(rx, test_ctx, handler_ctx, VsockError::HdrDescTooSmall(_));
|
||||
}
|
||||
|
||||
// Test case: RX descriptor chain is missing the packet buffer descriptor.
|
||||
{
|
||||
create_context!(test_ctx, handler_ctx);
|
||||
handler_ctx.guest_rxvq.dtable[0]
|
||||
.flags
|
||||
.set(VIRTQ_DESC_F_WRITE);
|
||||
expect_asm_error!(rx, test_ctx, handler_ctx, VsockError::BufDescMissing);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn test_packet_hdr_accessors() {
|
||||
const SRC_CID: u64 = 1;
|
||||
const DST_CID: u64 = 2;
|
||||
const SRC_PORT: u32 = 3;
|
||||
const DST_PORT: u32 = 4;
|
||||
const LEN: u32 = 5;
|
||||
const TYPE: u16 = 6;
|
||||
const OP: u16 = 7;
|
||||
const FLAGS: u32 = 8;
|
||||
const BUF_ALLOC: u32 = 9;
|
||||
const FWD_CNT: u32 = 10;
|
||||
|
||||
create_context!(test_ctx, handler_ctx);
|
||||
let mut pkt = VsockPacket::from_rx_virtq_head(
|
||||
&handler_ctx.handler.queues[0]
|
||||
.iter(&test_ctx.mem)
|
||||
.next()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Test field accessors.
|
||||
pkt.set_src_cid(SRC_CID)
|
||||
.set_dst_cid(DST_CID)
|
||||
.set_src_port(SRC_PORT)
|
||||
.set_dst_port(DST_PORT)
|
||||
.set_len(LEN)
|
||||
.set_type(TYPE)
|
||||
.set_op(OP)
|
||||
.set_flags(FLAGS)
|
||||
.set_buf_alloc(BUF_ALLOC)
|
||||
.set_fwd_cnt(FWD_CNT);
|
||||
|
||||
assert_eq!(pkt.src_cid(), SRC_CID);
|
||||
assert_eq!(pkt.dst_cid(), DST_CID);
|
||||
assert_eq!(pkt.src_port(), SRC_PORT);
|
||||
assert_eq!(pkt.dst_port(), DST_PORT);
|
||||
assert_eq!(pkt.len(), LEN);
|
||||
assert_eq!(pkt.type_(), TYPE);
|
||||
assert_eq!(pkt.op(), OP);
|
||||
assert_eq!(pkt.flags(), FLAGS);
|
||||
assert_eq!(pkt.buf_alloc(), BUF_ALLOC);
|
||||
assert_eq!(pkt.fwd_cnt(), FWD_CNT);
|
||||
|
||||
// Test individual flag setting.
|
||||
let flags = pkt.flags() | 0b1000;
|
||||
pkt.set_flag(0b1000);
|
||||
assert_eq!(pkt.flags(), flags);
|
||||
|
||||
// Test packet header as-slice access.
|
||||
//
|
||||
|
||||
assert_eq!(pkt.hdr().len(), VSOCK_PKT_HDR_SIZE);
|
||||
|
||||
assert_eq!(
|
||||
SRC_CID,
|
||||
LittleEndian::read_u64(&pkt.hdr()[HDROFF_SRC_CID..])
|
||||
);
|
||||
assert_eq!(
|
||||
DST_CID,
|
||||
LittleEndian::read_u64(&pkt.hdr()[HDROFF_DST_CID..])
|
||||
);
|
||||
assert_eq!(
|
||||
SRC_PORT,
|
||||
LittleEndian::read_u32(&pkt.hdr()[HDROFF_SRC_PORT..])
|
||||
);
|
||||
assert_eq!(
|
||||
DST_PORT,
|
||||
LittleEndian::read_u32(&pkt.hdr()[HDROFF_DST_PORT..])
|
||||
);
|
||||
assert_eq!(LEN, LittleEndian::read_u32(&pkt.hdr()[HDROFF_LEN..]));
|
||||
assert_eq!(TYPE, LittleEndian::read_u16(&pkt.hdr()[HDROFF_TYPE..]));
|
||||
assert_eq!(OP, LittleEndian::read_u16(&pkt.hdr()[HDROFF_OP..]));
|
||||
assert_eq!(FLAGS, LittleEndian::read_u32(&pkt.hdr()[HDROFF_FLAGS..]));
|
||||
assert_eq!(
|
||||
BUF_ALLOC,
|
||||
LittleEndian::read_u32(&pkt.hdr()[HDROFF_BUF_ALLOC..])
|
||||
);
|
||||
assert_eq!(
|
||||
FWD_CNT,
|
||||
LittleEndian::read_u32(&pkt.hdr()[HDROFF_FWD_CNT..])
|
||||
);
|
||||
|
||||
assert_eq!(pkt.hdr_mut().len(), VSOCK_PKT_HDR_SIZE);
|
||||
for b in pkt.hdr_mut() {
|
||||
*b = 0;
|
||||
}
|
||||
assert_eq!(pkt.src_cid(), 0);
|
||||
assert_eq!(pkt.dst_cid(), 0);
|
||||
assert_eq!(pkt.src_port(), 0);
|
||||
assert_eq!(pkt.dst_port(), 0);
|
||||
assert_eq!(pkt.len(), 0);
|
||||
assert_eq!(pkt.type_(), 0);
|
||||
assert_eq!(pkt.op(), 0);
|
||||
assert_eq!(pkt.flags(), 0);
|
||||
assert_eq!(pkt.buf_alloc(), 0);
|
||||
assert_eq!(pkt.fwd_cnt(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_packet_buf() {
|
||||
create_context!(test_ctx, handler_ctx);
|
||||
let mut pkt = VsockPacket::from_rx_virtq_head(
|
||||
&handler_ctx.handler.queues[0]
|
||||
.iter(&test_ctx.mem)
|
||||
.next()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
pkt.buf().unwrap().len(),
|
||||
handler_ctx.guest_rxvq.dtable[1].len.get() as usize
|
||||
);
|
||||
assert_eq!(
|
||||
pkt.buf_mut().unwrap().len(),
|
||||
handler_ctx.guest_rxvq.dtable[1].len.get() as usize
|
||||
);
|
||||
|
||||
for i in 0..pkt.buf().unwrap().len() {
|
||||
pkt.buf_mut().unwrap()[i] = (i % 0x100) as u8;
|
||||
assert_eq!(pkt.buf().unwrap()[i], (i % 0x100) as u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
vm-virtio/src/vsock/unix/mod.rs
Normal file
50
vm-virtio/src/vsock/unix/mod.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
/// This module implements the Unix Domain Sockets backend for vsock - a mediator between
|
||||
/// guest-side AF_VSOCK sockets and host-side AF_UNIX sockets. The heavy lifting is performed by
|
||||
/// `muxer::VsockMuxer`, a connection multiplexer that uses `super::csm::VsockConnection` for
|
||||
/// handling vsock connection states.
|
||||
/// Check out `muxer.rs` for a more detailed explanation of the inner workings of this backend.
|
||||
///
|
||||
mod muxer;
|
||||
mod muxer_killq;
|
||||
mod muxer_rxq;
|
||||
|
||||
pub use muxer::VsockMuxer as VsockUnixBackend;
|
||||
pub use Error as VsockUnixError;
|
||||
|
||||
mod defs {
|
||||
/// Maximum number of established connections that we can handle.
|
||||
pub const MAX_CONNECTIONS: usize = 1023;
|
||||
|
||||
/// Size of the muxer RX packet queue.
|
||||
pub const MUXER_RXQ_SIZE: usize = 256;
|
||||
|
||||
/// Size of the muxer connection kill queue.
|
||||
pub const MUXER_KILLQ_SIZE: usize = 128;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Error registering a new epoll-listening FD.
|
||||
EpollAdd(std::io::Error),
|
||||
/// Error creating an epoll FD.
|
||||
EpollFdCreate(std::io::Error),
|
||||
/// The host made an invalid vsock port connection request.
|
||||
InvalidPortRequest,
|
||||
/// Error accepting a new connection from the host-side Unix socket.
|
||||
UnixAccept(std::io::Error),
|
||||
/// Error binding to the host-side Unix socket.
|
||||
UnixBind(std::io::Error),
|
||||
/// Error connecting to a host-side Unix socket.
|
||||
UnixConnect(std::io::Error),
|
||||
/// Error reading from host-side Unix socket.
|
||||
UnixRead(std::io::Error),
|
||||
/// Muxer connection limit reached.
|
||||
TooManyConnections,
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
type MuxerConnection = super::csm::VsockConnection<std::os::unix::net::UnixStream>;
|
||||
1312
vm-virtio/src/vsock/unix/muxer.rs
Normal file
1312
vm-virtio/src/vsock/unix/muxer.rs
Normal file
File diff suppressed because it is too large
Load Diff
140
vm-virtio/src/vsock/unix/muxer_killq.rs
Normal file
140
vm-virtio/src/vsock/unix/muxer_killq.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
/// `MuxerKillQ` implements a helper object that `VsockMuxer` can use for scheduling forced
|
||||
/// connection termination. I.e. after one peer issues a clean shutdown request
|
||||
/// (VSOCK_OP_SHUTDOWN), the concerned connection is queued for termination (VSOCK_OP_RST) in
|
||||
/// the near future (herein implemented via an expiring timer).
|
||||
///
|
||||
/// Whenever the muxer needs to schedule a connection for termination, it pushes it (or rather
|
||||
/// an identifier - the connection key) to this queue. A subsequent pop() operation will
|
||||
/// succeed if and only if the first connection in the queue is ready to be terminated (i.e.
|
||||
/// its kill timer expired).
|
||||
///
|
||||
/// Without using this queue, the muxer would have to walk its entire connection pool
|
||||
/// (hashmap), whenever it needs to check for expired kill timers. With this queue, both
|
||||
/// scheduling and termination are performed in constant time. However, since we don't want to
|
||||
/// waste space on a kill queue that's as big as the connection hashmap itself, it is possible
|
||||
/// that this queue may become full at times. We call this kill queue "synchronized" if we are
|
||||
/// certain that all connections that are awaiting termination are present in the queue. This
|
||||
/// means a simple constant-time pop() operation is enough to check whether any connections
|
||||
/// need to be terminated. When the kill queue becomes full, though, pushing fails, so
|
||||
/// connections that should be terminated are left out. The queue is not synchronized anymore.
|
||||
/// When that happens, the muxer will first drain the queue, and then replace it with a new
|
||||
/// queue, created by walking the connection pool, looking for connections that will be
|
||||
/// expiring in the future.
|
||||
///
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::time::Instant;
|
||||
|
||||
use super::defs;
|
||||
use super::muxer::ConnMapKey;
|
||||
use super::MuxerConnection;
|
||||
|
||||
/// A kill queue item, holding the connection key and the scheduled time for termination.
|
||||
///
|
||||
#[derive(Clone, Copy)]
|
||||
struct MuxerKillQItem {
|
||||
key: ConnMapKey,
|
||||
kill_time: Instant,
|
||||
}
|
||||
|
||||
/// The connection kill queue: a FIFO structure, storing the connections that are scheduled for
|
||||
/// termination.
|
||||
///
|
||||
pub struct MuxerKillQ {
|
||||
/// The kill queue contents.
|
||||
q: VecDeque<MuxerKillQItem>,
|
||||
|
||||
/// The kill queue sync status:
|
||||
/// - when true, all connections that are awaiting termination are guaranteed to be in this
|
||||
/// queue;
|
||||
/// - when false, some connections may have been left out.
|
||||
///
|
||||
synced: bool,
|
||||
}
|
||||
|
||||
impl MuxerKillQ {
|
||||
const SIZE: usize = defs::MUXER_KILLQ_SIZE;
|
||||
|
||||
/// Trivial kill queue constructor.
|
||||
///
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
q: VecDeque::with_capacity(Self::SIZE),
|
||||
synced: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a kill queue by walking the connection pool, looking for connections that are
|
||||
/// set to expire at some point in the future.
|
||||
/// Note: if more than `Self::SIZE` connections are found, the queue will be created in an
|
||||
/// out-of-sync state, and will be discarded after it is emptied.
|
||||
///
|
||||
pub fn from_conn_map(conn_map: &HashMap<ConnMapKey, MuxerConnection>) -> Self {
|
||||
let mut q_buf: Vec<MuxerKillQItem> = Vec::with_capacity(Self::SIZE);
|
||||
let mut synced = true;
|
||||
for (key, conn) in conn_map.iter() {
|
||||
if !conn.will_expire() {
|
||||
continue;
|
||||
}
|
||||
if q_buf.len() >= Self::SIZE {
|
||||
synced = false;
|
||||
break;
|
||||
}
|
||||
q_buf.push(MuxerKillQItem {
|
||||
key: *key,
|
||||
kill_time: conn.expiry().unwrap(),
|
||||
});
|
||||
}
|
||||
q_buf.sort_unstable_by_key(|it| it.kill_time);
|
||||
Self {
|
||||
q: q_buf.into(),
|
||||
synced,
|
||||
}
|
||||
}
|
||||
|
||||
/// Push a connection key to the queue, scheduling it for termination at
|
||||
/// `CONN_SHUTDOWN_TIMEOUT_MS` from now (the push time).
|
||||
///
|
||||
pub fn push(&mut self, key: ConnMapKey, kill_time: Instant) {
|
||||
if !self.is_synced() || self.is_full() {
|
||||
self.synced = false;
|
||||
return;
|
||||
}
|
||||
self.q.push_back(MuxerKillQItem { key, kill_time });
|
||||
}
|
||||
|
||||
/// Attempt to pop an expired connection from the kill queue.
|
||||
///
|
||||
/// This will succeed and return a connection key, only if the connection at the front of
|
||||
/// the queue has expired. Otherwise, `None` is returned.
|
||||
///
|
||||
pub fn pop(&mut self) -> Option<ConnMapKey> {
|
||||
if let Some(item) = self.q.front() {
|
||||
if Instant::now() > item.kill_time {
|
||||
return Some(self.q.pop_front().unwrap().key);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Check if the kill queue is synchronized with the connection pool.
|
||||
///
|
||||
pub fn is_synced(&self) -> bool {
|
||||
self.synced
|
||||
}
|
||||
|
||||
/// Check if the kill queue is empty, obviously.
|
||||
///
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.q.len() == 0
|
||||
}
|
||||
|
||||
/// Check if the kill queue is full.
|
||||
///
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.q.len() == Self::SIZE
|
||||
}
|
||||
}
|
||||
139
vm-virtio/src/vsock/unix/muxer_rxq.rs
Normal file
139
vm-virtio/src/vsock/unix/muxer_rxq.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
/// `MuxerRxQ` implements a helper object that `VsockMuxer` can use for queuing RX (host -> guest)
|
||||
/// packets (or rather instructions on how to build said packets).
|
||||
///
|
||||
/// Under ideal operation, every connection, that has pending RX data, will be present in the muxer
|
||||
/// RX queue. However, since the RX queue is smaller than the connection pool, it may, under some
|
||||
/// conditions, become full, meaning that it can no longer account for all the connections that can
|
||||
/// yield RX data. When that happens, we say that it is no longer "synchronized" (i.e. with the
|
||||
/// connection pool). A desynchronized RX queue still holds valid data, and the muxer will
|
||||
/// continue to pop packets from it. However, when a desynchronized queue is drained, additional
|
||||
/// data may still be available, so the muxer will have to perform a more costly walk of the entire
|
||||
/// connection pool to find it. This walk is performed here, as part of building an RX queue from
|
||||
/// the connection pool. When an out-of-sync is drained, the muxer will discard it, and attempt to
|
||||
/// rebuild a synced one.
|
||||
///
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
use super::super::VsockChannel;
|
||||
use super::defs;
|
||||
use super::muxer::{ConnMapKey, MuxerRx};
|
||||
use super::MuxerConnection;
|
||||
|
||||
/// The muxer RX queue.
|
||||
///
|
||||
pub struct MuxerRxQ {
|
||||
/// The RX queue data.
|
||||
q: VecDeque<MuxerRx>,
|
||||
/// The RX queue sync status.
|
||||
synced: bool,
|
||||
}
|
||||
|
||||
impl MuxerRxQ {
|
||||
const SIZE: usize = defs::MUXER_RXQ_SIZE;
|
||||
|
||||
/// Trivial RX queue constructor.
|
||||
///
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
q: VecDeque::with_capacity(Self::SIZE),
|
||||
synced: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to build an RX queue, that is synchronized to the connection pool.
|
||||
/// Note: the resulting queue may still be desynchronized, if there are too many connections
|
||||
/// that have pending RX data. In that case, the muxer will first drain this queue, and
|
||||
/// then try again to build a synchronized one.
|
||||
///
|
||||
pub fn from_conn_map(conn_map: &HashMap<ConnMapKey, MuxerConnection>) -> Self {
|
||||
let mut q = VecDeque::new();
|
||||
let mut synced = true;
|
||||
|
||||
for (key, conn) in conn_map.iter() {
|
||||
if !conn.has_pending_rx() {
|
||||
continue;
|
||||
}
|
||||
if q.len() >= Self::SIZE {
|
||||
synced = false;
|
||||
break;
|
||||
}
|
||||
q.push_back(MuxerRx::ConnRx(*key));
|
||||
}
|
||||
Self { q, synced }
|
||||
}
|
||||
|
||||
/// Push a new RX item to the queue.
|
||||
///
|
||||
/// A push will fail when:
|
||||
/// - trying to push a connection key onto an out-of-sync, or full queue; or
|
||||
/// - trying to push an RST onto a queue already full of RSTs.
|
||||
/// RSTs take precedence over connections, because connections can always be queried for
|
||||
/// pending RX data later. Aside from this queue, there is no other storage for RSTs, so
|
||||
/// failing to push one means that we have to drop the packet.
|
||||
///
|
||||
/// Returns:
|
||||
/// - `true` if the new item has been successfully queued; or
|
||||
/// - `false` if there was no room left in the queue.
|
||||
///
|
||||
pub fn push(&mut self, rx: MuxerRx) -> bool {
|
||||
// Pushing to a non-full, synchronized queue will always succeed.
|
||||
if self.is_synced() && !self.is_full() {
|
||||
self.q.push_back(rx);
|
||||
return true;
|
||||
}
|
||||
|
||||
match rx {
|
||||
MuxerRx::RstPkt { .. } => {
|
||||
// If we just failed to push an RST packet, we'll look through the queue, trying to
|
||||
// find a connection key that we could evict. This way, the queue does lose sync,
|
||||
// but we don't drop any packets.
|
||||
for qi in self.q.iter_mut().rev() {
|
||||
if let MuxerRx::ConnRx(_) = qi {
|
||||
*qi = rx;
|
||||
self.synced = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
MuxerRx::ConnRx(_) => {
|
||||
self.synced = false;
|
||||
}
|
||||
};
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Pop an RX item from the front of the queue.
|
||||
///
|
||||
pub fn pop(&mut self) -> Option<MuxerRx> {
|
||||
self.q.pop_front()
|
||||
}
|
||||
|
||||
/// Check if the RX queue is synchronized with the connection pool.
|
||||
///
|
||||
pub fn is_synced(&self) -> bool {
|
||||
self.synced
|
||||
}
|
||||
|
||||
/// Get the total number of items in the queue.
|
||||
///
|
||||
pub fn len(&self) -> usize {
|
||||
self.q.len()
|
||||
}
|
||||
|
||||
/// Check if the queue is empty.
|
||||
///
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Check if the queue is full.
|
||||
///
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.len() == Self::SIZE
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,11 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = ["acpi"]
|
||||
acpi = ["acpi_tables"]
|
||||
default = []
|
||||
acpi = ["acpi_tables","devices/acpi", "arch/acpi"]
|
||||
pci_support = ["pci", "vfio", "vm-virtio/pci_support"]
|
||||
mmio_support = ["vm-virtio/mmio_support"]
|
||||
cmos = ["devices/cmos"]
|
||||
|
||||
[dependencies]
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
@@ -15,16 +18,22 @@ devices = { path = "../devices" }
|
||||
epoll = "4.1.0"
|
||||
kvm-bindings = "0.1.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.62"
|
||||
log = "0.4.8"
|
||||
micro_http = { git = "https://github.com/firecracker-microvm/firecracker", branch = "master" }
|
||||
net_util = { path = "../net_util" }
|
||||
pci = {path = "../pci"}
|
||||
pci = {path = "../pci", optional = true}
|
||||
qcow = { path = "../qcow" }
|
||||
vfio = { path = "../vfio" }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vfio = { path = "../vfio", optional = true }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
signal-hook = "0.1.10"
|
||||
threadpool = "1.0"
|
||||
|
||||
[dependencies.linux-loader]
|
||||
git = "https://github.com/rust-vmm/linux-loader"
|
||||
|
||||
138
vmm/src/api/http.rs
Normal file
138
vmm/src/api/http.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
extern crate threadpool;
|
||||
|
||||
use crate::api::http_endpoint::{VmActionHandler, VmCreate, VmInfo, VmmShutdown};
|
||||
use crate::api::{ApiRequest, VmAction};
|
||||
use crate::{Error, Result};
|
||||
use micro_http::{HttpConnection, Request, Response, StatusCode, Version};
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::net::UnixListener;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::thread;
|
||||
use threadpool::ThreadPool;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const HTTP_ROOT: &str = "/api/v1";
|
||||
const NUM_THREADS: usize = 4;
|
||||
|
||||
/// An HTTP endpoint handler interface
|
||||
pub trait EndpointHandler: Sync + Send {
|
||||
/// Handles an HTTP request.
|
||||
/// After parsing the request, the handler could decide to send an
|
||||
/// associated API request down to the VMM API server to e.g. create
|
||||
/// or start a VM. The request will block waiting for an answer from the
|
||||
/// API server and translate that into an HTTP response.
|
||||
fn handle_request(
|
||||
&self,
|
||||
req: &Request,
|
||||
api_notifier: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
) -> Response;
|
||||
}
|
||||
|
||||
/// An HTTP routes structure.
|
||||
pub struct HttpRoutes {
|
||||
/// routes is a hash table mapping endpoint URIs to their endpoint handlers.
|
||||
pub routes: HashMap<String, Box<dyn EndpointHandler + Sync + Send>>,
|
||||
}
|
||||
|
||||
macro_rules! endpoint {
|
||||
($path:expr) => {
|
||||
format!("{}{}", HTTP_ROOT, $path)
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// HTTP_ROUTES contain all the cloud-hypervisor HTTP routes.
|
||||
pub static ref HTTP_ROUTES: HttpRoutes = {
|
||||
let mut r = HttpRoutes {
|
||||
routes: HashMap::new(),
|
||||
};
|
||||
|
||||
r.routes.insert(endpoint!("/vm.create"), Box::new(VmCreate {}));
|
||||
r.routes.insert(endpoint!("/vm.boot"), Box::new(VmActionHandler::new(VmAction::Boot)));
|
||||
r.routes.insert(endpoint!("/vm.delete"), Box::new(VmActionHandler::new(VmAction::Delete)));
|
||||
r.routes.insert(endpoint!("/vm.info"), Box::new(VmInfo {}));
|
||||
r.routes.insert(endpoint!("/vm.pause"), Box::new(VmActionHandler::new(VmAction::Pause)));
|
||||
r.routes.insert(endpoint!("/vm.resume"), Box::new(VmActionHandler::new(VmAction::Resume)));
|
||||
r.routes.insert(endpoint!("/vm.shutdown"), Box::new(VmActionHandler::new(VmAction::Shutdown)));
|
||||
r.routes.insert(endpoint!("/vm.reboot"), Box::new(VmActionHandler::new(VmAction::Reboot)));
|
||||
r.routes.insert(endpoint!("/vmm.shutdown"), Box::new(VmmShutdown {}));
|
||||
|
||||
r
|
||||
};
|
||||
}
|
||||
|
||||
fn http_serve<T: Read + Write>(
|
||||
http_connection: &mut HttpConnection<T>,
|
||||
api_notifier: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
) {
|
||||
if http_connection.try_read().is_err() {
|
||||
http_connection.enqueue_response(Response::new(
|
||||
Version::Http11,
|
||||
StatusCode::InternalServerError,
|
||||
));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
while let Some(request) = http_connection.pop_parsed_request() {
|
||||
let sender = api_sender.clone();
|
||||
let path = request.uri().get_abs_path().to_string();
|
||||
let mut response = match HTTP_ROUTES.routes.get(&path) {
|
||||
Some(route) => match api_notifier.try_clone() {
|
||||
Ok(notifier) => route.handle_request(&request, notifier, sender),
|
||||
Err(_) => Response::new(Version::Http11, StatusCode::InternalServerError),
|
||||
},
|
||||
None => Response::new(Version::Http11, StatusCode::NotFound),
|
||||
};
|
||||
|
||||
response.set_server("Cloud Hypervisor API");
|
||||
http_connection.enqueue_response(response);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_http_thread(
|
||||
path: &str,
|
||||
api_notifier: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
) -> Result<thread::JoinHandle<Result<()>>> {
|
||||
let listener = UnixListener::bind(path).map_err(Error::Bind)?;
|
||||
let pool = ThreadPool::new(NUM_THREADS);
|
||||
|
||||
thread::Builder::new()
|
||||
.name("http-server".to_string())
|
||||
.spawn(move || {
|
||||
for stream in listener.incoming() {
|
||||
match stream {
|
||||
Ok(s) => {
|
||||
let sender = api_sender.clone();
|
||||
let notifier = api_notifier.try_clone().map_err(Error::EventFdClone)?;
|
||||
|
||||
pool.execute(move || {
|
||||
let mut http_connection = HttpConnection::new(s);
|
||||
|
||||
http_serve(&mut http_connection, notifier, sender);
|
||||
|
||||
// It's ok to panic from a threadpool managed thread,
|
||||
// it won't make parent threads crash.
|
||||
http_connection.try_write().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
pool.join();
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(Error::HttpThreadSpawn)
|
||||
}
|
||||
192
vmm/src/api/http_endpoint.rs
Normal file
192
vmm/src/api/http_endpoint.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use crate::api::http::EndpointHandler;
|
||||
use crate::api::{
|
||||
vm_boot, vm_create, vm_delete, vm_info, vm_pause, vm_reboot, vm_resume, vm_shutdown,
|
||||
vmm_shutdown, ApiError, ApiRequest, ApiResult, VmAction, VmConfig,
|
||||
};
|
||||
use micro_http::{Body, Method, Request, Response, StatusCode, Version};
|
||||
use serde_json::Error as SerdeError;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::Arc;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// Errors associated with VMM management
|
||||
#[derive(Debug)]
|
||||
pub enum HttpError {
|
||||
/// API request receive error
|
||||
SerdeJsonDeserialize(SerdeError),
|
||||
|
||||
/// Could not create a VM
|
||||
VmCreate(ApiError),
|
||||
|
||||
/// Could not boot a VM
|
||||
VmBoot(ApiError),
|
||||
|
||||
/// Could not get the VM information
|
||||
VmInfo(ApiError),
|
||||
|
||||
/// Could not pause the VM
|
||||
VmPause(ApiError),
|
||||
|
||||
/// Could not pause the VM
|
||||
VmResume(ApiError),
|
||||
|
||||
/// Could not shut a VM down
|
||||
VmShutdown(ApiError),
|
||||
|
||||
/// Could not reboot a VM
|
||||
VmReboot(ApiError),
|
||||
|
||||
/// Could not act on a VM
|
||||
VmAction(ApiError),
|
||||
|
||||
/// Could not shut the VMM down
|
||||
VmmShutdown(ApiError),
|
||||
}
|
||||
|
||||
fn error_response(error: HttpError, status: StatusCode) -> Response {
|
||||
let mut response = Response::new(Version::Http11, status);
|
||||
response.set_body(Body::new(format!("{:?}", error)));
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
// /api/v1/vm.create handler
|
||||
pub struct VmCreate {}
|
||||
|
||||
impl EndpointHandler for VmCreate {
|
||||
fn handle_request(
|
||||
&self,
|
||||
req: &Request,
|
||||
api_notifier: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
) -> Response {
|
||||
match req.method() {
|
||||
Method::Put => {
|
||||
match &req.body {
|
||||
Some(body) => {
|
||||
// Deserialize into a VmConfig
|
||||
let vm_config: VmConfig = match serde_json::from_slice(body.raw())
|
||||
.map_err(HttpError::SerdeJsonDeserialize)
|
||||
{
|
||||
Ok(config) => config,
|
||||
Err(e) => return error_response(e, StatusCode::BadRequest),
|
||||
};
|
||||
|
||||
// Call vm_create()
|
||||
match vm_create(api_notifier, api_sender, Arc::new(vm_config))
|
||||
.map_err(HttpError::VmCreate)
|
||||
{
|
||||
Ok(_) => Response::new(Version::Http11, StatusCode::OK),
|
||||
Err(e) => error_response(e, StatusCode::InternalServerError),
|
||||
}
|
||||
}
|
||||
|
||||
None => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
|
||||
_ => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Common handler for boot, shutdown and reboot
|
||||
pub struct VmActionHandler {
|
||||
action_fn: VmActionFn,
|
||||
}
|
||||
|
||||
type VmActionFn = Box<dyn Fn(EventFd, Sender<ApiRequest>) -> ApiResult<()> + Send + Sync>;
|
||||
|
||||
impl VmActionHandler {
|
||||
pub fn new(action: VmAction) -> Self {
|
||||
let action_fn = Box::new(match action {
|
||||
VmAction::Boot => vm_boot,
|
||||
VmAction::Delete => vm_delete,
|
||||
VmAction::Shutdown => vm_shutdown,
|
||||
VmAction::Reboot => vm_reboot,
|
||||
VmAction::Pause => vm_pause,
|
||||
VmAction::Resume => vm_resume,
|
||||
});
|
||||
|
||||
VmActionHandler { action_fn }
|
||||
}
|
||||
}
|
||||
|
||||
impl EndpointHandler for VmActionHandler {
|
||||
fn handle_request(
|
||||
&self,
|
||||
req: &Request,
|
||||
api_notifier: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
) -> Response {
|
||||
match req.method() {
|
||||
Method::Put => {
|
||||
match (self.action_fn)(api_notifier, api_sender).map_err(|e| match e {
|
||||
ApiError::VmBoot(_) => HttpError::VmBoot(e),
|
||||
ApiError::VmShutdown(_) => HttpError::VmShutdown(e),
|
||||
ApiError::VmReboot(_) => HttpError::VmReboot(e),
|
||||
ApiError::VmPause(_) => HttpError::VmPause(e),
|
||||
ApiError::VmResume(_) => HttpError::VmResume(e),
|
||||
_ => HttpError::VmAction(e),
|
||||
}) {
|
||||
Ok(_) => Response::new(Version::Http11, StatusCode::OK),
|
||||
Err(e) => error_response(e, StatusCode::InternalServerError),
|
||||
}
|
||||
}
|
||||
_ => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /api/v1/vm.info handler
|
||||
pub struct VmInfo {}
|
||||
|
||||
impl EndpointHandler for VmInfo {
|
||||
fn handle_request(
|
||||
&self,
|
||||
req: &Request,
|
||||
api_notifier: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
) -> Response {
|
||||
match req.method() {
|
||||
Method::Get => match vm_info(api_notifier, api_sender).map_err(HttpError::VmInfo) {
|
||||
Ok(info) => {
|
||||
let mut response = Response::new(Version::Http11, StatusCode::OK);
|
||||
let info_serialized = serde_json::to_string(&info).unwrap();
|
||||
|
||||
response.set_body(Body::new(info_serialized));
|
||||
response
|
||||
}
|
||||
Err(e) => error_response(e, StatusCode::InternalServerError),
|
||||
},
|
||||
_ => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /api/v1/vmm.shutdown handler
|
||||
pub struct VmmShutdown {}
|
||||
|
||||
impl EndpointHandler for VmmShutdown {
|
||||
fn handle_request(
|
||||
&self,
|
||||
req: &Request,
|
||||
api_notifier: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
) -> Response {
|
||||
match req.method() {
|
||||
Method::Put => {
|
||||
match vmm_shutdown(api_notifier, api_sender).map_err(HttpError::VmmShutdown) {
|
||||
Ok(_) => Response::new(Version::Http11, StatusCode::OK),
|
||||
Err(e) => error_response(e, StatusCode::InternalServerError),
|
||||
}
|
||||
}
|
||||
_ => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
}
|
||||
278
vmm/src/api/mod.rs
Normal file
278
vmm/src/api/mod.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
//! The internal VMM API for Cloud Hypervisor.
|
||||
//!
|
||||
//! This API is a synchronous, [mpsc](https://doc.rust-lang.org/std/sync/mpsc/)
|
||||
//! based IPC for sending commands to the VMM thread, from other
|
||||
//! Cloud Hypervisor threads. The IPC follows a command-response protocol, i.e.
|
||||
//! each command will receive a response back.
|
||||
//!
|
||||
//! The main Cloud Hypervisor thread creates an API event file descriptor
|
||||
//! to notify the VMM thread about pending API commands, together with an
|
||||
//! API mpsc channel. The former is the IPC control plane, the latter is the
|
||||
//! IPC data plane.
|
||||
//! In order to use the IPC, a Cloud Hypervisor thread needs to have a clone
|
||||
//! of both the API event file descriptor and the channel Sender. Then it must
|
||||
//! go through the following steps:
|
||||
//!
|
||||
//! 1. The thread creates an mpsc channel for receiving the command response.
|
||||
//! 2. The thread sends an ApiRequest to the Sender endpoint. The ApiRequest
|
||||
//! contains the response channel Sender, for the VMM API server to be able
|
||||
//! to send the response back.
|
||||
//! 3. The thread writes to the API event file descriptor to notify the VMM
|
||||
//! API server about a pending command.
|
||||
//! 4. The thread reads the response back from the VMM API server, from the
|
||||
//! response channel Receiver.
|
||||
//! 5. The thread handles the response and forwards potential errors.
|
||||
|
||||
extern crate micro_http;
|
||||
extern crate vmm_sys_util;
|
||||
|
||||
pub use self::http::start_http_thread;
|
||||
|
||||
pub mod http;
|
||||
pub mod http_endpoint;
|
||||
|
||||
use crate::config::VmConfig;
|
||||
use crate::vm::{Error as VmError, VmState};
|
||||
use std::io;
|
||||
use std::sync::mpsc::{channel, RecvError, SendError, Sender};
|
||||
use std::sync::Arc;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// API errors are sent back from the VMM API server through the ApiResponse.
|
||||
#[derive(Debug)]
|
||||
pub enum ApiError {
|
||||
/// Cannot write to EventFd.
|
||||
EventFdWrite(io::Error),
|
||||
|
||||
/// API request send error
|
||||
RequestSend(SendError<ApiRequest>),
|
||||
|
||||
/// Wrong reponse payload type
|
||||
ResponsePayloadType,
|
||||
|
||||
/// API response receive error
|
||||
ResponseRecv(RecvError),
|
||||
|
||||
/// The VM could not boot.
|
||||
VmBoot(VmError),
|
||||
|
||||
/// The VM is already created.
|
||||
VmAlreadyCreated,
|
||||
|
||||
/// The VM could not be created.
|
||||
VmCreate(VmError),
|
||||
|
||||
/// The VM could not be deleted.
|
||||
VmDelete(VmError),
|
||||
|
||||
/// The VM info is not available.
|
||||
VmInfo(VmError),
|
||||
|
||||
/// The VM config is missing.
|
||||
VmMissingConfig,
|
||||
|
||||
/// The VM could not be paused.
|
||||
VmPause(VmError),
|
||||
|
||||
/// The VM could not resume.
|
||||
VmResume(VmError),
|
||||
|
||||
/// The VM is not booted.
|
||||
VmNotBooted,
|
||||
|
||||
/// The VM is not created.
|
||||
VmNotCreated,
|
||||
|
||||
/// The VM could not shutdown.
|
||||
VmShutdown(VmError),
|
||||
|
||||
/// The VM could not reboot.
|
||||
VmReboot(VmError),
|
||||
|
||||
/// The VMM could not shutdown.
|
||||
VmmShutdown(VmError),
|
||||
}
|
||||
pub type ApiResult<T> = std::result::Result<T, ApiError>;
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct VmInfo {
|
||||
pub config: Arc<VmConfig>,
|
||||
pub state: VmState,
|
||||
}
|
||||
|
||||
pub enum ApiResponsePayload {
|
||||
/// No data is sent on the channel.
|
||||
Empty,
|
||||
|
||||
/// Virtual machine information
|
||||
VmInfo(VmInfo),
|
||||
}
|
||||
|
||||
/// This is the response sent by the VMM API server through the mpsc channel.
|
||||
pub type ApiResponse = std::result::Result<ApiResponsePayload, ApiError>;
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum ApiRequest {
|
||||
/// Create the virtual machine. This request payload is a VM configuration
|
||||
/// (VmConfig).
|
||||
/// If the VMM API server could not create the VM, it will send a VmCreate
|
||||
/// error back.
|
||||
VmCreate(Arc<VmConfig>, Sender<ApiResponse>),
|
||||
|
||||
/// Boot the previously created virtual machine.
|
||||
/// If the VM was not previously created, the VMM API server will send a
|
||||
/// VmBoot error back.
|
||||
VmBoot(Sender<ApiResponse>),
|
||||
|
||||
/// Delete the previously created virtual machine.
|
||||
/// If the VM was not previously created, the VMM API server will send a
|
||||
/// VmDelete error back.
|
||||
/// If the VM is booted, we shut it down first.
|
||||
VmDelete(Sender<ApiResponse>),
|
||||
|
||||
/// Request the VM information.
|
||||
VmInfo(Sender<ApiResponse>),
|
||||
|
||||
/// Pause a VM.
|
||||
VmPause(Sender<ApiResponse>),
|
||||
|
||||
/// Resume a VM.
|
||||
VmResume(Sender<ApiResponse>),
|
||||
|
||||
/// Shut the previously booted virtual machine down.
|
||||
/// If the VM was not previously booted or created, the VMM API server
|
||||
/// will send a VmShutdown error back.
|
||||
VmShutdown(Sender<ApiResponse>),
|
||||
|
||||
/// Reboot the previously booted virtual machine.
|
||||
/// If the VM was not previously booted or created, the VMM API server
|
||||
/// will send a VmReboot error back.
|
||||
VmReboot(Sender<ApiResponse>),
|
||||
|
||||
/// Shut the VMM down.
|
||||
/// This will shutdown and delete the current VM, if any, and then exit the
|
||||
/// VMM process.
|
||||
VmmShutdown(Sender<ApiResponse>),
|
||||
}
|
||||
|
||||
pub fn vm_create(
|
||||
api_evt: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
config: Arc<VmConfig>,
|
||||
) -> ApiResult<()> {
|
||||
let (response_sender, response_receiver) = channel();
|
||||
|
||||
// Send the VM creation request.
|
||||
api_sender
|
||||
.send(ApiRequest::VmCreate(config, response_sender))
|
||||
.map_err(ApiError::RequestSend)?;
|
||||
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
|
||||
|
||||
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Represents a VM related action.
|
||||
/// This is mostly used to factorize code between VM routines
|
||||
/// that only differ by the IPC command they send.
|
||||
pub enum VmAction {
|
||||
/// Boot a VM
|
||||
Boot,
|
||||
|
||||
/// Delete a VM
|
||||
Delete,
|
||||
|
||||
/// Shut a VM down
|
||||
Shutdown,
|
||||
|
||||
/// Reboot a VM
|
||||
Reboot,
|
||||
|
||||
/// Pause a VM
|
||||
Pause,
|
||||
|
||||
/// Resume a VM
|
||||
Resume,
|
||||
}
|
||||
|
||||
fn vm_action(api_evt: EventFd, api_sender: Sender<ApiRequest>, action: VmAction) -> ApiResult<()> {
|
||||
let (response_sender, response_receiver) = channel();
|
||||
|
||||
let request = match action {
|
||||
VmAction::Boot => ApiRequest::VmBoot(response_sender),
|
||||
VmAction::Delete => ApiRequest::VmDelete(response_sender),
|
||||
VmAction::Shutdown => ApiRequest::VmShutdown(response_sender),
|
||||
VmAction::Reboot => ApiRequest::VmReboot(response_sender),
|
||||
VmAction::Pause => ApiRequest::VmPause(response_sender),
|
||||
VmAction::Resume => ApiRequest::VmResume(response_sender),
|
||||
};
|
||||
|
||||
// Send the VM request.
|
||||
api_sender.send(request).map_err(ApiError::RequestSend)?;
|
||||
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
|
||||
|
||||
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn vm_boot(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
|
||||
vm_action(api_evt, api_sender, VmAction::Boot)
|
||||
}
|
||||
|
||||
pub fn vm_delete(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
|
||||
vm_action(api_evt, api_sender, VmAction::Delete)
|
||||
}
|
||||
|
||||
pub fn vm_shutdown(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
|
||||
vm_action(api_evt, api_sender, VmAction::Shutdown)
|
||||
}
|
||||
|
||||
pub fn vm_reboot(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
|
||||
vm_action(api_evt, api_sender, VmAction::Reboot)
|
||||
}
|
||||
|
||||
pub fn vm_pause(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
|
||||
vm_action(api_evt, api_sender, VmAction::Pause)
|
||||
}
|
||||
|
||||
pub fn vm_resume(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
|
||||
vm_action(api_evt, api_sender, VmAction::Resume)
|
||||
}
|
||||
|
||||
pub fn vm_info(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<VmInfo> {
|
||||
let (response_sender, response_receiver) = channel();
|
||||
|
||||
// Send the VM request.
|
||||
api_sender
|
||||
.send(ApiRequest::VmInfo(response_sender))
|
||||
.map_err(ApiError::RequestSend)?;
|
||||
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
|
||||
|
||||
let vm_info = response_receiver.recv().map_err(ApiError::ResponseRecv)??;
|
||||
|
||||
match vm_info {
|
||||
ApiResponsePayload::VmInfo(info) => Ok(info),
|
||||
_ => Err(ApiError::ResponsePayloadType),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vmm_shutdown(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
|
||||
let (response_sender, response_receiver) = channel();
|
||||
|
||||
// Send the VMM shutdown request.
|
||||
api_sender
|
||||
.send(ApiRequest::VmmShutdown(response_sender))
|
||||
.map_err(ApiError::RequestSend)?;
|
||||
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
|
||||
|
||||
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
413
vmm/src/api/openapi/cloud-hypervisor.yaml
Normal file
413
vmm/src/api/openapi/cloud-hypervisor.yaml
Normal file
@@ -0,0 +1,413 @@
|
||||
openapi: 3.0.1
|
||||
info:
|
||||
title: Cloud Hypervisor API
|
||||
description: Local HTTP based API for managing and inspecting a cloud-hypervisor virtual machine.
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
version: 0.3.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost/api/v1
|
||||
|
||||
paths:
|
||||
|
||||
/vmm.info:
|
||||
get:
|
||||
summary: Returns general information about the cloud-hypervisor Virtual Machine Monitor (VMM).
|
||||
responses:
|
||||
200:
|
||||
description: The VMM information
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VmmInfo'
|
||||
|
||||
/vmm.shutdown:
|
||||
put:
|
||||
summary: Shuts the cloud-hypervisor VMM.
|
||||
operationId: shutdownVMM
|
||||
responses:
|
||||
201:
|
||||
description: The VMM successfully shutdown.
|
||||
content: {}
|
||||
|
||||
/vm.info:
|
||||
get:
|
||||
summary: Returns general information about the cloud-hypervisor Virtual Machine (VM) instance.
|
||||
responses:
|
||||
200:
|
||||
description: The VM information
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VmInfo'
|
||||
|
||||
/vm.create:
|
||||
put:
|
||||
summary: Create the cloud-hypervisor Virtual Machine (VM) instance. The instance is not booted, only created.
|
||||
operationId: createVM
|
||||
requestBody:
|
||||
description: The VM configuration
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VmConfig'
|
||||
required: true
|
||||
responses:
|
||||
201:
|
||||
description: The VM instance was successfully created.
|
||||
content: {}
|
||||
|
||||
/vm.delete:
|
||||
put:
|
||||
summary: Delete the cloud-hypervisor Virtual Machine (VM) instance.
|
||||
operationId: deleteVM
|
||||
responses:
|
||||
201:
|
||||
description: The VM instance was successfully deleted.
|
||||
content: {}
|
||||
|
||||
/vm.boot:
|
||||
put:
|
||||
summary: Boot the previously created VM instance.
|
||||
operationId: bootVM
|
||||
responses:
|
||||
201:
|
||||
description: The VM instance successfully booted.
|
||||
content: {}
|
||||
404:
|
||||
description: The VM instance could not boot because it is not created yet
|
||||
content: {}
|
||||
|
||||
/vm.pause:
|
||||
put:
|
||||
summary: Pause a previously booted VM instance.
|
||||
operationId: pauseVM
|
||||
responses:
|
||||
201:
|
||||
description: The VM instance successfully paused.
|
||||
content: {}
|
||||
404:
|
||||
description: The VM instance could not pause because it is not created yet
|
||||
content: {}
|
||||
405:
|
||||
description: The VM instance could not pause because it is not booted.
|
||||
content: {}
|
||||
|
||||
/vm.resume:
|
||||
put:
|
||||
summary: Resume a previously paused VM instance.
|
||||
operationId: resumeVM
|
||||
responses:
|
||||
201:
|
||||
description: The VM instance successfully paused.
|
||||
content: {}
|
||||
404:
|
||||
description: The VM instance could not resume because it is not booted yet
|
||||
content: {}
|
||||
405:
|
||||
description: The VM instance could not resume because it is not paused.
|
||||
content: {}
|
||||
|
||||
/vm.shutdown:
|
||||
put:
|
||||
summary: Shut the VM instance down.
|
||||
operationId: shutdownVM
|
||||
responses:
|
||||
201:
|
||||
description: The VM instance successfully shut down.
|
||||
content: {}
|
||||
404:
|
||||
description: The VM instance could not shut down because is not created.
|
||||
content: {}
|
||||
405:
|
||||
description: The VM instance could not shut down because it is not started.
|
||||
content: {}
|
||||
|
||||
/vm.reboot:
|
||||
put:
|
||||
summary: Reboot the VM instance.
|
||||
operationId: rebootVM
|
||||
responses:
|
||||
201:
|
||||
description: The VM instance successfully rebooted.
|
||||
content: {}
|
||||
404:
|
||||
description: The VM instance could not reboot because it is not created.
|
||||
content: {}
|
||||
405:
|
||||
description: The VM instance could not reboot because it is not booted.
|
||||
content: {}
|
||||
|
||||
components:
|
||||
schemas:
|
||||
|
||||
VmmInfo:
|
||||
required:
|
||||
- version
|
||||
type: object
|
||||
properties:
|
||||
version:
|
||||
type: string
|
||||
description: Virtual Machine Monitor information
|
||||
|
||||
VmInfo:
|
||||
required:
|
||||
- config
|
||||
- state
|
||||
type: object
|
||||
properties:
|
||||
version:
|
||||
type: string
|
||||
state:
|
||||
type: string
|
||||
enum: [Created, Booted, Shutdown]
|
||||
description: Virtual Machine information
|
||||
|
||||
VmConfig:
|
||||
required:
|
||||
- kernel
|
||||
- cmdline
|
||||
type: object
|
||||
properties:
|
||||
cpus:
|
||||
$ref: '#/components/schemas/CpuConfig'
|
||||
memory:
|
||||
$ref: '#/components/schemas/MemoryConfig'
|
||||
kernel:
|
||||
$ref: '#/components/schemas/KernelConfig'
|
||||
cmdline:
|
||||
$ref: '#/components/schemas/CmdLineConfig'
|
||||
disks:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/DiskConfig'
|
||||
net:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/NetConfig'
|
||||
rng:
|
||||
$ref: '#/components/schemas/RngConfig'
|
||||
fs:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/FsConfig'
|
||||
pmem:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PmemConfig'
|
||||
serial:
|
||||
$ref: '#/components/schemas/ConsoleConfig'
|
||||
console:
|
||||
$ref: '#/components/schemas/ConsoleConfig'
|
||||
devices:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/DeviceConfig'
|
||||
vhost_user_net:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VhostUserNetConfig'
|
||||
vhost_user_blk:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VhostUserBlkConfig'
|
||||
vsock:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VsockConfig'
|
||||
iommu:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Virtual machine configuration
|
||||
|
||||
CpuConfig:
|
||||
required:
|
||||
- cpu_count
|
||||
type: object
|
||||
properties:
|
||||
cpu_count:
|
||||
minimum: 1
|
||||
default: 1
|
||||
type: integer
|
||||
|
||||
MemoryConfig:
|
||||
required:
|
||||
- size
|
||||
type: object
|
||||
properties:
|
||||
size:
|
||||
type: integer
|
||||
default: 512 MB
|
||||
file:
|
||||
type: string
|
||||
|
||||
KernelConfig:
|
||||
required:
|
||||
- path
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
|
||||
CmdLineConfig:
|
||||
required:
|
||||
- args
|
||||
type: object
|
||||
properties:
|
||||
args:
|
||||
type: string
|
||||
|
||||
DiskConfig:
|
||||
required:
|
||||
- path
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
iommu:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
NetConfig:
|
||||
required:
|
||||
- ip
|
||||
- mask
|
||||
- mac
|
||||
type: object
|
||||
properties:
|
||||
tap:
|
||||
type: string
|
||||
ip:
|
||||
type: string
|
||||
mask:
|
||||
type: string
|
||||
mac:
|
||||
type: string
|
||||
iommu:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
RngConfig:
|
||||
required:
|
||||
- src
|
||||
type: object
|
||||
properties:
|
||||
src:
|
||||
type: string
|
||||
default: "/dev/urandom"
|
||||
iommu:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
FsConfig:
|
||||
required:
|
||||
- tag
|
||||
- sock
|
||||
- num_queues
|
||||
- queue_size
|
||||
type: object
|
||||
properties:
|
||||
tag:
|
||||
type: string
|
||||
sock:
|
||||
type: string
|
||||
num_queues:
|
||||
type: integer
|
||||
queue_size:
|
||||
type: integer
|
||||
cache_size:
|
||||
type: integer
|
||||
|
||||
PmemConfig:
|
||||
required:
|
||||
- file
|
||||
- size
|
||||
type: object
|
||||
properties:
|
||||
file:
|
||||
type: string
|
||||
size:
|
||||
type: integer
|
||||
iommu:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
ConsoleConfig:
|
||||
required:
|
||||
- mode
|
||||
type: object
|
||||
properties:
|
||||
file:
|
||||
type: string
|
||||
mode:
|
||||
type: string
|
||||
enum: [Off, Tty, File, None]
|
||||
iommu:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
DeviceConfig:
|
||||
required:
|
||||
- path
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
iommu:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
VhostUserConfig:
|
||||
required:
|
||||
- sock
|
||||
- num_queues
|
||||
- queue_size
|
||||
type: object
|
||||
properties:
|
||||
sock:
|
||||
type: string
|
||||
num_queues:
|
||||
type: integer
|
||||
queue:size:
|
||||
type: integer
|
||||
|
||||
VhostUserNetConfig:
|
||||
required:
|
||||
- mac
|
||||
- vu_cfg
|
||||
type: object
|
||||
properties:
|
||||
mac:
|
||||
type: string
|
||||
vu_cfg:
|
||||
$ref: '#/components/schemas/VhostUserConfig'
|
||||
|
||||
VhostUserBlkConfig:
|
||||
required:
|
||||
- wce
|
||||
- vu_cfg
|
||||
type: object
|
||||
properties:
|
||||
wce:
|
||||
type: boolean
|
||||
vu_cfg:
|
||||
$ref: '#/components/schemas/VhostUserConfig'
|
||||
|
||||
VsockConfig:
|
||||
required:
|
||||
- cid
|
||||
- sock
|
||||
type: object
|
||||
properties:
|
||||
cid:
|
||||
type: integer
|
||||
minimum: 3
|
||||
description: Guest Vsock CID
|
||||
sock:
|
||||
type: string
|
||||
description: Path to UNIX domain socket, used to proxy vsock connections.
|
||||
iommu:
|
||||
type: boolean
|
||||
default: false
|
||||
@@ -5,20 +5,16 @@
|
||||
|
||||
extern crate vm_virtio;
|
||||
|
||||
use linux_loader::cmdline::Cmdline;
|
||||
use net_util::MacAddr;
|
||||
use std::convert::From;
|
||||
use std::net::AddrParseError;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::result;
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_virtio::vhost_user::VhostUserConfig;
|
||||
|
||||
pub const DEFAULT_VCPUS: &str = "1";
|
||||
pub const DEFAULT_MEMORY: &str = "size=512M";
|
||||
pub const DEFAULT_VCPUS: u8 = 1;
|
||||
pub const DEFAULT_MEMORY_MB: u64 = 512;
|
||||
pub const DEFAULT_RNG_SOURCE: &str = "/dev/urandom";
|
||||
const CMDLINE_OFFSET: GuestAddress = GuestAddress(0x20000);
|
||||
|
||||
/// Errors associated with VM configuration parameters.
|
||||
#[derive(Debug)]
|
||||
@@ -63,21 +59,31 @@ pub enum Error<'a> {
|
||||
ParseTTYParam,
|
||||
/// Failed parsing vhost-user-net mac parameter.
|
||||
ParseVuNetMacParam(&'a str),
|
||||
/// Failed parsing vhost-user-net sock parameter.
|
||||
ParseVuNetSockParam,
|
||||
/// Failed parsing vhost-user-net queue number parameter.
|
||||
ParseVuNetNumQueuesParam(std::num::ParseIntError),
|
||||
/// Failed parsing vhost-user-net queue size parameter.
|
||||
ParseVuNetQueueSizeParam(std::num::ParseIntError),
|
||||
/// Failed parsing vhost-user sock parameter.
|
||||
ParseVuSockParam,
|
||||
/// Failed parsing vhost-user queue number parameter.
|
||||
ParseVuNumQueuesParam(std::num::ParseIntError),
|
||||
/// Failed parsing vhost-user queue size parameter.
|
||||
ParseVuQueueSizeParam(std::num::ParseIntError),
|
||||
/// Failed parsing vhost-user-net server parameter.
|
||||
ParseVuNetServerParam(std::num::ParseIntError),
|
||||
/// Failed parsing vhost-user-blk wce parameter.
|
||||
ParseVuBlkWceParam(std::str::ParseBoolError),
|
||||
/// Failed parsing vsock context ID parameter.
|
||||
ParseVsockCidParam(std::num::ParseIntError),
|
||||
/// Failed parsing vsock socket path parameter.
|
||||
ParseVsockSockParam,
|
||||
/// Missing kernel configuration
|
||||
ValidateMissingKernelConfig,
|
||||
/// Failed parsing iommu parameter for the device.
|
||||
ParseDeviceIommu,
|
||||
}
|
||||
pub type Result<'a, T> = result::Result<T, Error<'a>>;
|
||||
|
||||
pub struct VmParams<'a> {
|
||||
pub cpus: &'a str,
|
||||
pub memory: &'a str,
|
||||
pub kernel: &'a str,
|
||||
pub kernel: Option<&'a str>,
|
||||
pub cmdline: Option<&'a str>,
|
||||
pub disks: Option<Vec<&'a str>>,
|
||||
pub net: Option<Vec<&'a str>>,
|
||||
@@ -88,6 +94,8 @@ pub struct VmParams<'a> {
|
||||
pub console: &'a str,
|
||||
pub devices: Option<Vec<&'a str>>,
|
||||
pub vhost_user_net: Option<Vec<&'a str>>,
|
||||
pub vhost_user_blk: Option<Vec<&'a str>>,
|
||||
pub vsock: Option<Vec<&'a str>>,
|
||||
}
|
||||
|
||||
fn parse_size(size: &str) -> Result<u64> {
|
||||
@@ -108,29 +116,49 @@ fn parse_size(size: &str) -> Result<u64> {
|
||||
Ok(res << shift)
|
||||
}
|
||||
|
||||
pub struct CpusConfig(pub u8);
|
||||
fn parse_iommu(iommu: &str) -> Result<bool> {
|
||||
if !iommu.is_empty() {
|
||||
let res = match iommu {
|
||||
"on" => true,
|
||||
"off" => false,
|
||||
_ => return Err(Error::ParseDeviceIommu),
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct CpusConfig {
|
||||
pub cpu_count: u8,
|
||||
}
|
||||
|
||||
impl CpusConfig {
|
||||
pub fn parse(cpus: &str) -> Result<Self> {
|
||||
Ok(CpusConfig(
|
||||
cpus.parse::<u8>().map_err(Error::ParseCpusParams)?,
|
||||
))
|
||||
Ok(CpusConfig {
|
||||
cpu_count: cpus.parse().map_err(Error::ParseCpusParams)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&CpusConfig> for u8 {
|
||||
fn from(val: &CpusConfig) -> Self {
|
||||
val.0
|
||||
impl Default for CpusConfig {
|
||||
fn default() -> Self {
|
||||
CpusConfig {
|
||||
cpu_count: DEFAULT_VCPUS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MemoryConfig<'a> {
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct MemoryConfig {
|
||||
pub size: u64,
|
||||
pub file: Option<&'a Path>,
|
||||
pub file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl<'a> MemoryConfig<'a> {
|
||||
pub fn parse(memory: &'a str) -> Result<Self> {
|
||||
impl MemoryConfig {
|
||||
pub fn parse(memory: &str) -> Result<Self> {
|
||||
// Split the parameters based on the comma delimiter
|
||||
let params_list: Vec<&str> = memory.split(',').collect();
|
||||
|
||||
@@ -152,7 +180,7 @@ impl<'a> MemoryConfig<'a> {
|
||||
return Err(Error::ParseMemoryFileParam);
|
||||
}
|
||||
|
||||
Some(Path::new(file_str))
|
||||
Some(PathBuf::from(file_str))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -164,60 +192,77 @@ impl<'a> MemoryConfig<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KernelConfig<'a> {
|
||||
pub path: &'a Path,
|
||||
}
|
||||
|
||||
impl<'a> KernelConfig<'a> {
|
||||
pub fn parse(kernel: &'a str) -> Result<Self> {
|
||||
Ok(KernelConfig {
|
||||
path: Path::new(kernel),
|
||||
})
|
||||
impl Default for MemoryConfig {
|
||||
fn default() -> Self {
|
||||
MemoryConfig {
|
||||
size: DEFAULT_MEMORY_MB << 20,
|
||||
file: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct KernelConfig {
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct CmdlineConfig {
|
||||
pub args: Cmdline,
|
||||
pub offset: GuestAddress,
|
||||
pub args: String,
|
||||
}
|
||||
|
||||
impl CmdlineConfig {
|
||||
pub fn parse(cmdline: Option<&str>) -> Result<Self> {
|
||||
let cmdline_str = cmdline
|
||||
let args = cmdline
|
||||
.map(std::string::ToString::to_string)
|
||||
.unwrap_or_else(String::new);
|
||||
let mut args = Cmdline::new(arch::CMDLINE_MAX_SIZE);
|
||||
args.insert_str(cmdline_str).unwrap();
|
||||
|
||||
Ok(CmdlineConfig {
|
||||
args,
|
||||
offset: CMDLINE_OFFSET,
|
||||
})
|
||||
Ok(CmdlineConfig { args })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DiskConfig<'a> {
|
||||
pub path: &'a Path,
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct DiskConfig {
|
||||
pub path: PathBuf,
|
||||
#[serde(default)]
|
||||
pub iommu: bool,
|
||||
}
|
||||
|
||||
impl<'a> DiskConfig<'a> {
|
||||
pub fn parse(disk: &'a str) -> Result<Self> {
|
||||
impl DiskConfig {
|
||||
pub fn parse(disk: &str) -> Result<Self> {
|
||||
// Split the parameters based on the comma delimiter
|
||||
let params_list: Vec<&str> = disk.split(',').collect();
|
||||
|
||||
let mut path_str: &str = "";
|
||||
let mut iommu_str: &str = "";
|
||||
|
||||
for param in params_list.iter() {
|
||||
if param.starts_with("path=") {
|
||||
path_str = ¶m[5..];
|
||||
} else if param.starts_with("iommu=") {
|
||||
iommu_str = ¶m[6..];
|
||||
}
|
||||
}
|
||||
|
||||
Ok(DiskConfig {
|
||||
path: Path::new(disk),
|
||||
path: PathBuf::from(path_str),
|
||||
iommu: parse_iommu(iommu_str)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NetConfig<'a> {
|
||||
pub tap: Option<&'a str>,
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct NetConfig {
|
||||
pub tap: Option<String>,
|
||||
pub ip: Ipv4Addr,
|
||||
pub mask: Ipv4Addr,
|
||||
pub mac: MacAddr,
|
||||
#[serde(default)]
|
||||
pub iommu: bool,
|
||||
}
|
||||
|
||||
impl<'a> NetConfig<'a> {
|
||||
pub fn parse(net: &'a str) -> Result<Self> {
|
||||
impl NetConfig {
|
||||
pub fn parse(net: &str) -> Result<Self> {
|
||||
// Split the parameters based on the comma delimiter
|
||||
let params_list: Vec<&str> = net.split(',').collect();
|
||||
|
||||
@@ -225,6 +270,7 @@ impl<'a> NetConfig<'a> {
|
||||
let mut ip_str: &str = "";
|
||||
let mut mask_str: &str = "";
|
||||
let mut mac_str: &str = "";
|
||||
let mut iommu_str: &str = "";
|
||||
|
||||
for param in params_list.iter() {
|
||||
if param.starts_with("tap=") {
|
||||
@@ -235,16 +281,19 @@ impl<'a> NetConfig<'a> {
|
||||
mask_str = ¶m[5..];
|
||||
} else if param.starts_with("mac=") {
|
||||
mac_str = ¶m[4..];
|
||||
} else if param.starts_with("iommu=") {
|
||||
iommu_str = ¶m[6..];
|
||||
}
|
||||
}
|
||||
|
||||
let mut tap: Option<&str> = None;
|
||||
let mut tap: Option<String> = None;
|
||||
let mut ip: Ipv4Addr = Ipv4Addr::new(192, 168, 249, 1);
|
||||
let mut mask: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0);;
|
||||
let mut mac: MacAddr = MacAddr::local_random();
|
||||
let iommu = parse_iommu(iommu_str)?;
|
||||
|
||||
if !tap_str.is_empty() {
|
||||
tap = Some(tap_str);
|
||||
tap = Some(tap_str.to_string());
|
||||
}
|
||||
if !ip_str.is_empty() {
|
||||
ip = ip_str.parse().map_err(Error::ParseNetIpParam)?;
|
||||
@@ -256,33 +305,66 @@ impl<'a> NetConfig<'a> {
|
||||
mac = MacAddr::parse_str(mac_str).map_err(Error::ParseNetMacParam)?;
|
||||
}
|
||||
|
||||
Ok(NetConfig { tap, ip, mask, mac })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RngConfig<'a> {
|
||||
pub src: &'a Path,
|
||||
}
|
||||
|
||||
impl<'a> RngConfig<'a> {
|
||||
pub fn parse(rng: &'a str) -> Result<Self> {
|
||||
Ok(RngConfig {
|
||||
src: Path::new(rng),
|
||||
Ok(NetConfig {
|
||||
tap,
|
||||
ip,
|
||||
mask,
|
||||
mac,
|
||||
iommu,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FsConfig<'a> {
|
||||
pub tag: &'a str,
|
||||
pub sock: &'a Path,
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct RngConfig {
|
||||
pub src: PathBuf,
|
||||
#[serde(default)]
|
||||
pub iommu: bool,
|
||||
}
|
||||
|
||||
impl RngConfig {
|
||||
pub fn parse(rng: &str) -> Result<Self> {
|
||||
// Split the parameters based on the comma delimiter
|
||||
let params_list: Vec<&str> = rng.split(',').collect();
|
||||
|
||||
let mut src_str: &str = "";
|
||||
let mut iommu_str: &str = "";
|
||||
|
||||
for param in params_list.iter() {
|
||||
if param.starts_with("src=") {
|
||||
src_str = ¶m[4..];
|
||||
} else if param.starts_with("iommu=") {
|
||||
iommu_str = ¶m[6..];
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RngConfig {
|
||||
src: PathBuf::from(src_str),
|
||||
iommu: parse_iommu(iommu_str)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RngConfig {
|
||||
fn default() -> Self {
|
||||
RngConfig {
|
||||
src: PathBuf::from(DEFAULT_RNG_SOURCE),
|
||||
iommu: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct FsConfig {
|
||||
pub tag: String,
|
||||
pub sock: PathBuf,
|
||||
pub num_queues: usize,
|
||||
pub queue_size: u16,
|
||||
pub cache_size: Option<u64>,
|
||||
}
|
||||
|
||||
impl<'a> FsConfig<'a> {
|
||||
pub fn parse(fs: &'a str) -> Result<Self> {
|
||||
impl FsConfig {
|
||||
pub fn parse(fs: &str) -> Result<Self> {
|
||||
// Split the parameters based on the comma delimiter
|
||||
let params_list: Vec<&str> = fs.split(',').collect();
|
||||
|
||||
@@ -351,8 +433,8 @@ impl<'a> FsConfig<'a> {
|
||||
}
|
||||
|
||||
Ok(FsConfig {
|
||||
tag,
|
||||
sock: Path::new(sock),
|
||||
tag: tag.to_string(),
|
||||
sock: PathBuf::from(sock),
|
||||
num_queues,
|
||||
queue_size,
|
||||
cache_size,
|
||||
@@ -360,24 +442,30 @@ impl<'a> FsConfig<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PmemConfig<'a> {
|
||||
pub file: &'a Path,
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct PmemConfig {
|
||||
pub file: PathBuf,
|
||||
pub size: u64,
|
||||
#[serde(default)]
|
||||
pub iommu: bool,
|
||||
}
|
||||
|
||||
impl<'a> PmemConfig<'a> {
|
||||
pub fn parse(pmem: &'a str) -> Result<Self> {
|
||||
impl PmemConfig {
|
||||
pub fn parse(pmem: &str) -> Result<Self> {
|
||||
// Split the parameters based on the comma delimiter
|
||||
let params_list: Vec<&str> = pmem.split(',').collect();
|
||||
|
||||
let mut file_str: &str = "";
|
||||
let mut size_str: &str = "";
|
||||
let mut iommu_str: &str = "";
|
||||
|
||||
for param in params_list.iter() {
|
||||
if param.starts_with("file=") {
|
||||
file_str = ¶m[5..];
|
||||
} else if param.starts_with("size=") {
|
||||
size_str = ¶m[5..];
|
||||
} else if param.starts_with("iommu=") {
|
||||
iommu_str = ¶m[6..];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,13 +474,14 @@ impl<'a> PmemConfig<'a> {
|
||||
}
|
||||
|
||||
Ok(PmemConfig {
|
||||
file: Path::new(file_str),
|
||||
file: PathBuf::from(file_str),
|
||||
size: parse_size(size_str)?,
|
||||
iommu: parse_iommu(iommu_str)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub enum ConsoleOutputMode {
|
||||
Off,
|
||||
Tty,
|
||||
@@ -409,59 +498,120 @@ impl ConsoleOutputMode {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConsoleConfig<'a> {
|
||||
pub file: Option<&'a Path>,
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct ConsoleConfig {
|
||||
pub file: Option<PathBuf>,
|
||||
pub mode: ConsoleOutputMode,
|
||||
#[serde(default)]
|
||||
pub iommu: bool,
|
||||
}
|
||||
|
||||
impl<'a> ConsoleConfig<'a> {
|
||||
pub fn parse(param: &'a str) -> Result<Self> {
|
||||
if param == "off" {
|
||||
Ok(Self {
|
||||
mode: ConsoleOutputMode::Off,
|
||||
file: None,
|
||||
})
|
||||
} else if param == "tty" {
|
||||
Ok(Self {
|
||||
mode: ConsoleOutputMode::Tty,
|
||||
file: None,
|
||||
})
|
||||
} else if param.starts_with("file=") {
|
||||
Ok(Self {
|
||||
mode: ConsoleOutputMode::File,
|
||||
file: Some(Path::new(¶m[5..])),
|
||||
})
|
||||
} else if param.starts_with("null") {
|
||||
Ok(Self {
|
||||
mode: ConsoleOutputMode::Null,
|
||||
file: None,
|
||||
})
|
||||
} else {
|
||||
Err(Error::ParseConsoleParam)
|
||||
impl ConsoleConfig {
|
||||
pub fn parse(console: &str) -> Result<Self> {
|
||||
// Split the parameters based on the comma delimiter
|
||||
let params_list: Vec<&str> = console.split(',').collect();
|
||||
|
||||
let mut valid = false;
|
||||
let mut file: Option<PathBuf> = None;
|
||||
let mut mode: ConsoleOutputMode = ConsoleOutputMode::Off;
|
||||
let mut iommu_str: &str = "";
|
||||
|
||||
for param in params_list.iter() {
|
||||
if param.starts_with("iommu=") {
|
||||
iommu_str = ¶m[6..];
|
||||
} else {
|
||||
if *param == "off" {
|
||||
mode = ConsoleOutputMode::Off;
|
||||
file = None;
|
||||
} else if *param == "tty" {
|
||||
mode = ConsoleOutputMode::Tty;
|
||||
file = None;
|
||||
} else if param.starts_with("file=") {
|
||||
mode = ConsoleOutputMode::File;
|
||||
file = Some(PathBuf::from(¶m[5..]));
|
||||
} else if param.starts_with("null") {
|
||||
mode = ConsoleOutputMode::Null;
|
||||
file = None;
|
||||
} else {
|
||||
return Err(Error::ParseConsoleParam);
|
||||
}
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !valid {
|
||||
return Err(Error::ParseConsoleParam);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
mode,
|
||||
file,
|
||||
iommu: parse_iommu(iommu_str)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn default_serial() -> Self {
|
||||
ConsoleConfig {
|
||||
file: None,
|
||||
mode: ConsoleOutputMode::Null,
|
||||
iommu: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_console() -> Self {
|
||||
ConsoleConfig {
|
||||
file: None,
|
||||
mode: ConsoleOutputMode::Tty,
|
||||
iommu: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DeviceConfig<'a> {
|
||||
pub path: &'a Path,
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct DeviceConfig {
|
||||
pub path: PathBuf,
|
||||
#[serde(default)]
|
||||
pub iommu: bool,
|
||||
}
|
||||
|
||||
impl<'a> DeviceConfig<'a> {
|
||||
pub fn parse(device: &'a str) -> Result<Self> {
|
||||
impl DeviceConfig {
|
||||
pub fn parse(device: &str) -> Result<Self> {
|
||||
// Split the parameters based on the comma delimiter
|
||||
let params_list: Vec<&str> = device.split(',').collect();
|
||||
|
||||
let mut path_str: &str = "";
|
||||
let mut iommu_str: &str = "";
|
||||
|
||||
for param in params_list.iter() {
|
||||
if param.starts_with("path=") {
|
||||
path_str = ¶m[5..];
|
||||
} else if param.starts_with("iommu=") {
|
||||
iommu_str = ¶m[6..];
|
||||
}
|
||||
}
|
||||
|
||||
Ok(DeviceConfig {
|
||||
path: Path::new(device),
|
||||
path: PathBuf::from(path_str),
|
||||
iommu: parse_iommu(iommu_str)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VhostUserNetConfig<'a> {
|
||||
pub mac: MacAddr,
|
||||
pub vu_cfg: VhostUserConfig<'a>,
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct VuConfig {
|
||||
pub sock: String,
|
||||
pub num_queues: usize,
|
||||
pub queue_size: u16,
|
||||
}
|
||||
|
||||
impl<'a> VhostUserNetConfig<'a> {
|
||||
pub fn parse(vhost_user_net: &'a str) -> Result<Self> {
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct VhostUserNetConfig {
|
||||
pub mac: MacAddr,
|
||||
pub vu_cfg: VuConfig,
|
||||
}
|
||||
|
||||
impl VhostUserNetConfig {
|
||||
pub fn parse(vhost_user_net: &str) -> Result<Self> {
|
||||
// Split the parameters based on the comma delimiter
|
||||
let params_list: Vec<&str> = vhost_user_net.split(',').collect();
|
||||
|
||||
@@ -490,21 +640,21 @@ impl<'a> VhostUserNetConfig<'a> {
|
||||
mac = MacAddr::parse_str(mac_str).map_err(Error::ParseVuNetMacParam)?;
|
||||
}
|
||||
if sock.is_empty() {
|
||||
return Err(Error::ParseVuNetSockParam);
|
||||
return Err(Error::ParseVuSockParam);
|
||||
}
|
||||
if !num_queues_str.is_empty() {
|
||||
num_queues = num_queues_str
|
||||
.parse()
|
||||
.map_err(Error::ParseVuNetNumQueuesParam)?;
|
||||
.map_err(Error::ParseVuNumQueuesParam)?;
|
||||
}
|
||||
if !queue_size_str.is_empty() {
|
||||
queue_size = queue_size_str
|
||||
.parse()
|
||||
.map_err(Error::ParseVuNetQueueSizeParam)?;
|
||||
.map_err(Error::ParseVuQueueSizeParam)?;
|
||||
}
|
||||
|
||||
let vu_cfg = VhostUserConfig {
|
||||
sock,
|
||||
let vu_cfg = VuConfig {
|
||||
sock: sock.to_string(),
|
||||
num_queues,
|
||||
queue_size,
|
||||
};
|
||||
@@ -513,29 +663,144 @@ impl<'a> VhostUserNetConfig<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VmConfig<'a> {
|
||||
pub cpus: CpusConfig,
|
||||
pub memory: MemoryConfig<'a>,
|
||||
pub kernel: KernelConfig<'a>,
|
||||
pub cmdline: CmdlineConfig,
|
||||
pub disks: Option<Vec<DiskConfig<'a>>>,
|
||||
pub net: Option<Vec<NetConfig<'a>>>,
|
||||
pub rng: RngConfig<'a>,
|
||||
pub fs: Option<Vec<FsConfig<'a>>>,
|
||||
pub pmem: Option<Vec<PmemConfig<'a>>>,
|
||||
pub serial: ConsoleConfig<'a>,
|
||||
pub console: ConsoleConfig<'a>,
|
||||
pub devices: Option<Vec<DeviceConfig<'a>>>,
|
||||
pub vhost_user_net: Option<Vec<VhostUserNetConfig<'a>>>,
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct VsockConfig {
|
||||
pub cid: u64,
|
||||
pub sock: PathBuf,
|
||||
#[serde(default)]
|
||||
pub iommu: bool,
|
||||
}
|
||||
|
||||
impl<'a> VmConfig<'a> {
|
||||
pub fn parse(vm_params: VmParams<'a>) -> Result<Self> {
|
||||
impl VsockConfig {
|
||||
pub fn parse(vsock: &str) -> Result<Self> {
|
||||
// Split the parameters based on the comma delimiter
|
||||
let params_list: Vec<&str> = vsock.split(',').collect();
|
||||
|
||||
let mut cid_str: &str = "";
|
||||
let mut sock_str: &str = "";
|
||||
let mut iommu_str: &str = "";
|
||||
|
||||
for param in params_list.iter() {
|
||||
if param.starts_with("cid=") {
|
||||
cid_str = ¶m[4..];
|
||||
} else if param.starts_with("sock=") {
|
||||
sock_str = ¶m[5..];
|
||||
} else if param.starts_with("iommu=") {
|
||||
iommu_str = ¶m[6..];
|
||||
}
|
||||
}
|
||||
|
||||
if sock_str.is_empty() {
|
||||
return Err(Error::ParseVsockSockParam);
|
||||
}
|
||||
|
||||
Ok(VsockConfig {
|
||||
cid: cid_str.parse::<u64>().map_err(Error::ParseVsockCidParam)?,
|
||||
sock: PathBuf::from(sock_str),
|
||||
iommu: parse_iommu(iommu_str)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct VhostUserBlkConfig {
|
||||
pub wce: bool,
|
||||
pub vu_cfg: VuConfig,
|
||||
}
|
||||
|
||||
impl VhostUserBlkConfig {
|
||||
pub fn parse(vhost_user_blk: &str) -> Result<Self> {
|
||||
// Split the parameters based on the comma delimiter
|
||||
let params_list: Vec<&str> = vhost_user_blk.split(',').collect();
|
||||
|
||||
let mut sock: &str = "";
|
||||
let mut num_queues_str: &str = "";
|
||||
let mut queue_size_str: &str = "";
|
||||
let mut wce_str: &str = "";
|
||||
|
||||
for param in params_list.iter() {
|
||||
if param.starts_with("sock=") {
|
||||
sock = ¶m[5..];
|
||||
} else if param.starts_with("num_queues=") {
|
||||
num_queues_str = ¶m[11..];
|
||||
} else if param.starts_with("queue_size=") {
|
||||
queue_size_str = ¶m[11..];
|
||||
} else if param.starts_with("wce=") {
|
||||
wce_str = ¶m[4..];
|
||||
}
|
||||
}
|
||||
|
||||
let mut num_queues: usize = 1;
|
||||
let mut queue_size: u16 = 128;
|
||||
let mut wce: bool = true;
|
||||
|
||||
if !num_queues_str.is_empty() {
|
||||
num_queues = num_queues_str
|
||||
.parse()
|
||||
.map_err(Error::ParseVuNumQueuesParam)?;
|
||||
}
|
||||
if !queue_size_str.is_empty() {
|
||||
queue_size = queue_size_str
|
||||
.parse()
|
||||
.map_err(Error::ParseVuQueueSizeParam)?;
|
||||
}
|
||||
if !wce_str.is_empty() {
|
||||
wce = wce_str.parse().map_err(Error::ParseVuBlkWceParam)?;
|
||||
}
|
||||
|
||||
let vu_cfg = VuConfig {
|
||||
sock: sock.to_string(),
|
||||
num_queues,
|
||||
queue_size,
|
||||
};
|
||||
|
||||
Ok(VhostUserBlkConfig { wce, vu_cfg })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct VmConfig {
|
||||
#[serde(default)]
|
||||
pub cpus: CpusConfig,
|
||||
#[serde(default)]
|
||||
pub memory: MemoryConfig,
|
||||
pub kernel: Option<KernelConfig>,
|
||||
pub cmdline: CmdlineConfig,
|
||||
pub disks: Option<Vec<DiskConfig>>,
|
||||
pub net: Option<Vec<NetConfig>>,
|
||||
#[serde(default)]
|
||||
pub rng: RngConfig,
|
||||
pub fs: Option<Vec<FsConfig>>,
|
||||
pub pmem: Option<Vec<PmemConfig>>,
|
||||
#[serde(default = "ConsoleConfig::default_serial")]
|
||||
pub serial: ConsoleConfig,
|
||||
#[serde(default = "ConsoleConfig::default_console")]
|
||||
pub console: ConsoleConfig,
|
||||
pub devices: Option<Vec<DeviceConfig>>,
|
||||
pub vhost_user_net: Option<Vec<VhostUserNetConfig>>,
|
||||
pub vhost_user_blk: Option<Vec<VhostUserBlkConfig>>,
|
||||
pub vsock: Option<Vec<VsockConfig>>,
|
||||
#[serde(default)]
|
||||
pub iommu: bool,
|
||||
}
|
||||
|
||||
impl VmConfig {
|
||||
pub fn valid(&self) -> bool {
|
||||
self.kernel.is_some()
|
||||
}
|
||||
|
||||
pub fn parse(vm_params: VmParams) -> Result<Self> {
|
||||
let mut iommu = false;
|
||||
|
||||
let mut disks: Option<Vec<DiskConfig>> = None;
|
||||
if let Some(disk_list) = &vm_params.disks {
|
||||
let mut disk_config_list = Vec::new();
|
||||
for item in disk_list.iter() {
|
||||
disk_config_list.push(DiskConfig::parse(item)?);
|
||||
let disk_config = DiskConfig::parse(item)?;
|
||||
if disk_config.iommu {
|
||||
iommu = true;
|
||||
}
|
||||
disk_config_list.push(disk_config);
|
||||
}
|
||||
disks = Some(disk_config_list);
|
||||
}
|
||||
@@ -544,11 +809,20 @@ impl<'a> VmConfig<'a> {
|
||||
if let Some(net_list) = &vm_params.net {
|
||||
let mut net_config_list = Vec::new();
|
||||
for item in net_list.iter() {
|
||||
net_config_list.push(NetConfig::parse(item)?);
|
||||
let net_config = NetConfig::parse(item)?;
|
||||
if net_config.iommu {
|
||||
iommu = true;
|
||||
}
|
||||
net_config_list.push(net_config);
|
||||
}
|
||||
net = Some(net_config_list);
|
||||
}
|
||||
|
||||
let rng = RngConfig::parse(vm_params.rng)?;
|
||||
if rng.iommu {
|
||||
iommu = true;
|
||||
}
|
||||
|
||||
let mut fs: Option<Vec<FsConfig>> = None;
|
||||
if let Some(fs_list) = &vm_params.fs {
|
||||
let mut fs_config_list = Vec::new();
|
||||
@@ -562,12 +836,19 @@ impl<'a> VmConfig<'a> {
|
||||
if let Some(pmem_list) = &vm_params.pmem {
|
||||
let mut pmem_config_list = Vec::new();
|
||||
for item in pmem_list.iter() {
|
||||
pmem_config_list.push(PmemConfig::parse(item)?);
|
||||
let pmem_config = PmemConfig::parse(item)?;
|
||||
if pmem_config.iommu {
|
||||
iommu = true;
|
||||
}
|
||||
pmem_config_list.push(pmem_config);
|
||||
}
|
||||
pmem = Some(pmem_config_list);
|
||||
}
|
||||
|
||||
let console = ConsoleConfig::parse(vm_params.console)?;
|
||||
if console.iommu {
|
||||
iommu = true;
|
||||
}
|
||||
let serial = ConsoleConfig::parse(vm_params.serial)?;
|
||||
if console.mode == ConsoleOutputMode::Tty && serial.mode == ConsoleOutputMode::Tty {
|
||||
return Err(Error::ParseTTYParam);
|
||||
@@ -577,7 +858,11 @@ impl<'a> VmConfig<'a> {
|
||||
if let Some(device_list) = &vm_params.devices {
|
||||
let mut device_config_list = Vec::new();
|
||||
for item in device_list.iter() {
|
||||
device_config_list.push(DeviceConfig::parse(item)?);
|
||||
let device_config = DeviceConfig::parse(item)?;
|
||||
if device_config.iommu {
|
||||
iommu = true;
|
||||
}
|
||||
device_config_list.push(device_config);
|
||||
}
|
||||
devices = Some(device_config_list);
|
||||
}
|
||||
@@ -591,20 +876,52 @@ impl<'a> VmConfig<'a> {
|
||||
vhost_user_net = Some(vhost_user_net_config_list);
|
||||
}
|
||||
|
||||
let mut vsock: Option<Vec<VsockConfig>> = None;
|
||||
if let Some(vsock_list) = &vm_params.vsock {
|
||||
let mut vsock_config_list = Vec::new();
|
||||
for item in vsock_list.iter() {
|
||||
let vsock_config = VsockConfig::parse(item)?;
|
||||
if vsock_config.iommu {
|
||||
iommu = true;
|
||||
}
|
||||
vsock_config_list.push(vsock_config);
|
||||
}
|
||||
vsock = Some(vsock_config_list);
|
||||
}
|
||||
|
||||
let mut vhost_user_blk: Option<Vec<VhostUserBlkConfig>> = None;
|
||||
if let Some(vhost_user_blk_list) = &vm_params.vhost_user_blk {
|
||||
let mut vhost_user_blk_config_list = Vec::new();
|
||||
for item in vhost_user_blk_list.iter() {
|
||||
vhost_user_blk_config_list.push(VhostUserBlkConfig::parse(item)?);
|
||||
}
|
||||
vhost_user_blk = Some(vhost_user_blk_config_list);
|
||||
}
|
||||
|
||||
let mut kernel: Option<KernelConfig> = None;
|
||||
if let Some(k) = vm_params.kernel {
|
||||
kernel = Some(KernelConfig {
|
||||
path: PathBuf::from(k),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(VmConfig {
|
||||
cpus: CpusConfig::parse(vm_params.cpus)?,
|
||||
memory: MemoryConfig::parse(vm_params.memory)?,
|
||||
kernel: KernelConfig::parse(vm_params.kernel)?,
|
||||
kernel,
|
||||
cmdline: CmdlineConfig::parse(vm_params.cmdline)?,
|
||||
disks,
|
||||
net,
|
||||
rng: RngConfig::parse(vm_params.rng)?,
|
||||
rng,
|
||||
fs,
|
||||
pmem,
|
||||
serial,
|
||||
console,
|
||||
devices,
|
||||
vhost_user_net,
|
||||
vhost_user_blk,
|
||||
vsock,
|
||||
iommu,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
1290
vmm/src/device_manager.rs
Normal file
1290
vmm/src/device_manager.rs
Normal file
File diff suppressed because it is too large
Load Diff
471
vmm/src/lib.rs
471
vmm/src/lib.rs
@@ -3,67 +3,466 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
extern crate kvm_ioctls;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate vmm_sys_util;
|
||||
|
||||
use kvm_ioctls::*;
|
||||
use std::fmt::{self, Display};
|
||||
use std::result;
|
||||
use crate::api::{ApiError, ApiRequest, ApiResponse, ApiResponsePayload, VmInfo};
|
||||
use crate::config::VmConfig;
|
||||
use crate::vm::{Error as VmError, Vm, VmState};
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std::io;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::sync::mpsc::{Receiver, RecvError, SendError, Sender};
|
||||
use std::sync::Arc;
|
||||
use std::{result, thread};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub mod api;
|
||||
pub mod config;
|
||||
pub mod device_manager;
|
||||
pub mod vm;
|
||||
|
||||
use self::config::VmConfig;
|
||||
use self::vm::{ExitBehaviour, Vm};
|
||||
|
||||
/// Errors associated with VM management
|
||||
/// Errors associated with VMM management
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Error {
|
||||
/// Cannot create a new VM.
|
||||
VmNew(vm::Error),
|
||||
/// API request receive error
|
||||
ApiRequestRecv(RecvError),
|
||||
|
||||
/// Cannot start a VM.
|
||||
VmStart(vm::Error),
|
||||
/// API response send error
|
||||
ApiResponseSend(SendError<ApiResponse>),
|
||||
|
||||
/// Cannot load a kernel.
|
||||
LoadKernel(vm::Error),
|
||||
/// Cannot bind to the UNIX domain socket path
|
||||
Bind(io::Error),
|
||||
|
||||
/// Cannot clone EventFd.
|
||||
EventFdClone(io::Error),
|
||||
|
||||
/// Cannot create EventFd.
|
||||
EventFdCreate(io::Error),
|
||||
|
||||
/// Cannot read from EventFd.
|
||||
EventFdRead(io::Error),
|
||||
|
||||
/// Cannot create epoll context.
|
||||
Epoll(io::Error),
|
||||
|
||||
/// Cannot create HTTP thread
|
||||
HttpThreadSpawn(io::Error),
|
||||
|
||||
/// Cannot handle the VM STDIN stream
|
||||
Stdin(VmError),
|
||||
|
||||
/// Cannot reboot the VM
|
||||
VmReboot(VmError),
|
||||
|
||||
/// Cannot shut a VM down
|
||||
VmShutdown(VmError),
|
||||
|
||||
/// Cannot create VMM thread
|
||||
VmmThreadSpawn(io::Error),
|
||||
|
||||
/// Cannot shut the VMM down
|
||||
VmmShutdown(VmError),
|
||||
}
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum EpollDispatch {
|
||||
Exit,
|
||||
Reset,
|
||||
Stdin,
|
||||
Api,
|
||||
}
|
||||
|
||||
match self {
|
||||
VmNew(e) => write!(f, "Can not create a new virtual machine: {:?}", e),
|
||||
VmStart(e) => write!(f, "Can not start a new virtual machine: {:?}", e),
|
||||
LoadKernel(e) => write!(f, "Can not load a guest kernel: {:?}", e),
|
||||
}
|
||||
pub struct EpollContext {
|
||||
raw_fd: RawFd,
|
||||
dispatch_table: Vec<Option<EpollDispatch>>,
|
||||
}
|
||||
|
||||
impl EpollContext {
|
||||
pub fn new() -> result::Result<EpollContext, io::Error> {
|
||||
let raw_fd = epoll::create(true)?;
|
||||
|
||||
// Initial capacity needs to be large enough to hold:
|
||||
// * 1 exit event
|
||||
// * 1 reset event
|
||||
// * 1 stdin event
|
||||
// * 1 API event
|
||||
let mut dispatch_table = Vec::with_capacity(5);
|
||||
dispatch_table.push(None);
|
||||
|
||||
Ok(EpollContext {
|
||||
raw_fd,
|
||||
dispatch_table,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_stdin(&mut self) -> result::Result<(), io::Error> {
|
||||
let dispatch_index = self.dispatch_table.len() as u64;
|
||||
epoll::ctl(
|
||||
self.raw_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
libc::STDIN_FILENO,
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, dispatch_index),
|
||||
)?;
|
||||
|
||||
self.dispatch_table.push(Some(EpollDispatch::Stdin));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_event<T>(&mut self, fd: &T, token: EpollDispatch) -> result::Result<(), io::Error>
|
||||
where
|
||||
T: AsRawFd,
|
||||
{
|
||||
let dispatch_index = self.dispatch_table.len() as u64;
|
||||
epoll::ctl(
|
||||
self.raw_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
fd.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, dispatch_index),
|
||||
)?;
|
||||
self.dispatch_table.push(Some(token));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct Vmm {
|
||||
kvm: Kvm,
|
||||
impl AsRawFd for EpollContext {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.raw_fd
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_vmm_thread(
|
||||
http_path: &str,
|
||||
api_event: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
api_receiver: Receiver<ApiRequest>,
|
||||
) -> Result<thread::JoinHandle<Result<()>>> {
|
||||
let http_api_event = api_event.try_clone().map_err(Error::EventFdClone)?;
|
||||
|
||||
let thread = thread::Builder::new()
|
||||
.name("vmm".to_string())
|
||||
.spawn(move || {
|
||||
let mut vmm = Vmm::new(api_event)?;
|
||||
|
||||
vmm.control_loop(Arc::new(api_receiver))
|
||||
})
|
||||
.map_err(Error::VmmThreadSpawn)?;
|
||||
|
||||
// The VMM thread is started, we can start serving HTTP requests
|
||||
api::start_http_thread(http_path, http_api_event, api_sender)?;
|
||||
|
||||
Ok(thread)
|
||||
}
|
||||
|
||||
pub struct Vmm {
|
||||
epoll: EpollContext,
|
||||
exit_evt: EventFd,
|
||||
reset_evt: EventFd,
|
||||
api_evt: EventFd,
|
||||
vm: Option<Vm>,
|
||||
vm_config: Option<Arc<VmConfig>>,
|
||||
}
|
||||
|
||||
impl Vmm {
|
||||
fn new() -> Result<Self> {
|
||||
let kvm = Kvm::new().expect("new KVM instance creation failed");
|
||||
Ok(Vmm { kvm })
|
||||
fn new(api_evt: EventFd) -> Result<Self> {
|
||||
let mut epoll = EpollContext::new().map_err(Error::Epoll)?;
|
||||
let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?;
|
||||
let reset_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?;
|
||||
|
||||
if unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0 {
|
||||
epoll.add_stdin().map_err(Error::Epoll)?;
|
||||
}
|
||||
|
||||
epoll
|
||||
.add_event(&exit_evt, EpollDispatch::Exit)
|
||||
.map_err(Error::Epoll)?;
|
||||
|
||||
epoll
|
||||
.add_event(&reset_evt, EpollDispatch::Reset)
|
||||
.map_err(Error::Epoll)?;
|
||||
|
||||
epoll
|
||||
.add_event(&api_evt, EpollDispatch::Api)
|
||||
.map_err(Error::Epoll)?;
|
||||
|
||||
Ok(Vmm {
|
||||
epoll,
|
||||
exit_evt,
|
||||
reset_evt,
|
||||
api_evt,
|
||||
vm: None,
|
||||
vm_config: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn boot_kernel(config: VmConfig) -> Result<()> {
|
||||
loop {
|
||||
let vmm = Vmm::new()?;
|
||||
let mut vm = Vm::new(&vmm.kvm, &config).map_err(Error::VmNew)?;
|
||||
fn vm_boot(&mut self) -> result::Result<(), VmError> {
|
||||
// Create a new VM is we don't have one yet.
|
||||
if self.vm.is_none() {
|
||||
let exit_evt = self.exit_evt.try_clone().map_err(VmError::EventFdClone)?;
|
||||
let reset_evt = self.reset_evt.try_clone().map_err(VmError::EventFdClone)?;
|
||||
|
||||
let entry = vm.load_kernel().map_err(Error::LoadKernel)?;
|
||||
if vm.start(entry).map_err(Error::VmStart)? == ExitBehaviour::Shutdown {
|
||||
break;
|
||||
if let Some(ref vm_config) = self.vm_config {
|
||||
let vm = Vm::new(Arc::clone(vm_config), exit_evt, reset_evt)?;
|
||||
self.vm = Some(vm);
|
||||
}
|
||||
}
|
||||
|
||||
// Now we can boot the VM.
|
||||
if let Some(ref mut vm) = self.vm {
|
||||
vm.boot()
|
||||
} else {
|
||||
Err(VmError::VmNotCreated)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn vm_pause(&mut self) -> result::Result<(), VmError> {
|
||||
if let Some(ref mut vm) = self.vm {
|
||||
vm.pause()
|
||||
} else {
|
||||
Err(VmError::VmNotRunning)
|
||||
}
|
||||
}
|
||||
|
||||
fn vm_resume(&mut self) -> result::Result<(), VmError> {
|
||||
if let Some(ref mut vm) = self.vm {
|
||||
vm.resume()
|
||||
} else {
|
||||
Err(VmError::VmNotRunning)
|
||||
}
|
||||
}
|
||||
|
||||
fn vm_shutdown(&mut self) -> result::Result<(), VmError> {
|
||||
if let Some(ref mut vm) = self.vm.take() {
|
||||
vm.shutdown()
|
||||
} else {
|
||||
Err(VmError::VmNotRunning)
|
||||
}
|
||||
}
|
||||
|
||||
fn vm_reboot(&mut self) -> result::Result<(), VmError> {
|
||||
// Without ACPI, a reset is equivalent to a shutdown
|
||||
#[cfg(not(feature = "acpi"))]
|
||||
{
|
||||
if let Some(ref mut vm) = self.vm {
|
||||
vm.shutdown()?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// First we stop the current VM and create a new one.
|
||||
if let Some(ref mut vm) = self.vm {
|
||||
let config = vm.get_config();
|
||||
vm.shutdown()?;
|
||||
|
||||
let exit_evt = self.exit_evt.try_clone().map_err(VmError::EventFdClone)?;
|
||||
let reset_evt = self.reset_evt.try_clone().map_err(VmError::EventFdClone)?;
|
||||
|
||||
self.vm = Some(Vm::new(config, exit_evt, reset_evt)?);
|
||||
}
|
||||
|
||||
// Then we start the new VM.
|
||||
if let Some(ref mut vm) = self.vm {
|
||||
vm.boot()?;
|
||||
} else {
|
||||
return Err(VmError::VmNotCreated);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn vm_info(&self) -> result::Result<VmInfo, VmError> {
|
||||
match &self.vm_config {
|
||||
Some(config) => {
|
||||
let state = match &self.vm {
|
||||
Some(vm) => vm.get_state()?,
|
||||
None => VmState::Created,
|
||||
};
|
||||
|
||||
Ok(VmInfo {
|
||||
config: Arc::clone(config),
|
||||
state,
|
||||
})
|
||||
}
|
||||
None => Err(VmError::VmNotCreated),
|
||||
}
|
||||
}
|
||||
|
||||
fn vm_delete(&mut self) -> result::Result<(), VmError> {
|
||||
if self.vm_config.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// First we try to shut the current VM down.
|
||||
self.vm_shutdown()?;
|
||||
|
||||
self.vm_config = None;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn vmm_shutdown(&mut self) -> result::Result<(), VmError> {
|
||||
self.vm_delete()
|
||||
}
|
||||
|
||||
fn control_loop(&mut self, api_receiver: Arc<Receiver<ApiRequest>>) -> Result<()> {
|
||||
const EPOLL_EVENTS_LEN: usize = 100;
|
||||
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
let epoll_fd = self.epoll.as_raw_fd();
|
||||
|
||||
'outer: loop {
|
||||
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
// It's well defined from the epoll_wait() syscall
|
||||
// documentation that the epoll loop can be interrupted
|
||||
// before any of the requested events occurred or the
|
||||
// timeout expired. In both those cases, epoll_wait()
|
||||
// returns an error of type EINTR, but this should not
|
||||
// be considered as a regular error. Instead it is more
|
||||
// appropriate to retry, by calling into epoll_wait().
|
||||
continue;
|
||||
}
|
||||
return Err(Error::Epoll(e));
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let dispatch_idx = event.data as usize;
|
||||
|
||||
if let Some(dispatch_type) = self.epoll.dispatch_table[dispatch_idx] {
|
||||
match dispatch_type {
|
||||
EpollDispatch::Exit => {
|
||||
// Consume the event.
|
||||
self.exit_evt.read().map_err(Error::EventFdRead)?;
|
||||
self.vmm_shutdown().map_err(Error::VmmShutdown)?;
|
||||
|
||||
break 'outer;
|
||||
}
|
||||
EpollDispatch::Reset => {
|
||||
// Consume the event.
|
||||
self.reset_evt.read().map_err(Error::EventFdRead)?;
|
||||
self.vm_reboot().map_err(Error::VmReboot)?;
|
||||
}
|
||||
EpollDispatch::Stdin => {
|
||||
if let Some(ref vm) = self.vm {
|
||||
vm.handle_stdin().map_err(Error::Stdin)?;
|
||||
}
|
||||
}
|
||||
EpollDispatch::Api => {
|
||||
// Consume the event.
|
||||
self.api_evt.read().map_err(Error::EventFdRead)?;
|
||||
|
||||
// Read from the API receiver channel
|
||||
let api_request = api_receiver.recv().map_err(Error::ApiRequestRecv)?;
|
||||
|
||||
match api_request {
|
||||
ApiRequest::VmCreate(config, sender) => {
|
||||
// We only store the passed VM config.
|
||||
// The VM will be created when being asked to boot it.
|
||||
let response = if self.vm_config.is_none() {
|
||||
self.vm_config = Some(config);
|
||||
Ok(ApiResponsePayload::Empty)
|
||||
} else {
|
||||
Err(ApiError::VmAlreadyCreated)
|
||||
};
|
||||
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmDelete(sender) => {
|
||||
let response = self
|
||||
.vm_delete()
|
||||
.map_err(ApiError::VmDelete)
|
||||
.map(|_| ApiResponsePayload::Empty);
|
||||
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmBoot(sender) => {
|
||||
// If we don't have a config, we can not boot a VM.
|
||||
if self.vm_config.is_none() {
|
||||
sender
|
||||
.send(Err(ApiError::VmMissingConfig))
|
||||
.map_err(Error::ApiResponseSend)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let response = self
|
||||
.vm_boot()
|
||||
.map_err(ApiError::VmBoot)
|
||||
.map(|_| ApiResponsePayload::Empty);
|
||||
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmShutdown(sender) => {
|
||||
let response = self
|
||||
.vm_shutdown()
|
||||
.map_err(ApiError::VmShutdown)
|
||||
.map(|_| ApiResponsePayload::Empty);
|
||||
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmReboot(sender) => {
|
||||
let response = self
|
||||
.vm_reboot()
|
||||
.map_err(ApiError::VmReboot)
|
||||
.map(|_| ApiResponsePayload::Empty);
|
||||
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmInfo(sender) => {
|
||||
let response = self
|
||||
.vm_info()
|
||||
.map_err(ApiError::VmInfo)
|
||||
.map(ApiResponsePayload::VmInfo);
|
||||
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmPause(sender) => {
|
||||
let response = self
|
||||
.vm_pause()
|
||||
.map_err(ApiError::VmPause)
|
||||
.map(|_| ApiResponsePayload::Empty);
|
||||
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmResume(sender) => {
|
||||
let response = self
|
||||
.vm_resume()
|
||||
.map_err(ApiError::VmResume)
|
||||
.map(|_| ApiResponsePayload::Empty);
|
||||
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmmShutdown(sender) => {
|
||||
let response = self
|
||||
.vmm_shutdown()
|
||||
.map_err(ApiError::VmmShutdown)
|
||||
.map(|_| ApiResponsePayload::Empty);
|
||||
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
1471
vmm/src/vm.rs
1471
vmm/src/vm.rs
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user