mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
285 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6557acf60 | ||
|
|
4d041bd603 | ||
|
|
e0fda7bef9 | ||
|
|
9e2d051237 | ||
|
|
446d2c89d9 | ||
|
|
d6895188b1 | ||
|
|
e2e070ad8a | ||
|
|
6aa10938e4 | ||
|
|
7936bfe680 | ||
|
|
29b522e464 | ||
|
|
da6df6bddf | ||
|
|
b4dd3a52c5 | ||
|
|
4ff11f5dfa | ||
|
|
40faac3d85 | ||
|
|
cbf5068e17 | ||
|
|
2c3908eed9 | ||
|
|
8ba1bc7621 | ||
|
|
8d11ba4833 | ||
|
|
273098ee99 | ||
|
|
84f1377903 | ||
|
|
9592accb46 | ||
|
|
a25b49e80c | ||
|
|
b0324f8557 | ||
|
|
faa632feaa | ||
|
|
421e51fb55 | ||
|
|
71370d7e15 | ||
|
|
8458696094 | ||
|
|
4cc44f8cc7 | ||
|
|
846bd3a504 | ||
|
|
c11d430651 | ||
|
|
9978aac402 | ||
|
|
082872423f | ||
|
|
b869e65e7e | ||
|
|
04bfb3d93e | ||
|
|
acafda6739 | ||
|
|
6db3d89837 | ||
|
|
7c965538a6 | ||
|
|
f254f11b48 | ||
|
|
18d51d3bd1 | ||
|
|
70a3564a5e | ||
|
|
128d0984a2 | ||
|
|
5e3d72671b | ||
|
|
60d805771b | ||
|
|
3f4cbce900 | ||
|
|
5343e09e7b | ||
|
|
7dc9259c6c | ||
|
|
e7a0d6b4e4 | ||
|
|
fdda680eb4 | ||
|
|
d744ce10ef | ||
|
|
4552d07a80 | ||
|
|
ec7d4e727a | ||
|
|
9cbb94b6d9 | ||
|
|
6c1fe07d90 | ||
|
|
92a8fbbff8 | ||
|
|
f2dfb44d5c | ||
|
|
9d1985111a | ||
|
|
a4b478b96a | ||
|
|
3069ce9f2d | ||
|
|
c35220b1ca | ||
|
|
ece4fc2026 | ||
|
|
05769879dd | ||
|
|
28413d26db | ||
|
|
a08da07d2a | ||
|
|
00fbd77494 | ||
|
|
23fb4fa26d | ||
|
|
8846409eab | ||
|
|
020b64a9ef | ||
|
|
c504f302e9 | ||
|
|
ceb43fae6d | ||
|
|
2451c4d833 | ||
|
|
23bb629241 | ||
|
|
bae63a8b8c | ||
|
|
2f430e08e1 | ||
|
|
f1c4705638 | ||
|
|
a2a492f3df | ||
|
|
f452e51488 | ||
|
|
9f111388c0 | ||
|
|
9f27954fbd | ||
|
|
603ca0e21b | ||
|
|
7c6bdf8804 | ||
|
|
afb1c761ec | ||
|
|
271bacc4ef | ||
|
|
17722ada51 | ||
|
|
3a6df69d45 | ||
|
|
a116add991 | ||
|
|
dd77070f16 | ||
|
|
df86d4c50f | ||
|
|
81d53c5711 | ||
|
|
3844bd20d1 | ||
|
|
a6cd2e10c1 | ||
|
|
0fc3fad363 | ||
|
|
bf6920f774 | ||
|
|
28d6bff340 | ||
|
|
0fa31539eb | ||
|
|
297459087a | ||
|
|
8cf3de3c85 | ||
|
|
97f5b68424 | ||
|
|
2861330d09 | ||
|
|
55b8a2182b | ||
|
|
b1f61d7286 | ||
|
|
7e22786b2c | ||
|
|
eade9d8b87 | ||
|
|
1a5b94ee0e | ||
|
|
2805e7b1dc | ||
|
|
3593055e77 | ||
|
|
5447451921 | ||
|
|
b2bf88bcb1 | ||
|
|
75fbea3c46 | ||
|
|
e3bb822c4e | ||
|
|
9acb69f3e3 | ||
|
|
4c4eb8db40 | ||
|
|
6daddf0831 | ||
|
|
5d55070ceb | ||
|
|
0ac094c0d1 | ||
|
|
cb844ecd1d | ||
|
|
a3dfe726f8 | ||
|
|
9aab70ec1a | ||
|
|
dfcb0a192a | ||
|
|
8c19d526cd | ||
|
|
580bab6008 | ||
|
|
46d6a6cdfd | ||
|
|
b7512d3d83 | ||
|
|
51c5ba9610 | ||
|
|
09e21e8fe5 | ||
|
|
d27316dab6 | ||
|
|
98bbfa9738 | ||
|
|
36852795e4 | ||
|
|
6f571d5c07 | ||
|
|
5bd305fa4f | ||
|
|
d650c684f1 | ||
|
|
458ae2c5be | ||
|
|
1025783973 | ||
|
|
411b8b3a96 | ||
|
|
0862064fd2 | ||
|
|
e41fe0acae | ||
|
|
a7024074c0 | ||
|
|
a50c10bdf9 | ||
|
|
b806935941 | ||
|
|
1cf73c83e2 | ||
|
|
dc91866b56 | ||
|
|
6b915de2e0 | ||
|
|
1a17564ee9 | ||
|
|
c9c031a425 | ||
|
|
0ab4097606 | ||
|
|
f1151cb55e | ||
|
|
9b0bd71154 | ||
|
|
1603841f91 | ||
|
|
9c6e7c4a4b | ||
|
|
0ee8ead53d | ||
|
|
5fd49927f2 | ||
|
|
80c34bd756 | ||
|
|
76d3d3d505 | ||
|
|
f16fe56e33 | ||
|
|
d9eff12ba3 | ||
|
|
42b5d4a2f7 | ||
|
|
200b13517b | ||
|
|
07d78e6a12 | ||
|
|
1659a0cb6f | ||
|
|
6b44f0d12a | ||
|
|
5752a2a4fb | ||
|
|
5d2db68f67 | ||
|
|
b780a916bb | ||
|
|
16782e8c6d | ||
|
|
bd024bffb1 | ||
|
|
ddef99a0ff | ||
|
|
1591cae440 | ||
|
|
ea4ab9a4cc | ||
|
|
55479a64d2 | ||
|
|
d0789871a5 | ||
|
|
57184f110a | ||
|
|
20b9f95afd | ||
|
|
6994b33a24 | ||
|
|
da20fa74c6 | ||
|
|
384752647a | ||
|
|
052f38fa96 | ||
|
|
414e8e068b | ||
|
|
c325aa3297 | ||
|
|
db5d74cb26 | ||
|
|
3982a2af0f | ||
|
|
072114d7c5 | ||
|
|
5e19422fcf | ||
|
|
26d1a76ad9 | ||
|
|
e864e35c29 | ||
|
|
8c170570c4 | ||
|
|
10676b74dc | ||
|
|
6717ac0cf2 | ||
|
|
df38ef56be | ||
|
|
747003d33c | ||
|
|
5ec693aa2a | ||
|
|
52e2286916 | ||
|
|
507912385a | ||
|
|
b552aebbd4 | ||
|
|
85f5364594 | ||
|
|
7f987552ef | ||
|
|
a3a175216a | ||
|
|
7a3d8c6299 | ||
|
|
832f09a075 | ||
|
|
2f0073544a | ||
|
|
3c421593c3 | ||
|
|
2198baa547 | ||
|
|
7b93a8dd78 | ||
|
|
1bd30dc69f | ||
|
|
e5bc5e632d | ||
|
|
5cdb284db2 | ||
|
|
4c23f38759 | ||
|
|
b3ca1d90e9 | ||
|
|
a45e458c50 | ||
|
|
a061bc32e4 | ||
|
|
71148e1fa5 | ||
|
|
41f2f41ed2 | ||
|
|
8a066466ed | ||
|
|
9c759463c3 | ||
|
|
424efe6c69 | ||
|
|
bea100707f | ||
|
|
ac00838f34 | ||
|
|
e9b8126cf7 | ||
|
|
e575582dfa | ||
|
|
804bad53df | ||
|
|
0dafd47a7c | ||
|
|
9bd1ece9cf | ||
|
|
e9eb2c5dbc | ||
|
|
4e46a1bc3c | ||
|
|
400c28fa58 | ||
|
|
b9d6cbf5f6 | ||
|
|
e0f794f8ba | ||
|
|
6cbe08fa40 | ||
|
|
8f74bd5bd2 | ||
|
|
77df4e6773 | ||
|
|
c99d637693 | ||
|
|
8eed276d14 | ||
|
|
8759c3db3e | ||
|
|
e2225bb4b0 | ||
|
|
09f5b82fd7 | ||
|
|
7731d2f1be | ||
|
|
4becb11a44 | ||
|
|
ce984b73f5 | ||
|
|
3e1ce98d1a | ||
|
|
75b9e70ec8 | ||
|
|
7d09df468d | ||
|
|
f29f3085ed | ||
|
|
ce6446501d | ||
|
|
5ee0291ddc | ||
|
|
6672334abc | ||
|
|
24b6def951 | ||
|
|
4253192ffc | ||
|
|
eee10b4b01 | ||
|
|
34c5116883 | ||
|
|
8fc3b970cd | ||
|
|
816bf3abf0 | ||
|
|
de3e003e3e | ||
|
|
d3081ff50c | ||
|
|
ef9a388f87 | ||
|
|
74dabf5b90 | ||
|
|
5d80084868 | ||
|
|
284b616738 | ||
|
|
b85c5faafc | ||
|
|
d71f073af3 | ||
|
|
1c1472b88a | ||
|
|
6e4eb01c20 | ||
|
|
3adc654680 | ||
|
|
bbcb4bc828 | ||
|
|
1ee801450e | ||
|
|
365e4e6bc2 | ||
|
|
8d5b4f9707 | ||
|
|
f57a04830c | ||
|
|
25ce38bee1 | ||
|
|
5847e73f40 | ||
|
|
53caa565bb | ||
|
|
db5583d6a3 | ||
|
|
1ed49a2306 | ||
|
|
e9b069bf38 | ||
|
|
8f4aa07a80 | ||
|
|
5462fd810c | ||
|
|
81c5855184 | ||
|
|
53060874a7 | ||
|
|
9bcb984962 | ||
|
|
ae68c2f31a | ||
|
|
1f5e2a38a7 | ||
|
|
cf1c64171a | ||
|
|
9fe33890f8 | ||
|
|
7b9a110540 | ||
|
|
2acb50f752 | ||
|
|
ea0729c016 | ||
|
|
cdc14815be | ||
|
|
4fda4ad6c9 |
4
.github/workflows/build.yaml
vendored
4
.github/workflows/build.yaml
vendored
@@ -13,6 +13,7 @@ jobs:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
- 1.54
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
@@ -22,6 +23,9 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install musl-gcc
|
||||
run: sudo apt install -y musl-tools
|
||||
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
|
||||
15
.github/workflows/docker-image.yaml
vendored
15
.github/workflows/docker-image.yaml
vendored
@@ -27,6 +27,17 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
# list of Docker images to use as base name for tags
|
||||
images: cloudhypervisor/dev
|
||||
# generate Docker tags based on the following events/attributes
|
||||
tags: |
|
||||
type=raw,value={{date 'YYYYMMDD'}}-0
|
||||
type=sha
|
||||
|
||||
- name: Build and push
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
uses: docker/build-push-action@v2
|
||||
@@ -34,7 +45,7 @@ jobs:
|
||||
file: ./resources/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: cloudhypervisor/dev:latest
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
|
||||
- name: Build only
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
@@ -42,7 +53,7 @@ jobs:
|
||||
with:
|
||||
file: ./resources/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: cloudhypervisor/dev:latest
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
|
||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -9,6 +9,8 @@ jobs:
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install musl-gcc
|
||||
run: sudo apt install -y musl-tools
|
||||
- name: Create release directory
|
||||
run: rsync -rv --exclude=.git . ../cloud-hypervisor-${{ github.event.ref }}
|
||||
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
|
||||
|
||||
193
Cargo.lock
generated
193
Cargo.lock
generated
@@ -20,9 +20,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.52"
|
||||
version = "1.0.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
|
||||
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
|
||||
|
||||
[[package]]
|
||||
name = "api_client"
|
||||
@@ -73,9 +73,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
@@ -119,9 +119,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.72"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@@ -131,9 +131,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.0.10"
|
||||
version = "3.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a30c3bf9ff12dfe5dae53f0a96e0febcd18420d1c0e7fad77796d9d5c4b5375"
|
||||
checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
@@ -148,12 +148,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cloud-hypervisor"
|
||||
version = "21.1.0"
|
||||
version = "22.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"api_client",
|
||||
"clap",
|
||||
"dirs 4.0.0",
|
||||
"dirs",
|
||||
"epoll",
|
||||
"event_monitor",
|
||||
"hypervisor",
|
||||
@@ -208,15 +208,6 @@ dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "4.0.0"
|
||||
@@ -277,10 +268,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b643857cf70949306b81d7e92cb9d47add673868edac9863c4a49c42feaf3f1e"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.4"
|
||||
name = "gdbstub"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
|
||||
checksum = "9fa2ca5d6b045de372cef3991f873389b421b4cabcfbe52f7787fae8b8b37906"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"log",
|
||||
"managed",
|
||||
"num-traits",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gdbstub_arch"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51dc4b5718ac76d21e8605c0966dd32d80273b89b11e6cfef467b04e45934d37"
|
||||
dependencies = [
|
||||
"gdbstub",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
@@ -338,9 +353,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "iced-x86"
|
||||
version = "1.15.0"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46e977036f7f5139d580c7f19ad62df9cb8ebd8410bb569e73585226be80a86f"
|
||||
checksum = "158f5204401d08f91d19176112146d75e99b3cf745092e268fa7be33e09adcec"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"static_assertions",
|
||||
@@ -393,7 +408,7 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||
[[package]]
|
||||
name = "kvm-bindings"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/cloud-hypervisor/kvm-bindings?branch=ch-v0.5.0#9c497710ba9968d8efe15dbae03991b16cf82e23"
|
||||
source = "git+https://github.com/cloud-hypervisor/kvm-bindings?branch=ch-v0.5.0-tdx#52e56d0e8ef0f6ea32fc0492e6a175b73617a49f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -403,7 +418,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "kvm-ioctls"
|
||||
version = "0.11.0"
|
||||
source = "git+https://github.com/rust-vmm/kvm-ioctls?branch=main#d22ef1f51852dfb055da38004e1a4fed81246f81"
|
||||
source = "git+https://github.com/rust-vmm/kvm-ioctls?branch=main#1e03e29cdfbb0cb108a98de7a78045a5a517f18e"
|
||||
dependencies = [
|
||||
"kvm-bindings",
|
||||
"libc",
|
||||
@@ -418,9 +433,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.112"
|
||||
version = "0.2.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
||||
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
||||
|
||||
[[package]]
|
||||
name = "libssh2-sys"
|
||||
@@ -459,9 +474,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
|
||||
checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
@@ -475,25 +490,22 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "managed"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "micro_http"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#0a58eb1ece68e326e68365c4297d0a7c08ecd9bc"
|
||||
source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#a730d86940081ad044cdfbc1285c1db6d3048392"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"vmm-sys-util",
|
||||
@@ -502,7 +514,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "mshv-bindings"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/mshv?branch=main#b8b69b655337292037219ab765d2c94c565a076e"
|
||||
source = "git+https://github.com/rust-vmm/mshv?branch=main#d241ffcacdfa4240b4a1d34b44af89dc7b113986"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"serde",
|
||||
@@ -514,7 +526,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "mshv-ioctls"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/mshv?branch=main#b8b69b655337292037219ab765d2c94c565a076e"
|
||||
source = "git+https://github.com/rust-vmm/mshv?branch=main#d241ffcacdfa4240b4a1d34b44af89dc7b113986"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mshv-bindings",
|
||||
@@ -551,6 +563,24 @@ dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "111.17.0+1.1.1m"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05d6a336abd10814198f66e2a91ccd7336611f30334119ca8ce300536666fcf4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.72"
|
||||
@@ -560,6 +590,7 @@ dependencies = [
|
||||
"autocfg",
|
||||
"cc",
|
||||
"libc",
|
||||
"openssl-src",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
@@ -602,6 +633,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5"
|
||||
|
||||
[[package]]
|
||||
name = "pci"
|
||||
version = "0.1.0"
|
||||
@@ -611,6 +648,8 @@ dependencies = [
|
||||
"hypervisor",
|
||||
"libc",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"thiserror",
|
||||
"versionize",
|
||||
"versionize_derive",
|
||||
@@ -624,6 +663,20 @@ dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "performance-metrics"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"dirs",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"test_infra",
|
||||
"thiserror",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.24"
|
||||
@@ -740,9 +793,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.14"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
|
||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -758,9 +811,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
||||
checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
@@ -835,21 +888,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.4"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
|
||||
checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.133"
|
||||
version = "1.0.136"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
|
||||
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.133"
|
||||
version = "1.0.136"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
|
||||
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -858,9 +911,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.75"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c059c05b48c5c0067d4b4b2b4f0732dd65feb52daf7e0ea09cd87e7dadc1af79"
|
||||
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -918,9 +971,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.85"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7"
|
||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -941,9 +994,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
@@ -962,8 +1015,9 @@ dependencies = [
|
||||
name = "test_infra"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dirs 3.0.2",
|
||||
"dirs",
|
||||
"epoll",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"ssh2",
|
||||
"vmm-sys-util",
|
||||
@@ -972,9 +1026,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.14.2"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
dependencies = [
|
||||
"terminal_size",
|
||||
]
|
||||
@@ -1059,7 +1113,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "vfio-ioctls"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/vfio-ioctls?branch=main#19e5b83ddfad430109ce3e8d51f4a24796099127"
|
||||
source = "git+https://github.com/rust-vmm/vfio?branch=main#f75a77c1ab6349c105bc1462a65508726b4c2e0f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"kvm-bindings",
|
||||
@@ -1117,17 +1171,16 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vhost_user_backend"
|
||||
name = "vhost-user-backend"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/vhost-user-backend?branch=main#bbc892ba4526bdf8101252f7aa51832d1f2eeabd"
|
||||
dependencies = [
|
||||
"epoll",
|
||||
"libc",
|
||||
"log",
|
||||
"vhost",
|
||||
"virtio-bindings",
|
||||
"virtio-queue",
|
||||
"vm-memory",
|
||||
"vm-virtio",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
@@ -1144,7 +1197,7 @@ dependencies = [
|
||||
"option_parser",
|
||||
"qcow",
|
||||
"vhost",
|
||||
"vhost_user_backend",
|
||||
"vhost-user-backend",
|
||||
"virtio-bindings",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
@@ -1162,7 +1215,7 @@ dependencies = [
|
||||
"net_util",
|
||||
"option_parser",
|
||||
"vhost",
|
||||
"vhost_user_backend",
|
||||
"vhost-user-backend",
|
||||
"virtio-bindings",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
@@ -1211,9 +1264,9 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "virtio-queue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/vm-virtio?branch=main#bbb22d43558a76cc7cc9c262c4be870a9e682a86"
|
||||
dependencies = [
|
||||
"log",
|
||||
"memoffset",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
@@ -1244,7 +1297,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "vm-fdt"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/rust-vmm/vm-fdt?branch=main#9cfa0c8d7c3032f79589c7b01c8722a37f4cf44f"
|
||||
source = "git+https://github.com/rust-vmm/vm-fdt?branch=main#ca35d96191f8232bd7ab8c3e72ec925bf04bd2e0"
|
||||
|
||||
[[package]]
|
||||
name = "vm-memory"
|
||||
@@ -1295,6 +1348,8 @@ dependencies = [
|
||||
"devices",
|
||||
"epoll",
|
||||
"event_monitor",
|
||||
"gdbstub",
|
||||
"gdbstub_arch",
|
||||
"hypervisor",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
|
||||
25
Cargo.toml
25
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cloud-hypervisor"
|
||||
version = "21.1.0"
|
||||
version = "22.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
default-run = "cloud-hypervisor"
|
||||
@@ -8,22 +8,25 @@ build = "build.rs"
|
||||
license = "LICENSE-APACHE & LICENSE-BSD-3-Clause"
|
||||
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM"
|
||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||
# Minimum buildable version:
|
||||
# Keep in sync with version in .github/workflows/build.yaml
|
||||
rust-version = "1.54"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.52"
|
||||
anyhow = "1.0.55"
|
||||
api_client = { path = "api_client" }
|
||||
clap = { version = "3.0.10", features = ["wrap_help","cargo"] }
|
||||
clap = { version = "3.1.5", features = ["wrap_help","cargo"] }
|
||||
epoll = "4.3.1"
|
||||
event_monitor = { path = "event_monitor" }
|
||||
hypervisor = { path = "hypervisor" }
|
||||
libc = "0.2.112"
|
||||
libc = "0.2.119"
|
||||
log = { version = "0.4.14", features = ["std"] }
|
||||
option_parser = { path = "option_parser" }
|
||||
seccompiler = "0.2.0"
|
||||
serde_json = "1.0.75"
|
||||
serde_json = "1.0.79"
|
||||
signal-hook = "0.3.13"
|
||||
thiserror = "1.0.30"
|
||||
vmm = { path = "vmm" }
|
||||
@@ -31,19 +34,20 @@ vmm-sys-util = "0.9.0"
|
||||
vm-memory = "0.7.0"
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version = "3.0.10", features = ["wrap_help"] }
|
||||
clap = { version = "3.1.5", features = ["wrap_help"] }
|
||||
|
||||
# List of patched crates
|
||||
[patch.crates-io]
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.5.0", features = ["with-serde", "fam-wrappers"] }
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.5.0-tdx" }
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "main" }
|
||||
versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch" }
|
||||
virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", branch = "main" }
|
||||
|
||||
[dev-dependencies]
|
||||
dirs = "4.0.0"
|
||||
lazy_static= "1.4.0"
|
||||
net_util = { path = "net_util" }
|
||||
serde_json = "1.0.75"
|
||||
serde_json = "1.0.79"
|
||||
test_infra = { path = "test_infra" }
|
||||
wait-timeout = "0.2.0"
|
||||
|
||||
@@ -54,6 +58,7 @@ common = ["acpi", "cmos", "fwdebug"]
|
||||
acpi = ["vmm/acpi"]
|
||||
cmos = ["vmm/cmos"]
|
||||
fwdebug = ["vmm/fwdebug"]
|
||||
gdb = ["vmm/gdb"]
|
||||
kvm = ["vmm/kvm"]
|
||||
mshv = ["vmm/mshv"]
|
||||
tdx = ["vmm/tdx"]
|
||||
@@ -71,19 +76,17 @@ members = [
|
||||
"net_util",
|
||||
"option_parser",
|
||||
"pci",
|
||||
"performance-metrics",
|
||||
"qcow",
|
||||
"rate_limiter",
|
||||
"vfio_user",
|
||||
"vhdx",
|
||||
"vhost_user_backend",
|
||||
"vhost_user_block",
|
||||
"vhost_user_net",
|
||||
"virtio-devices",
|
||||
"virtio-queue",
|
||||
"vmm",
|
||||
"vm-allocator",
|
||||
"vm-device",
|
||||
"vm-migration",
|
||||
"vm-virtio"
|
||||
]
|
||||
exclude = ["test_infra"]
|
||||
|
||||
115
Jenkinsfile
vendored
115
Jenkinsfile
vendored
@@ -1,9 +1,28 @@
|
||||
def runWorkers = true
|
||||
pipeline{
|
||||
agent none
|
||||
stages {
|
||||
stage ('Early checks') {
|
||||
agent { node { label 'built-in' } }
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
stage ('Check for documentation only changes') {
|
||||
when {
|
||||
expression {
|
||||
return docsFileOnly()
|
||||
}
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
runWorkers = false
|
||||
echo "Documentation only changes, no need to run the CI"
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Check for RFC/WIP builds') {
|
||||
when {
|
||||
changeRequest comparator: 'REGEXP', title: '.*(rfc|RFC|wip|WIP).*'
|
||||
@@ -22,11 +41,14 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Build') {
|
||||
parallel {
|
||||
parallel {
|
||||
stage ('Worker build') {
|
||||
agent { node { label 'hirsute' } }
|
||||
environment {
|
||||
AZURE_CONNECTION_STRING = credentials('46b4e7d6-315f-4cc1-8333-b58780863b9b')
|
||||
when {
|
||||
beforeAgent true
|
||||
expression {
|
||||
return runWorkers
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
@@ -58,6 +80,12 @@ pipeline{
|
||||
}
|
||||
stage ('AArch64 worker build') {
|
||||
agent { node { label 'bionic-arm64' } }
|
||||
when {
|
||||
beforeAgent true
|
||||
expression {
|
||||
return runWorkers
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
@@ -66,7 +94,7 @@ pipeline{
|
||||
}
|
||||
stage ('Run unit tests') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --unit"
|
||||
sh "scripts/dev_cli.sh tests --unit --libc musl"
|
||||
}
|
||||
}
|
||||
stage ('Run integration tests') {
|
||||
@@ -75,7 +103,7 @@ pipeline{
|
||||
}
|
||||
steps {
|
||||
sh "sudo modprobe openvswitch"
|
||||
sh "scripts/dev_cli.sh tests --integration"
|
||||
sh "scripts/dev_cli.sh tests --integration --libc musl"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,8 +116,11 @@ pipeline{
|
||||
}
|
||||
stage ('Worker build (musl)') {
|
||||
agent { node { label 'hirsute' } }
|
||||
environment {
|
||||
AZURE_CONNECTION_STRING = credentials('46b4e7d6-315f-4cc1-8333-b58780863b9b')
|
||||
when {
|
||||
beforeAgent true
|
||||
expression {
|
||||
return runWorkers
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
@@ -118,7 +149,12 @@ pipeline{
|
||||
agent { node { label 'bionic-sgx' } }
|
||||
when {
|
||||
beforeAgent true
|
||||
branch 'main'
|
||||
allOf {
|
||||
branch 'main'
|
||||
expression {
|
||||
return runWorkers
|
||||
}
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
@@ -154,7 +190,12 @@ pipeline{
|
||||
agent { node { label 'bionic-vfio' } }
|
||||
when {
|
||||
beforeAgent true
|
||||
branch 'main'
|
||||
allOf {
|
||||
branch 'main'
|
||||
expression {
|
||||
return runWorkers
|
||||
}
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
@@ -188,6 +229,12 @@ pipeline{
|
||||
}
|
||||
stage ('Worker build - Windows guest') {
|
||||
agent { node { label 'hirsute' } }
|
||||
when {
|
||||
beforeAgent true
|
||||
expression {
|
||||
return runWorkers
|
||||
}
|
||||
}
|
||||
environment {
|
||||
AZURE_CONNECTION_STRING = credentials('46b4e7d6-315f-4cc1-8333-b58780863b9b')
|
||||
}
|
||||
@@ -228,6 +275,12 @@ pipeline{
|
||||
}
|
||||
stage ('Worker build - Live Migration') {
|
||||
agent { node { label 'hirsute-small' } }
|
||||
when {
|
||||
beforeAgent true
|
||||
expression {
|
||||
return runWorkers
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
@@ -254,6 +307,39 @@ pipeline{
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Worker build - Metrics') {
|
||||
agent { node { label 'focal-metrics' } }
|
||||
when {
|
||||
branch 'main'
|
||||
beforeAgent true
|
||||
expression {
|
||||
return runWorkers
|
||||
}
|
||||
}
|
||||
environment {
|
||||
METRICS_PUBLISH_KEY = credentials('52e0945f-ce7a-43d1-87af-67d1d87cc40f')
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
stage ('Run metrics tests') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh 'scripts/dev_cli.sh tests --metrics -- -- --report-file /root/workloads/metrics.json'
|
||||
}
|
||||
}
|
||||
stage ('Upload metrics report') {
|
||||
steps {
|
||||
sh 'curl -X PUT https://cloud-hypervisor-metrics.azurewebsites.net/api/publishmetrics -H "x-functions-key: $METRICS_PUBLISH_KEY" -T ~/workloads/metrics.json'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,3 +383,14 @@ def installAzureCli() {
|
||||
sh "sudo apt update"
|
||||
sh "sudo apt install -y azure-cli"
|
||||
}
|
||||
|
||||
def boolean docsFileOnly() {
|
||||
if (env.CHANGE_TARGET == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return sh(
|
||||
returnStatus: true,
|
||||
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v '\\.md'"
|
||||
) != 0
|
||||
}
|
||||
|
||||
151
README.md
151
README.md
@@ -27,12 +27,19 @@
|
||||
|
||||
# 1. What is Cloud Hypervisor?
|
||||
|
||||
Cloud Hypervisor is an open source Virtual Machine Monitor (VMM) that runs on top of [KVM](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt) and the MSHV hypervisors .
|
||||
Cloud Hypervisor is an open source Virtual Machine Monitor (VMM) that runs on
|
||||
top of [KVM](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt)
|
||||
hypervisor and Microsoft Hypervisor (MSHV).
|
||||
|
||||
The project focuses on exclusively running modern, cloud workloads, on top of a limited set of hardware architectures and platforms.
|
||||
Cloud workloads refers to those that are usually run by customers inside a cloud provider. For our purposes this means modern operating systems with most I/O handled by paravirtualised devices (i.e. virtio), no requirement for legacy devices, and 64-bit CPUs.
|
||||
The project focuses on exclusively running modern, cloud workloads, on top of
|
||||
a limited set of hardware architectures and platforms. Cloud workloads refers
|
||||
to those that are usually run by customers inside a cloud provider. For our
|
||||
purposes this means modern operating systems with most I/O handled by
|
||||
paravirtualised devices (i.e. virtio), no requirement for legacy devices, and
|
||||
64-bit CPUs.
|
||||
|
||||
Cloud Hypervisor is implemented in [Rust](https://www.rust-lang.org/) and is based on the [rust-vmm](https://github.com/rust-vmm) crates.
|
||||
Cloud Hypervisor is implemented in [Rust](https://www.rust-lang.org/) and is
|
||||
based on the [rust-vmm](https://github.com/rust-vmm) crates.
|
||||
|
||||
## Objectives
|
||||
|
||||
@@ -51,7 +58,9 @@ Cloud Hypervisor is implemented in [Rust](https://www.rust-lang.org/) and is bas
|
||||
|
||||
### Architectures
|
||||
|
||||
Cloud Hypervisor supports the `x86-64` and `AArch64` architectures. There are some small differences in functionality between the two architectures (see [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125)).
|
||||
Cloud Hypervisor supports the `x86-64` and `AArch64` architectures. There are
|
||||
some small differences in functionality between the two architectures
|
||||
(see [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125)).
|
||||
|
||||
### Guest OS
|
||||
|
||||
@@ -74,9 +83,9 @@ $ mkdir $CLOUDH
|
||||
|
||||
## Install prerequisites
|
||||
|
||||
You need to install some prerequisite packages in order to build and test Cloud Hypervisor.
|
||||
Here, all the steps are based on Ubuntu, for other Linux distributions please replace the
|
||||
package manager and package name.
|
||||
You need to install some prerequisite packages in order to build and test Cloud
|
||||
Hypervisor. Here, all the steps are based on Ubuntu, for other Linux
|
||||
distributions please replace the package manager and package name.
|
||||
|
||||
```shell
|
||||
# Install git
|
||||
@@ -107,7 +116,8 @@ $ cargo build --release --target=x86_64-unknown-linux-musl --all
|
||||
$ popd
|
||||
```
|
||||
|
||||
This will build a `cloud-hypervisor` binary under `$CLOUDH/cloud-hypervisor/target/release/cloud-hypervisor`.
|
||||
This will build a `cloud-hypervisor` binary under
|
||||
`$CLOUDH/cloud-hypervisor/target/release/cloud-hypervisor`.
|
||||
|
||||
### Containerized builds and tests
|
||||
|
||||
@@ -139,18 +149,20 @@ development script commands and their related options.
|
||||
|
||||
## Run
|
||||
|
||||
You can run a guest VM by either using an existing cloud image or booting into your own kernel and disk image.
|
||||
You can run a guest VM by either using an existing cloud image or booting into
|
||||
your own kernel and disk image.
|
||||
|
||||
### Cloud image
|
||||
|
||||
Cloud Hypervisor supports booting disk images containing all needed
|
||||
components to run cloud workloads, a.k.a. cloud images. To do that we rely on
|
||||
the [Rust Hypervisor
|
||||
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) project to provide
|
||||
an ELF
|
||||
formatted KVM firmware for `cloud-hypervisor` to directly boot into.
|
||||
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) project
|
||||
to provide an ELF formatted KVM firmware for `cloud-hypervisor` to directly
|
||||
boot into.
|
||||
|
||||
We need to get the latest `rust-hypervisor-firmware` release and also a working cloud image. Here we will use a Ubuntu image:
|
||||
We need to get the latest `rust-hypervisor-firmware` release and also a working
|
||||
cloud image. Here we will use a Ubuntu image:
|
||||
|
||||
```shell
|
||||
$ pushd $CLOUDH
|
||||
@@ -178,7 +190,10 @@ Multiple arguments can be given to the `--disk` parameter.
|
||||
|
||||
#### Building your kernel
|
||||
|
||||
Cloud Hypervisor also supports direct kernel boot into a `vmlinux` ELF kernel. In order to support virtio-iommu we have our own development branch. You are of course able to use your own kernel but these instructions will continue with the version that we develop and test against.
|
||||
Cloud Hypervisor also supports direct kernel boot into a `vmlinux` ELF kernel.
|
||||
In order to support virtio-watchdog we have our own development branch. You are
|
||||
of course able to use your own kernel but these instructions will continue with
|
||||
the version that we develop and test against.
|
||||
|
||||
To build the kernel:
|
||||
|
||||
@@ -195,11 +210,13 @@ $ KCFLAGS="-Wa,-mx86-used-note=no" make bzImage -j `nproc`
|
||||
$ popd
|
||||
```
|
||||
|
||||
The `vmlinux` kernel image will then be located at `linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin`.
|
||||
The `vmlinux` kernel image will then be located at
|
||||
`linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin`.
|
||||
|
||||
#### Disk image
|
||||
|
||||
For the disk image, we will use a Ubuntu cloud image that contains a root partition:
|
||||
For the disk image, we will use a Ubuntu cloud image that contains a root
|
||||
partition:
|
||||
|
||||
```shell
|
||||
$ pushd $CLOUDH
|
||||
@@ -210,8 +227,8 @@ $ popd
|
||||
|
||||
#### Booting the guest VM
|
||||
|
||||
Now we can directly boot into our custom kernel and make it use the Ubuntu root partition.
|
||||
If we want to have 4 vCPUs and 1024 MBytes of memory:
|
||||
Now we can directly boot into our custom kernel and make it use the Ubuntu root
|
||||
partition. If we want to have 4 vCPUs and 1024 MBytes of memory:
|
||||
|
||||
```shell
|
||||
$ pushd $CLOUDH
|
||||
@@ -246,7 +263,8 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
|
||||
# 3. Status
|
||||
|
||||
Cloud Hypervisor is under active development. The following stability guarantees are currently made:
|
||||
Cloud Hypervisor is under active development. The following stability guarantees
|
||||
are currently made:
|
||||
|
||||
* The API (including command line options) will not be removed or changed in a
|
||||
breaking way without a minimum of 2 releases notice. Where possible warnings
|
||||
@@ -270,70 +288,87 @@ As of 2021-04-29, the following cloud images are supported:
|
||||
- [Ubuntu Groovy](https://cloud-images.ubuntu.com/groovy/current/) (cloudimg)
|
||||
- [Ubuntu Hirsute](https://cloud-images.ubuntu.com/hirsute/current/) (cloudimg)
|
||||
|
||||
Direct kernel boot to userspace should work with a rootfs from most distributions.
|
||||
Direct kernel boot to userspace should work with a rootfs from most
|
||||
distributions.
|
||||
|
||||
## Hot Plug
|
||||
|
||||
Cloud Hypervisor supports hotplug of CPUs, passthrough devices (VFIO), `virtio-{net,block,pmem,fs,vsock}` and memory resizing. This [document](docs/hotplug.md) details how to add devices to
|
||||
a running VM.
|
||||
Cloud Hypervisor supports hotplug of CPUs, passthrough devices (VFIO),
|
||||
`virtio-{net,block,pmem,fs,vsock}` and memory resizing. This
|
||||
[document](docs/hotplug.md) details how to add devices to a running VM.
|
||||
|
||||
## Device Model
|
||||
|
||||
Details of the device model can be found in this [documentation](docs/device_model.md).
|
||||
Details of the device model can be found in this
|
||||
[documentation](docs/device_model.md).
|
||||
|
||||
## TODO
|
||||
|
||||
We are not tracking the Cloud Hypervisor TODO list from a specific git tracked file but through
|
||||
[github issues](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new) instead.
|
||||
We are not tracking the Cloud Hypervisor TODO list from a specific git tracked
|
||||
file but through
|
||||
[github issues](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new)
|
||||
instead.
|
||||
|
||||
# 4. `rust-vmm` project dependency
|
||||
|
||||
In order to satisfy the design goal of having a high-performance, security-focused hypervisor the decision
|
||||
was made to use the [Rust](https://www.rust-lang.org/) programming language.
|
||||
The language's strong focus on memory and thread safety makes it an ideal candidate for implementing VMMs.
|
||||
In order to satisfy the design goal of having a high-performance,
|
||||
security-focused hypervisor the decision was made to use the
|
||||
[Rust](https://www.rust-lang.org/) programming language. The language's strong
|
||||
focus on memory and thread safety makes it an ideal candidate for implementing
|
||||
VMMs.
|
||||
|
||||
Instead of implementing the VMM components from scratch, Cloud Hypervisor is importing the [rust-vmm](https://github.com/rust-vmm)
|
||||
crates, and sharing code and architecture together with other VMMs like e.g. Amazon's [Firecracker](https://firecracker-microvm.github.io/)
|
||||
and Google's [crosvm](https://chromium.googlesource.com/chromiumos/platform/crosvm/).
|
||||
Instead of implementing the VMM components from scratch, Cloud Hypervisor is
|
||||
importing the [rust-vmm](https://github.com/rust-vmm) crates, and sharing code
|
||||
and architecture together with other VMMs like e.g. Amazon's
|
||||
[Firecracker](https://firecracker-microvm.github.io/) and Google's
|
||||
[crosvm](https://chromium.googlesource.com/chromiumos/platform/crosvm/).
|
||||
|
||||
Cloud Hypervisor embraces the rust-vmm project goals, which is to be able to share and re-use
|
||||
as many virtualization crates as possible. As such, the Cloud Hypervisor relationship with the rust-vmm
|
||||
project is twofold:
|
||||
Cloud Hypervisor embraces the rust-vmm project goals, which is to be able to
|
||||
share and re-use as many virtualization crates as possible. As such, the Cloud
|
||||
Hypervisor relationship with the rust-vmm project is twofold:
|
||||
|
||||
1. It will use as much of the rust-vmm code as possible. Any new rust-vmm crate that's relevant to the project
|
||||
goals will be integrated as soon as possible.
|
||||
2. As it is likely that the rust-vmm project will lack some of the features that Cloud Hypervisor needs (e.g. ACPI,
|
||||
VFIO, vhost-user, etc), we will be using the Cloud Hypervisor VMM to implement and test them, and contribute them
|
||||
back to the rust-vmm project.
|
||||
1. It will use as much of the rust-vmm code as possible. Any new rust-vmm crate
|
||||
that's relevant to the project goals will be integrated as soon as possible.
|
||||
2. As it is likely that the rust-vmm project will lack some of the features that
|
||||
Cloud Hypervisor needs (e.g. ACPI, VFIO, vhost-user, etc), we will be using
|
||||
the Cloud Hypervisor VMM to implement and test them, and contribute them back
|
||||
to the rust-vmm project.
|
||||
|
||||
## Firecracker and crosvm
|
||||
|
||||
A large part of the Cloud Hypervisor code is based on either the Firecracker or the crosvm projects implementations.
|
||||
Both of these are VMMs written in Rust with a focus on safety and security, like Cloud Hypervisor.
|
||||
A large part of the Cloud Hypervisor code is based on either the Firecracker or
|
||||
the crosvm projects implementations. Both of these are VMMs written in Rust with
|
||||
a focus on safety and security, like Cloud Hypervisor.
|
||||
|
||||
However we want to emphasize that the Cloud Hypervisor project is neither a fork nor a reimplementation of any of those
|
||||
projects. The goals and use cases we're trying to meet are different.
|
||||
We're aiming at supporting cloud workloads, i.e. those modern, full Linux distribution images currently being run by
|
||||
Cloud Service Provider (CSP) tenants.
|
||||
However we want to emphasize that the Cloud Hypervisor project is neither a fork
|
||||
nor a reimplementation of any of those projects. The goals and use cases we're
|
||||
trying to meet are different. We're aiming at supporting cloud workloads, i.e.
|
||||
those modern, full Linux distribution images currently being run by Cloud
|
||||
Service Provider (CSP) tenants.
|
||||
|
||||
Our primary target is not to support client or serverless use cases, and as such our code base already diverges
|
||||
from the crosvm and Firecracker ones. As we add more features to support our use cases, we believe that the divergence
|
||||
will increase while at the same time sharing as much of the fundamental virtualization code through the rust-vmm project
|
||||
crates as possible.
|
||||
Our primary target is not to support client or serverless use cases, and as such
|
||||
our code base already diverges from the crosvm and Firecracker ones. As we add
|
||||
more features to support our use cases, we believe that the divergence will
|
||||
increase while at the same time sharing as much of the fundamental
|
||||
virtualization code through the rust-vmm project crates as possible.
|
||||
|
||||
# 5. Community
|
||||
|
||||
The Cloud Hypervisor project follows the governance, and community guidelines described in
|
||||
the [Community](https://github.com/cloud-hypervisor/community) repository.
|
||||
The Cloud Hypervisor project follows the governance, and community guidelines
|
||||
described in the [Community](https://github.com/cloud-hypervisor/community)
|
||||
repository.
|
||||
|
||||
## Contribute
|
||||
|
||||
We are working on building a global, diverse and collaborative community around the Cloud Hypervisor project.
|
||||
Anyone who is interested in [contributing](CONTRIBUTING.md) to the project is welcome to participate.
|
||||
We are working on building a global, diverse and collaborative community around
|
||||
the Cloud Hypervisor project. Anyone who is interested in
|
||||
[contributing](CONTRIBUTING.md) to the project is welcome to participate.
|
||||
|
||||
We believe that contributing to a open source project like Cloud Hypervisor covers a lot more than just sending
|
||||
code. Testing, documentation, pull request reviews, bug reports, feature requests, project improvement suggestions,
|
||||
etc, are all equal and welcome means of contribution. See the [CONTRIBUTING](CONTRIBUTING.md) document for more details.
|
||||
We believe that contributing to a open source project like Cloud Hypervisor
|
||||
covers a lot more than just sending code. Testing, documentation, pull request
|
||||
reviews, bug reports, feature requests, project improvement suggestions, etc,
|
||||
are all equal and welcome means of contribution. See the
|
||||
[CONTRIBUTING](CONTRIBUTING.md) document for more details.
|
||||
|
||||
## Join us
|
||||
|
||||
@@ -342,4 +377,4 @@ and [join us on Slack](https://cloud-hypervisor.slack.com/).
|
||||
|
||||
## Security issues
|
||||
|
||||
Please use the GitHub security advisories feature for reporting issues: https://github.com/cloud-hypervisor/cloud-hypervisor/security/advisories/new
|
||||
Please contact the maintainers listed in the MAINTAINERS.md file with security issues.
|
||||
|
||||
@@ -11,14 +11,14 @@ tdx = []
|
||||
|
||||
[dependencies]
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
anyhow = "1.0.52"
|
||||
anyhow = "1.0.55"
|
||||
byteorder = "1.4.3"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.112"
|
||||
libc = "0.2.119"
|
||||
linux-loader = { version = "0.4.0", features = ["elf", "bzimage", "pe"] }
|
||||
log = "0.4.14"
|
||||
serde = { version = "1.0.133", features = ["rc"] }
|
||||
serde_derive = "1.0.133"
|
||||
serde = { version = "1.0.136", features = ["rc"] }
|
||||
serde_derive = "1.0.136"
|
||||
thiserror = "1.0.30"
|
||||
versionize = "0.1.6"
|
||||
versionize_derive = "0.1.4"
|
||||
|
||||
@@ -51,11 +51,16 @@ const SIZE_CELLS: u32 = 0x2;
|
||||
// Look for "The 1st cell..."
|
||||
const GIC_FDT_IRQ_TYPE_SPI: u32 = 0;
|
||||
const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
|
||||
const GIC_FDT_IRQ_PPI_CPU_SHIFT: u32 = 8;
|
||||
const GIC_FDT_IRQ_PPI_CPU_MASK: u32 = 0xff << GIC_FDT_IRQ_PPI_CPU_SHIFT;
|
||||
|
||||
// From https://elixir.bootlin.com/linux/v4.9.62/source/include/dt-bindings/interrupt-controller/irq.h#L17
|
||||
const IRQ_TYPE_EDGE_RISING: u32 = 1;
|
||||
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
||||
|
||||
// PMU PPI interrupt number
|
||||
pub const AARCH64_PMU_IRQ: u32 = 7;
|
||||
|
||||
// Keys and Buttons
|
||||
// System Power Down
|
||||
const KEY_POWER: u32 = 116;
|
||||
@@ -91,6 +96,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
||||
pci_space_info: &[PciSpaceInfo],
|
||||
numa_nodes: &NumaNodes,
|
||||
virtio_iommu_bdf: Option<u32>,
|
||||
pmu_supported: bool,
|
||||
) -> FdtWriterResult<Vec<u8>> {
|
||||
// Allocate stuff necessary for the holding the blob.
|
||||
let mut fdt = FdtWriter::new().unwrap();
|
||||
@@ -114,6 +120,9 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
||||
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
||||
create_gic_node(&mut fdt, gic_device)?;
|
||||
create_timer_node(&mut fdt)?;
|
||||
if pmu_supported {
|
||||
create_pmu_node(&mut fdt, vcpu_mpidr.len())?;
|
||||
}
|
||||
create_clock_node(&mut fdt)?;
|
||||
create_psci_node(&mut fdt)?;
|
||||
create_devices_node(&mut fdt, device_info)?;
|
||||
@@ -152,9 +161,6 @@ fn create_cpu_nodes(
|
||||
fdt.property_u32("#size-cells", 0x0)?;
|
||||
|
||||
let num_cpus = vcpu_mpidr.len();
|
||||
let threads_per_core = vcpu_topology.unwrap_or_default().0 as u8;
|
||||
let cores_per_package = vcpu_topology.unwrap_or_default().1 as u8;
|
||||
let packages = vcpu_topology.unwrap_or_default().2 as u8;
|
||||
|
||||
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
|
||||
let cpu_name = format!("cpu@{:x}", cpu_id);
|
||||
@@ -183,32 +189,9 @@ fn create_cpu_nodes(
|
||||
fdt.end_node(cpu_node)?;
|
||||
}
|
||||
|
||||
// If there is a valid cpu topology config, create the cpu-map node.
|
||||
if (threads_per_core > 0)
|
||||
&& (cores_per_package > 0)
|
||||
&& (packages > 0)
|
||||
&& (num_cpus as u8 == threads_per_core * cores_per_package * packages)
|
||||
{
|
||||
if let Some(topology) = vcpu_topology {
|
||||
let (threads_per_core, cores_per_package, packages) = topology;
|
||||
let cpu_map_node = fdt.begin_node("cpu-map")?;
|
||||
// Create mappings between CPU index and cluster,core, and thread.
|
||||
let mut cluster_core_thread_to_cpuidx = HashMap::new();
|
||||
for cpu_idx in 0..num_cpus as u8 {
|
||||
if threads_per_core > 1 {
|
||||
cluster_core_thread_to_cpuidx.insert(
|
||||
(
|
||||
cpu_idx / (cores_per_package * threads_per_core),
|
||||
(cpu_idx / threads_per_core) % cores_per_package,
|
||||
cpu_idx % threads_per_core,
|
||||
),
|
||||
cpu_idx,
|
||||
);
|
||||
} else {
|
||||
cluster_core_thread_to_cpuidx.insert(
|
||||
(cpu_idx / cores_per_package, cpu_idx % cores_per_package, 0),
|
||||
cpu_idx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Create device tree nodes with regard of above mapping.
|
||||
for cluster_idx in 0..packages {
|
||||
@@ -219,24 +202,16 @@ fn create_cpu_nodes(
|
||||
let core_name = format!("core{:x}", core_idx);
|
||||
let core_node = fdt.begin_node(&core_name)?;
|
||||
|
||||
if threads_per_core > 1 {
|
||||
for thread_idx in 0..threads_per_core {
|
||||
let thread_name = format!("thread{:x}", thread_idx);
|
||||
let thread_node = fdt.begin_node(&thread_name)?;
|
||||
let cpu_idx = cluster_core_thread_to_cpuidx
|
||||
.get(&(cluster_idx, core_idx, thread_idx))
|
||||
.unwrap();
|
||||
|
||||
fdt.property_u32("cpu", *cpu_idx as u32 + FIRST_VCPU_PHANDLE)?;
|
||||
fdt.end_node(thread_node)?;
|
||||
}
|
||||
} else {
|
||||
let cpu_idx = cluster_core_thread_to_cpuidx
|
||||
.get(&(cluster_idx, core_idx, 0))
|
||||
.unwrap();
|
||||
|
||||
fdt.property_u32("cpu", *cpu_idx as u32 + FIRST_VCPU_PHANDLE)?;
|
||||
for thread_idx in 0..threads_per_core {
|
||||
let thread_name = format!("thread{:x}", thread_idx);
|
||||
let thread_node = fdt.begin_node(&thread_name)?;
|
||||
let cpu_idx = threads_per_core * cores_per_package * cluster_idx
|
||||
+ threads_per_core * core_idx
|
||||
+ thread_idx;
|
||||
fdt.property_u32("cpu", cpu_idx as u32 + FIRST_VCPU_PHANDLE)?;
|
||||
fdt.end_node(thread_node)?;
|
||||
}
|
||||
|
||||
fdt.end_node(core_node)?;
|
||||
}
|
||||
fdt.end_node(cluster_node)?;
|
||||
@@ -540,6 +515,24 @@ fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Buil
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_pmu_node(fdt: &mut FdtWriter, cpu_nums: usize) -> FdtWriterResult<()> {
|
||||
let num_cpus = cpu_nums as u64 as u32;
|
||||
let compatible = "arm,armv8-pmuv3";
|
||||
let cpu_mask: u32 =
|
||||
(((1 << num_cpus) - 1) << GIC_FDT_IRQ_PPI_CPU_SHIFT) & GIC_FDT_IRQ_PPI_CPU_MASK;
|
||||
let irq = [
|
||||
GIC_FDT_IRQ_TYPE_PPI,
|
||||
AARCH64_PMU_IRQ,
|
||||
cpu_mask | IRQ_TYPE_LEVEL_HI,
|
||||
];
|
||||
|
||||
let pmu_node = fdt.begin_node("pmu")?;
|
||||
fdt.property_string("compatible", compatible)?;
|
||||
fdt.property_array_u32("interrupts", &irq)?;
|
||||
fdt.end_node(pmu_node)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_pci_nodes(
|
||||
fdt: &mut FdtWriter,
|
||||
pci_device_info: &[PciSpaceInfo],
|
||||
|
||||
@@ -43,6 +43,9 @@ pub enum Error {
|
||||
|
||||
/// Error configuring the MPIDR register
|
||||
VcpuRegMpidr(hypervisor::HypervisorCpuError),
|
||||
|
||||
/// Error initializing PMU for vcpu
|
||||
VcpuInitPmu,
|
||||
}
|
||||
|
||||
impl From<Error> for super::Error {
|
||||
@@ -136,6 +139,7 @@ pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Bui
|
||||
virtio_iommu_bdf: Option<u32>,
|
||||
gic_device: &dyn GicDevice,
|
||||
numa_nodes: &NumaNodes,
|
||||
pmu_supported: bool,
|
||||
) -> super::Result<()> {
|
||||
let fdt_final = fdt::create_fdt(
|
||||
guest_mem,
|
||||
@@ -148,6 +152,7 @@ pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Bui
|
||||
pci_space_info,
|
||||
numa_nodes,
|
||||
virtio_iommu_bdf,
|
||||
pmu_supported,
|
||||
)
|
||||
.map_err(|_| Error::SetupFdt)?;
|
||||
|
||||
|
||||
@@ -52,6 +52,9 @@ pub enum TdvfSectionType {
|
||||
Cfv,
|
||||
TdHob,
|
||||
TempMem,
|
||||
PermMem,
|
||||
Payload,
|
||||
PayloadParam,
|
||||
Reserved = 0xffffffff,
|
||||
}
|
||||
|
||||
@@ -61,57 +64,6 @@ impl Default for TdvfSectionType {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct TdVmmDataRegion {
|
||||
pub start_address: u64,
|
||||
pub length: u64,
|
||||
pub region_type: TdVmmDataRegionType,
|
||||
}
|
||||
|
||||
#[repr(u16)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum TdVmmDataRegionType {
|
||||
Signature = 0x0000,
|
||||
InterfaceVersion = 0x0001,
|
||||
SystemUuid = 0x0002,
|
||||
RamSize = 0x0003,
|
||||
GraphicsEnabled = 0x0004,
|
||||
SmpCpuCount = 0x0005,
|
||||
MachineId = 0x0006,
|
||||
KernelAddress = 0x0007,
|
||||
KernelSize = 0x0008,
|
||||
KernelCommandLine = 0x0009,
|
||||
InitrdAddress = 0x000a,
|
||||
InitrdSize = 0x000b,
|
||||
BootDevice = 0x000c,
|
||||
NumaData = 0x000d,
|
||||
BootMenu = 0x000e,
|
||||
MaximumCpuCount = 0x000f,
|
||||
KernelEntry = 0x0010,
|
||||
KernelData = 0x0011,
|
||||
InitrdData = 0x0012,
|
||||
CommandLineAddress = 0x0013,
|
||||
CommandLineSize = 0x0014,
|
||||
CommandLineData = 0x0015,
|
||||
KernelSetupAddress = 0x0016,
|
||||
KernelSetupSize = 0x0017,
|
||||
KernelSetupData = 0x0018,
|
||||
FileDir = 0x0019,
|
||||
AcpiTables = 0x8000,
|
||||
SmbiosTables = 0x8001,
|
||||
Irq0Override = 0x8002,
|
||||
E820Table = 0x8003,
|
||||
HpetData = 0x8004,
|
||||
Reserved = 0xffff,
|
||||
}
|
||||
|
||||
impl Default for TdVmmDataRegionType {
|
||||
fn default() -> Self {
|
||||
TdVmmDataRegionType::Reserved
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_tdvf_sections(file: &mut File) -> Result<Vec<TdvfSection>, TdvfError> {
|
||||
// The 32-bit offset to the TDVF metadata is located 32 bytes from
|
||||
// the end of the file.
|
||||
@@ -231,20 +183,41 @@ struct HobGuidType {
|
||||
name: EfiGuid,
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum PayloadImageType {
|
||||
ExecutablePayload,
|
||||
BzImage,
|
||||
RawVmLinux,
|
||||
}
|
||||
|
||||
impl Default for PayloadImageType {
|
||||
fn default() -> Self {
|
||||
PayloadImageType::ExecutablePayload
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct TdVmmData {
|
||||
pub struct PayloadInfo {
|
||||
pub image_type: PayloadImageType,
|
||||
pub entry_point: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct TdPayload {
|
||||
guid_type: HobGuidType,
|
||||
region: TdVmmDataRegion,
|
||||
payload_info: PayloadInfo,
|
||||
}
|
||||
|
||||
// SAFETY: These data structures only contain a series of integers
|
||||
unsafe impl ByteValued for TdVmmDataRegion {}
|
||||
unsafe impl ByteValued for HobHeader {}
|
||||
unsafe impl ByteValued for HobHandoffInfoTable {}
|
||||
unsafe impl ByteValued for HobResourceDescriptor {}
|
||||
unsafe impl ByteValued for HobGuidType {}
|
||||
unsafe impl ByteValued for TdVmmData {}
|
||||
unsafe impl ByteValued for PayloadInfo {}
|
||||
unsafe impl ByteValued for TdPayload {}
|
||||
|
||||
pub struct TdHob {
|
||||
start_offset: u64,
|
||||
@@ -376,35 +349,90 @@ impl TdHob {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn add_td_vmm_data(
|
||||
pub fn add_acpi_table(
|
||||
&mut self,
|
||||
mem: &GuestMemoryMmap,
|
||||
region: TdVmmDataRegion,
|
||||
table_content: &[u8],
|
||||
) -> Result<(), TdvfError> {
|
||||
let td_vmm_data = TdVmmData {
|
||||
// We already know the HobGuidType size is 8 bytes multiple, but we
|
||||
// need the total size to be 8 bytes multiple. That is why the ACPI
|
||||
// table size must be 8 bytes multiple as well.
|
||||
let length = std::mem::size_of::<HobGuidType>() as u16
|
||||
+ align_hob(table_content.len() as u64) as u16;
|
||||
let hob_guid_type = HobGuidType {
|
||||
header: HobHeader {
|
||||
r#type: HobType::GuidExtension,
|
||||
length,
|
||||
reserved: 0,
|
||||
},
|
||||
// ACPI_TABLE_HOB_GUID
|
||||
// 0x6a0c5870, 0xd4ed, 0x44f4, {0xa1, 0x35, 0xdd, 0x23, 0x8b, 0x6f, 0xc, 0x8d }
|
||||
name: EfiGuid {
|
||||
data1: 0x6a0c_5870,
|
||||
data2: 0xd4ed,
|
||||
data3: 0x44f4,
|
||||
data4: [0xa1, 0x35, 0xdd, 0x23, 0x8b, 0x6f, 0xc, 0x8d],
|
||||
},
|
||||
};
|
||||
info!(
|
||||
"Writing HOB ACPI table {:x} {:x?} {:x?}",
|
||||
self.current_offset, hob_guid_type, table_content
|
||||
);
|
||||
mem.write_obj(hob_guid_type, GuestAddress(self.current_offset))
|
||||
.map_err(TdvfError::GuestMemoryWriteHob)?;
|
||||
let current_offset = self.current_offset + std::mem::size_of::<HobGuidType>() as u64;
|
||||
|
||||
// In case the table is quite large, let's make sure we can handle
|
||||
// retrying until everything has been correctly copied.
|
||||
let mut offset: usize = 0;
|
||||
loop {
|
||||
let bytes_written = mem
|
||||
.write(
|
||||
&table_content[offset..],
|
||||
GuestAddress(current_offset + offset as u64),
|
||||
)
|
||||
.map_err(TdvfError::GuestMemoryWriteHob)?;
|
||||
offset += bytes_written;
|
||||
if offset >= table_content.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.current_offset += length as u64;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_payload(
|
||||
&mut self,
|
||||
mem: &GuestMemoryMmap,
|
||||
payload_info: PayloadInfo,
|
||||
) -> Result<(), TdvfError> {
|
||||
let payload = TdPayload {
|
||||
guid_type: HobGuidType {
|
||||
header: HobHeader {
|
||||
r#type: HobType::GuidExtension,
|
||||
length: std::mem::size_of::<TdVmmData>() as u16,
|
||||
length: std::mem::size_of::<TdPayload>() as u16,
|
||||
reserved: 0,
|
||||
},
|
||||
// TD_VMM_DATA_GUID CF2643E4-C0D3-46FF-0000-72EE623DDE38
|
||||
// HOB_PAYLOAD_INFO_GUID
|
||||
// 0xb96fa412, 0x461f, 0x4be3, {0x8c, 0xd, 0xad, 0x80, 0x5a, 0x49, 0x7a, 0xc0
|
||||
name: EfiGuid {
|
||||
data1: 0xcf26_43e4,
|
||||
data2: 0xc0d3,
|
||||
data3: 0x46ff,
|
||||
data4: [0x00, 0x00, 0x72, 0xee, 0x62, 0x3d, 0xde, 0x38],
|
||||
data1: 0xb96f_a412,
|
||||
data2: 0x461f,
|
||||
data3: 0x4be3,
|
||||
data4: [0x8c, 0xd, 0xad, 0x80, 0x5a, 0x49, 0x7a, 0xc0],
|
||||
},
|
||||
},
|
||||
region,
|
||||
payload_info,
|
||||
};
|
||||
info!(
|
||||
"Writing HOB TD_VMM_DATA {:x} {:x?}",
|
||||
self.current_offset, td_vmm_data
|
||||
"Writing HOB TD_PAYLOAD {:x} {:x?}",
|
||||
self.current_offset, payload
|
||||
);
|
||||
mem.write_obj(td_vmm_data, GuestAddress(self.current_offset))
|
||||
mem.write_obj(payload, GuestAddress(self.current_offset))
|
||||
.map_err(TdvfError::GuestMemoryWriteHob)?;
|
||||
self.update_offset::<TdVmmData>();
|
||||
self.update_offset::<TdPayload>();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ default = []
|
||||
|
||||
[dependencies]
|
||||
io-uring = "0.5.2"
|
||||
libc = "0.2.112"
|
||||
libc = "0.2.119"
|
||||
log = "0.4.14"
|
||||
qcow = { path = "../qcow" }
|
||||
thiserror = "1.0.30"
|
||||
@@ -17,7 +17,7 @@ versionize = "0.1.6"
|
||||
versionize_derive = "0.1.4"
|
||||
vhdx = { path = "../vhdx" }
|
||||
virtio-bindings = { version = "0.1.0", features = ["virtio-v5_0_0"] }
|
||||
virtio-queue = { path = "../virtio-queue" }
|
||||
virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", branch = "main" }
|
||||
vm-memory = { version = "0.7.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = "0.9.0"
|
||||
|
||||
@@ -30,6 +30,7 @@ use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
||||
use std::os::linux::fs::MetadataExt;
|
||||
use std::path::Path;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use std::sync::MutexGuard;
|
||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
@@ -39,6 +40,7 @@ use vm_memory::{
|
||||
bitmap::AtomicBitmap, bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory,
|
||||
GuestMemoryError, GuestMemoryLoadGuard,
|
||||
};
|
||||
use vm_virtio::{AccessPlatform, Translatable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
||||
@@ -190,6 +192,7 @@ pub struct Request {
|
||||
impl Request {
|
||||
pub fn parse(
|
||||
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap>>,
|
||||
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
||||
) -> result::Result<Request, Error> {
|
||||
let hdr_desc = desc_chain
|
||||
.next()
|
||||
@@ -204,9 +207,13 @@ impl Request {
|
||||
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
||||
}
|
||||
|
||||
let hdr_desc_addr = hdr_desc
|
||||
.addr()
|
||||
.translate(access_platform, hdr_desc.len() as usize);
|
||||
|
||||
let mut req = Request {
|
||||
request_type: request_type(desc_chain.memory(), hdr_desc.addr())?,
|
||||
sector: sector(desc_chain.memory(), hdr_desc.addr())?,
|
||||
request_type: request_type(desc_chain.memory(), hdr_desc_addr)?,
|
||||
sector: sector(desc_chain.memory(), hdr_desc_addr)?,
|
||||
data_descriptors: Vec::new(),
|
||||
status_addr: GuestAddress(0),
|
||||
writeback: true,
|
||||
@@ -240,7 +247,11 @@ impl Request {
|
||||
if !desc.is_write_only() && req.request_type == RequestType::GetDeviceId {
|
||||
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||
}
|
||||
req.data_descriptors.push((desc.addr(), desc.len()));
|
||||
|
||||
req.data_descriptors.push((
|
||||
desc.addr().translate(access_platform, desc.len() as usize),
|
||||
desc.len(),
|
||||
));
|
||||
desc = desc_chain
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
@@ -261,7 +272,9 @@ impl Request {
|
||||
return Err(Error::DescriptorLengthTooSmall);
|
||||
}
|
||||
|
||||
req.status_addr = status_desc.addr();
|
||||
req.status_addr = status_desc
|
||||
.addr()
|
||||
.translate(access_platform, status_desc.len() as usize);
|
||||
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
anyhow = "1.0.52"
|
||||
anyhow = "1.0.55"
|
||||
arch = { path = "../arch" }
|
||||
bitflags = "1.3.2"
|
||||
byteorder = "1.4.3"
|
||||
epoll = "4.3.1"
|
||||
libc = "0.2.112"
|
||||
libc = "0.2.119"
|
||||
log = "0.4.14"
|
||||
versionize = "0.1.6"
|
||||
versionize_derive = "0.1.4"
|
||||
|
||||
399
docs/COPYING.docs
Normal file
399
docs/COPYING.docs
Normal file
@@ -0,0 +1,399 @@
|
||||
The documentation in this directory is covered by the following license:
|
||||
|
||||
Attribution 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution 4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution 4.0 International Public License ("Public License"). To the
|
||||
extent this Public License may be interpreted as a contract, You are
|
||||
granted the Licensed Rights in consideration of Your acceptance of
|
||||
these terms and conditions, and the Licensor grants You such rights in
|
||||
consideration of benefits the Licensor receives from making the
|
||||
Licensed Material available under these terms and conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
d. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
f. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
g. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
h. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
i. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
j. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
k. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
4. If You Share Adapted Material You produce, the Adapter's
|
||||
License You apply must not prevent recipients of the Adapted
|
||||
Material from complying with this Public License.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public
|
||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||
its public licenses to material it publishes and in those instances
|
||||
will be considered the “Licensor.” The text of the Creative Commons
|
||||
public licenses is dedicated to the public domain under the CC0 Public
|
||||
Domain Dedication. Except for the limited purpose of indicating that
|
||||
material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the
|
||||
public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
|
||||
|
||||
16
docs/fs.md
16
docs/fs.md
@@ -12,12 +12,12 @@ This virtual device relies on the _vhost-user_ protocol, which assumes the backe
|
||||
|
||||
_Build virtiofsd_
|
||||
```bash
|
||||
git clone --depth 1 "https://gitlab.com/virtio-fs/qemu.git" -b "qemu5.0-virtiofs-dax" $VIRTIOFSD_DIR
|
||||
cd $VIRTIOFSD_DIR
|
||||
./configure --prefix=$PWD --target-list=x86_64-softmmu
|
||||
make virtiofsd -j `nproc`
|
||||
sudo setcap cap_sys_admin+epi "virtiofsd"
|
||||
git clone https://gitlab.com/virtio-fs/virtiofsd
|
||||
pushd virtiofsd
|
||||
cargo build --release
|
||||
sudo setcap cap_sys_admin+epi target/release/virtiofsd
|
||||
```
|
||||
|
||||
_Create shared directory_
|
||||
```bash
|
||||
mkdir /tmp/shared_dir
|
||||
@@ -27,11 +27,11 @@ _Run virtiofsd_
|
||||
./virtiofsd \
|
||||
-d \
|
||||
--socket-path=/tmp/virtiofs \
|
||||
-o source=/tmp/shared_dir \
|
||||
-o cache=none
|
||||
--shared-dir=/tmp/shared_dir \
|
||||
--cache=never
|
||||
```
|
||||
|
||||
The `cache=none` option should be the default when using `virtiofsd` with the __cloud-hypervisor__ VMM. This prevents from using the guest page cache, which reduces the memory footprint of the guest. When running multiple virtual machines on the same host, this will let the host deal with page cache, which will increase the density of virtual machines which can be launched.
|
||||
The `cache=never` option should be the default when using `virtiofsd` with the __cloud-hypervisor__ VMM. This prevents from using the guest page cache, which reduces the memory footprint of the guest. When running multiple virtual machines on the same host, this will let the host deal with page cache, which will increase the density of virtual machines which can be launched.
|
||||
|
||||
The `cache=always` option will allow for the guest page cache to be used, which will increase the memory footprint of the guest. This option should be used only for specific use cases where a single VM is going to be running on a host.
|
||||
|
||||
|
||||
47
docs/gdb.md
Normal file
47
docs/gdb.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# GDB Support
|
||||
|
||||
This feature allows remote guest debugging using GDB. Note that this feature is only supported on x86_64/KVM.
|
||||
|
||||
To enable debugging with GDB, build with the `gdb` feature enabled:
|
||||
|
||||
```bash
|
||||
cargo build --features gdb
|
||||
```
|
||||
|
||||
To use the `--gdb` option, specify the Unix Domain Socket with `--path` that Cloud Hypervisor will use to communicate with the host's GDB:
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--kernel hypervisor-fw \
|
||||
--disk path=bionic-server-cloudimg-amd64.raw \
|
||||
--cpus boot=1 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
--console off \
|
||||
--serial tty \
|
||||
--gdb path=/tmp/ch-gdb-sock
|
||||
```
|
||||
|
||||
Cloud Hypervisor will listen for GDB on the host side before starting the guest.
|
||||
On the host side, connect to the GDB remote server as follows:
|
||||
|
||||
```bash
|
||||
gdb -q
|
||||
(gdb) target remote /tmp/ch-gdb-sock
|
||||
Remote debugging using /tmp/ch-gdb-sock
|
||||
warning: No executable has been specified, and target does not support
|
||||
determining executable automatically. Try using the "file" command.
|
||||
0x000000000011217e in ?? ()
|
||||
```
|
||||
|
||||
You can set up to four hardware breakpoints using the x86 debug register:
|
||||
|
||||
```bash
|
||||
(gdb) hb *0x1121b7
|
||||
Hardware assisted breakpoint 1 at 0x1121b7
|
||||
(gdb) c
|
||||
Continuing.
|
||||
|
||||
Breakpoint 1, 0x00000000001121b7 in ?? ()
|
||||
(gdb)
|
||||
```
|
||||
@@ -15,14 +15,20 @@ the guest side can be found in the [Guest TDX tree](https://github.com/intel/tdx
|
||||
The TDVF firmware can be found in the
|
||||
[EDK2 staging project](https://github.com/tianocore/edk2-staging/tree/TDVF).
|
||||
|
||||
The TDShim firmware can be found in the
|
||||
[Confidential Containers project](https://github.com/confidential-containers/td-shim).
|
||||
|
||||
## Cloud Hypervisor support
|
||||
|
||||
First, you must be running on a machine with TDX enabled in hardware, and
|
||||
with the host OS compiled from the [KVM TDX tree](https://github.com/intel/tdx/tree/kvm).
|
||||
|
||||
Cloud Hypervisor can run TDX VM (Trust Domain) by loading the TDVF firmware,
|
||||
Cloud Hypervisor can run TDX VM (Trust Domain) by loading a TD firmware,
|
||||
which will then load the guest kernel from the image. The image must be custom
|
||||
as it must include a kernel built from the [Guest TDX tree](https://github.com/intel/tdx/tree/guest).
|
||||
|
||||
### TDVF
|
||||
|
||||
The firmware can be built as follows:
|
||||
|
||||
```bash
|
||||
@@ -73,3 +79,26 @@ guest kernel command line contains `console=ttyS0`):
|
||||
--serial tty \
|
||||
--console off
|
||||
```
|
||||
|
||||
### TDShim
|
||||
|
||||
This is a lightweight version of the TDVF, written in Rust and designed for
|
||||
direct kernel boot, which is useful for containers use cases.
|
||||
|
||||
You can find the instructions for building the firmware directly from the
|
||||
project [documentation](https://github.com/confidential-containers/td-shim/tree/staging#how-to-build).
|
||||
|
||||
And run a TDX VM by providing the firmware previously built, along with a guest
|
||||
kernel built from the [Guest TDX tree](https://github.com/intel/tdx/tree/guest).
|
||||
The appropriate kernel boot options must be provided through the `--cmdline`
|
||||
option as well.
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--tdx firmware=tdshim \
|
||||
--kernel bzImage \
|
||||
--cmdline "root=/dev/vda1 console=hvc0 rw tdx_allow_acpi=MCFG"
|
||||
--cpus boot=1 \
|
||||
--memory size=1G \
|
||||
--disk path=tdx_guest_img
|
||||
```
|
||||
@@ -1,209 +0,0 @@
|
||||
# How to use networking
|
||||
|
||||
cloud-hypervisor can emulate one or more virtual network interfaces, represented at the hypervisor host by [tap devices](https://www.kernel.org/doc/Documentation/networking/tuntap.txt). This guide briefly describes, in a manual and distribution neutral way, how to setup and use networking with cloud-hypervisor.
|
||||
|
||||
## Multiple queue support for net devices
|
||||
|
||||
While multiple vcpus defined for guest, to gain the benefit of vcpu scalable to improve performance, it suggests to define multiple queue pairs for net devices, one Tx/Rx queue pair per one vcpu, that means the number of queue pairs at least is equal to the vcpu count. In that case, after virtnet driver set cpu affinity for virtqueues in guest kernel, vcpus could handle interrupt from different virtqueue pairs in parallel.
|
||||
|
||||
It will gain better performance for guest that has multiple queues defined for net devices while it has multiple net sessions running in userspace.
|
||||
|
||||
To enable multiple queue support in cloud-hypervisor, multiple queue pairs will be defined, while multiple tap fds will be opened for the same tap device, it will also have multiple threads started, each thread will monitor and handle the events from each virtqueue pairs and the associated tap fd.
|
||||
|
||||
Note:
|
||||
|
||||
- Currently, it does not support to use ethtool to change the combined queue numbers in guest.
|
||||
- Multiple queue is enabled for vhost-user-net backend in cloud-hypervisor, however, multiple thread is not added to handle mq, thus, the performance for vhost-user-net backend is not supposed to be improved. The multiple thread will be added for backend later.
|
||||
- Performance test for vhost-user-net will be covered once vhost-user-net backend has multiple thread supported.
|
||||
- Performance test for virtio-net is done by comparing 2 queue pairs with 1 queue pairs, that to run 2 iperf3 sessions in the same test environments, throughput is improved about 37%.
|
||||
|
||||
## Start cloud-hypervisor with net devices
|
||||
|
||||
Use one `--net` command-line argument from cloud-hypervisor to specify the emulation of one or more virtual NIC's. The example below instructs cloud-hypervisor to emulate for instance 2 virtual NIC's:
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=4 \
|
||||
--memory "size=512M" \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--kernel my-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--net tap=ich0,mac=a4:a1:c2:00:00:01,ip=192.168.4.2,mask=255.255.255.0,num_queues=2,queue_size=256 \
|
||||
tap=ich1,mac=a4:a1:c2:00:00:02,ip=10.0.1.2,mask=255.255.255.0,num_queues=2,queue_size=256
|
||||
```
|
||||
|
||||
The `--net` argument takes 1 or more space-separated strings of key value pairs containing the following 4 keys or fields:
|
||||
|
||||
| Name | Purpose | Optional |
|
||||
| ---------- | ---------------------- | -------- |
|
||||
| tap | tap device name | Yes |
|
||||
| mac | vNIC mac address | Yes |
|
||||
| ip | tap IP IP address | yes |
|
||||
| mask | tap IP netmask | Yes |
|
||||
| num_queues | the number of queues | yes |
|
||||
| queue_size | the size of each queue | Yes |
|
||||
|
||||
num_queues is the total number of tx and rx queues, the default value is 2, and it could be increased by multiples of 2. Additionally, num_queues is suggested to be as 2 times of vcpu count. The default value for queue_size is 256.
|
||||
|
||||
If the tap device is pre-created on host before guest boot up. To use multiple queue support for net device in guest, the tap device should be opened like this from host.
|
||||
|
||||
```bash
|
||||
[root@localhost ~]# ip tuntap add name ich0 mode tap multi_queue
|
||||
```
|
||||
|
||||
And the `--net` device should specify support for multiple queues. `num_queues` must be a multiple of 2 starting at least from 4 since multiple queues really means multiple queue pairs. We need at least 2 pairs for this configuration to be correct:
|
||||
|
||||
```bash
|
||||
--net tap=ich0,mac=a4:a1:c2:00:00:01,ip=192.168.4.2,mask=255.255.255.0,num_queues=4,queue_size=256
|
||||
```
|
||||
|
||||
## Configure the tap devices
|
||||
|
||||
After starting cloud-hypervisor as shown above, 2 tap devices with state down will become available at the host:
|
||||
|
||||
```bash
|
||||
root@host:~# ip link show ich0
|
||||
78: ich0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 72:54:12:ff:ce:6f brd ff:ff:ff:ff:ff:ff
|
||||
root@host:~# ip link show ich1
|
||||
79: ich1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 06:7a:fc:1b:9a:67 brd ff:ff:ff:ff:ff:ff
|
||||
```
|
||||
|
||||
Set the tap devices to up state:
|
||||
|
||||
```bash
|
||||
root@host:~# ip link set up ich0
|
||||
root@host:~# ip link set up ich1
|
||||
|
||||
root@host:~# ip link show ich0
|
||||
78: ich0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 72:54:12:ff:ce:6f brd ff:ff:ff:ff:ff:ff
|
||||
root@host:~# ip link show ich1
|
||||
79: ich1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 06:7a:fc:1b:9a:67 brd ff:ff:ff:ff:ff:ff
|
||||
```
|
||||
|
||||
## Connect tap devices
|
||||
|
||||
Different networking models can be used to provide external connectivity. In this example we will
|
||||
use 2 linux bridges emulating 2 different networks. The integration bridge (ich-int) in this example will also be used
|
||||
for external connectivity.
|
||||
|
||||
Create the bridges and connect the cloud-hypervisor tap devices to the bridges:
|
||||
|
||||
```bash
|
||||
root@host:~# brctl addbr ich-int
|
||||
root@host:~# brctl addbr ich-dpl
|
||||
root@host:~# ip link set up ich-int
|
||||
root@host:~# ip link set up ich-dpl
|
||||
root@host:~# brctl addif ich-int ich0
|
||||
root@host:~# brctl addif ich-dpl ich1
|
||||
root@host:~# brctl show
|
||||
bridge name bridge id STP enabled interfaces
|
||||
ich-dpl 8000.067afc1b9a67 no ich1
|
||||
ich-int 8000.725412ffce6f no ich0
|
||||
```
|
||||
|
||||
This completes the layer 2 wiring: The cloud-hypervisor is now connected to the hypervisor host via the 2 linux bridges.
|
||||
|
||||
## IP (Layer 3) provisioning
|
||||
|
||||
### Hypervisor host
|
||||
|
||||
On the hypervisor host add the network gateway IP address of each network to the 2 linux bridges:
|
||||
|
||||
```bash
|
||||
root@host:~# ip addr add 192.168.4.1/24 dev ich-int
|
||||
root@host:~# ip addr add 10.0.1.1/24 dev ich-dpl
|
||||
```
|
||||
|
||||
The routing table of the hypervisor host should now also have corresponding routing entries:
|
||||
|
||||
```bash
|
||||
root@host:~# route -n
|
||||
Kernel IP routing table
|
||||
Destination Gateway Genmask Flags Metric Ref Use Iface
|
||||
0.0.0.0 192.168.178.1 0.0.0.0 UG 600 0 0 wlan1
|
||||
10.0.1.0 0.0.0.0 255.255.255.0 U 0 0 0 ich-dpl
|
||||
192.168.4.0 0.0.0.0 255.255.255.0 U 0 0 0 ich-int
|
||||
192.168.178.0 0.0.0.0 255.255.255.0 U 600 0 0 wlan1
|
||||
```
|
||||
|
||||
### Virtual Machine
|
||||
|
||||
Within the virtual machine set the vNIC's to up state and provision the corresponding IP addresses on the 2 vNIC's. The steps outlined below use the ip command as an example. Alternative distribution specific procedures can also apply.
|
||||
|
||||
```bash
|
||||
root@guest:~# ip link set up enp0s2
|
||||
root@guest:~# ip link set up enp0s3
|
||||
root@guest:~# ip addr add 192.168.4.2/24 dev enp0s2
|
||||
root@guest:~# ip addr add 10.0.1.2/24 dev enp0s3
|
||||
```
|
||||
|
||||
IP connectivity between the virtual machine and the hypervisor-host can be verified by sending
|
||||
ICMP requests to the hypervisor-host for the gateway IP address from within the virtual machine:
|
||||
|
||||
```bash
|
||||
root@guest:~# ping 192.168.4.1
|
||||
PING 192.168.4.1 (192.168.4.1) 56(84) bytes of data.
|
||||
64 bytes from 192.168.4.1: icmp_seq=1 ttl=64 time=0.456 ms
|
||||
64 bytes from 192.168.4.1: icmp_seq=2 ttl=64 time=0.226 ms
|
||||
root@guest:~# ping 10.0.1.1
|
||||
PING 10.0.1.1 (10.0.1.1) 56(84) bytes of data.
|
||||
64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.449 ms
|
||||
64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=0.393 ms
|
||||
```
|
||||
|
||||
The connection can now be used for instance to log into the virtual machine with
|
||||
ssh under the precondition that the machine has an ssh daemon provisioned:
|
||||
|
||||
```bash
|
||||
root@host:~# ssh root@192.168.4.2
|
||||
The authenticity of host '192.168.4.2 (192.168.4.2)' can't be established.
|
||||
ECDSA key fingerprint is SHA256:qNAUmTtDMW9pNuZARkpLQhfw+Yc1tqUDBrQp7aZGSjw.
|
||||
Are you sure you want to continue connecting (yes/no)? yes
|
||||
Warning: Permanently added '192.168.4.2' (ECDSA) to the list of known hosts.
|
||||
root@192.168.4.2's password:
|
||||
Linux cloud-hypervisor 5.2.0 #2 SMP Thu Jul 11 08:08:16 CEST 2019 x86_64
|
||||
|
||||
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
|
||||
permitted by applicable law.
|
||||
|
||||
|
||||
Last login: Fri Jul 12 13:27:56 2019 from 192.168.4.1
|
||||
root@guest:~#
|
||||
```
|
||||
|
||||
## Internet connectivity
|
||||
|
||||
To enable internet connectivity a default gw and a nameserver has to be set within
|
||||
the virtual machine:
|
||||
|
||||
```bash
|
||||
root@guest:~# ip route add default via 192.168.4.1
|
||||
root@guest:~# cat /etc/resolv.conf
|
||||
options timeout:2
|
||||
domain vallis.nl
|
||||
search vallis.nl
|
||||
nameserver 192.168.178.1
|
||||
```
|
||||
|
||||
make sure that the default gateway of the hypervisor host (in this example host 192.168.178.1 which is an adsl router) has an entry in the routing table for the 192.168.4.0/24 network otherwise IP connectivity will not work.
|
||||
|
||||
```bash
|
||||
root@guest:~# nslookup ftp.nl.debian.org
|
||||
Server: 192.168.178.1
|
||||
Address: 192.168.178.1#53
|
||||
|
||||
Non-authoritative answer:
|
||||
cdn-fastly.deb.debian.org canonical name = prod.debian.map.fastly.net.
|
||||
Name: prod.debian.map.fastly.net
|
||||
Address: 151.101.36.204
|
||||
|
||||
root@guest:~# apt-get update
|
||||
Ign:1 http://cdn-fastly.deb.debian.org/debian stretch InRelease
|
||||
Get:2 http://cdn-fastly.deb.debian.org/debian stretch Release [118 kB]
|
||||
Get:3 http://cdn-fastly.deb.debian.org/debian stretch Release.gpg [2434 B]
|
||||
Fetched 120 kB in 1s (110 kB/s)
|
||||
```
|
||||
@@ -8,9 +8,6 @@ snapshot and creates the exact same virtual machine, restoring the previously
|
||||
saved states. The new virtual machine is restored in a paused state, as it was
|
||||
before the snapshot was performed.
|
||||
|
||||
This feature is important for the project as it establishes the first step
|
||||
towards the support for live migration.
|
||||
|
||||
## Snapshot a Cloud Hypervisor VM
|
||||
|
||||
First thing, we must run a Cloud Hypervisor VM:
|
||||
@@ -46,24 +43,22 @@ ll /home/foo/snapshot/
|
||||
total 4194536
|
||||
drwxrwxr-x 2 foo bar 4096 Jul 22 11:50 ./
|
||||
drwxr-xr-x 47 foo bar 4096 Jul 22 11:47 ../
|
||||
-rw------- 1 foo bar 3221225472 Jul 22 11:19 memory-region-0
|
||||
-rw------- 1 foo bar 1073741824 Jul 22 11:19 memory-region-1
|
||||
-rw------- 1 foo bar 217853 Jul 22 11:19 vm.json
|
||||
-rw------- 1 foo bar 1084 Jul 22 11:19 config.json
|
||||
-rw------- 1 foo bar 4294967296 Jul 22 11:19 memory-ranges
|
||||
-rw------- 1 foo bar 217853 Jul 22 11:19 state.json
|
||||
```
|
||||
|
||||
In this particular example, we can observe that 2 memory region files were
|
||||
created. That is explained by the size of the guest RAM, which is 4GiB in this
|
||||
case. Because it exceeds 3GiB (which is where we can find a ~1GiB memory hole),
|
||||
Cloud Hypervisor needs 2 distinct memory regions to be created. Each memory
|
||||
region's content is stored through a dedicated file, which explains why we end
|
||||
up with 2 different files, the first one containing the guest RAM range 0-3GiB
|
||||
and the second one containing the guest RAM range 3-4GiB.
|
||||
`config.json` contains the virtual machine configuration. It is used to create
|
||||
a similar virtual machine with the correct amount of CPUs, RAM, and other
|
||||
expected devices. It is stored in a human readable format so that it could be
|
||||
modified between the snapshot and restore phases to achieve some very special
|
||||
use cases. But for most cases, manually modifying the configuration should not
|
||||
be needed.
|
||||
|
||||
`vm.json` gathers all information related to the virtual machine configuration
|
||||
and state. The configuration bits are used to create a similar virtual machine
|
||||
with the correct amount of CPUs, RAM, and other expected devices. The state
|
||||
bits are used to restore each component in the state it was left before the
|
||||
snapshot occurred.
|
||||
`memory-ranges` stores the content of the guest RAM.
|
||||
|
||||
`state.json` contains the virtual machine state. It is used to restore each
|
||||
component in the state it was left before the snapshot occurred.
|
||||
|
||||
## Restore a Cloud Hypervisor VM
|
||||
|
||||
@@ -100,13 +95,4 @@ snapshot earlier.
|
||||
|
||||
## Limitations
|
||||
|
||||
The support of snapshot/restore feature is still experimental, meaning one
|
||||
might still find some bugs associated with it.
|
||||
|
||||
Additionally, some devices and features don't support to be snapshot and
|
||||
restored yet:
|
||||
- `vhost-user` devices
|
||||
- `virtio-mem`
|
||||
- Intel SGX
|
||||
|
||||
VFIO devices are out of scope.
|
||||
VFIO devices and Intel SGX are out of scope.
|
||||
|
||||
@@ -33,11 +33,7 @@ Any UEFI capable image can be booted using the Cloud Hypervisor specific firmwar
|
||||
|
||||
To make Cloud Hypervisor use UEFI boot, pass the `CLOUDHV.fd` file path as an argument to the `--kernel` option. The firmware file will be opened in read only mode.
|
||||
|
||||
The same firmware can be used with Cloud Hypervisor or with QEMU. This is particularly useful if using QEMU for the preparation phase.
|
||||
|
||||
# Links
|
||||
|
||||
- [OVMF wiki](https://github.com/tianocore/tianocore.github.io/wiki/OVMF)
|
||||
- [Cloud Hypervisor specific tree](https://github.com/cloud-hypervisor/edk2/tree/ch)
|
||||
- [Redhat OVMF Status Report](https://access.redhat.com/sites/default/files/attachments/ovmf-whtepaper-031815.pdf)
|
||||
- [SeaBIOS Build Overview](https://www.seabios.org/Build_overview#Build_as_a_UEFI_Compatibility_Support_Module_.28CSM.29)
|
||||
|
||||
@@ -65,7 +65,7 @@ iface eth0 inet dhcp
|
||||
|
||||
```bash
|
||||
# starting in the directory above rootfs
|
||||
sudo virtiofsd --socket-path=$PWD/virtiofs-rootfs.sock -o source=$PWD/rootfs -o cache=none &
|
||||
sudo virtiofsd --socket-path=$PWD/virtiofs-rootfs.sock --shared-dir=$PWD/rootfs --cache=never &
|
||||
sudo cloud-hypervisor \
|
||||
--cpus boot=1,max=1 \
|
||||
--kernel vmlinux \
|
||||
|
||||
@@ -22,7 +22,7 @@ __Prerequisites__
|
||||
- QEMU, version >=5.0.0 is recommended.
|
||||
- Windows installation ISO. Obtained through MSDN, Visual Studio subscription, evaluation center, etc.
|
||||
- [VirtIO driver ISO](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/)
|
||||
- Suitable [OVMF](uefi.md) firmware
|
||||
- Suitable firmware for Cloud Hypervisor (`CLOUDHV.fd`) and for QEMU (`OVMF.fd`)
|
||||
- With the suggested image size of 30G, there should be enough free disk space to hold the installation ISO and any other necessary files
|
||||
|
||||
This step currently requires QEMU to install Windows onto the guest. QEMU is only used at the preparation stage, the resulting image is then fully functional with Cloud Hypervisor.
|
||||
|
||||
@@ -5,7 +5,7 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.112"
|
||||
serde = { version = "1.0.133", features = ["rc"] }
|
||||
serde_derive = "1.0.133"
|
||||
serde_json = "1.0.75"
|
||||
libc = "0.2.119"
|
||||
serde = { version = "1.0.136", features = ["rc"] }
|
||||
serde_derive = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
|
||||
126
fuzz/Cargo.lock
generated
126
fuzz/Cargo.lock
generated
@@ -11,9 +11,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.52"
|
||||
version = "1.0.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
|
||||
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
|
||||
|
||||
[[package]]
|
||||
name = "api_client"
|
||||
@@ -24,9 +24,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.0.3"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "510c76ecefdceada737ea728f4f9a84bd2e1ef29f1ba555e560940fe279954de"
|
||||
checksum = "c38b6b6b79f671c25e1a3e785b7b82d7562ffc9cd3efdc98627e5668a2472490"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
@@ -70,9 +70,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
@@ -116,9 +116,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.72"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@@ -128,9 +128,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.0.10"
|
||||
version = "3.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a30c3bf9ff12dfe5dae53f0a96e0febcd18420d1c0e7fad77796d9d5c4b5375"
|
||||
checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
@@ -145,7 +145,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cloud-hypervisor"
|
||||
version = "20.0.0"
|
||||
version = "21.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"api_client",
|
||||
@@ -245,10 +245,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b643857cf70949306b81d7e92cb9d47add673868edac9863c4a49c42feaf3f1e"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.4"
|
||||
name = "gdbstub"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
|
||||
checksum = "9fa2ca5d6b045de372cef3991f873389b421b4cabcfbe52f7787fae8b8b37906"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"log",
|
||||
"managed",
|
||||
"num-traits",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gdbstub_arch"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51dc4b5718ac76d21e8605c0966dd32d80273b89b11e6cfef467b04e45934d37"
|
||||
dependencies = [
|
||||
"gdbstub",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
@@ -291,9 +315,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "iced-x86"
|
||||
version = "1.15.0"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46e977036f7f5139d580c7f19ad62df9cb8ebd8410bb569e73585226be80a86f"
|
||||
checksum = "158f5204401d08f91d19176112146d75e99b3cf745092e268fa7be33e09adcec"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"static_assertions",
|
||||
@@ -328,7 +352,7 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||
[[package]]
|
||||
name = "kvm-bindings"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/cloud-hypervisor/kvm-bindings?branch=ch-v0.5.0#9c497710ba9968d8efe15dbae03991b16cf82e23"
|
||||
source = "git+https://github.com/cloud-hypervisor/kvm-bindings?branch=ch-v0.5.0-tdx#52e56d0e8ef0f6ea32fc0492e6a175b73617a49f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -338,7 +362,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "kvm-ioctls"
|
||||
version = "0.11.0"
|
||||
source = "git+https://github.com/rust-vmm/kvm-ioctls?branch=main#d22ef1f51852dfb055da38004e1a4fed81246f81"
|
||||
source = "git+https://github.com/rust-vmm/kvm-ioctls?branch=main#1e03e29cdfbb0cb108a98de7a78045a5a517f18e"
|
||||
dependencies = [
|
||||
"kvm-bindings",
|
||||
"libc",
|
||||
@@ -353,9 +377,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.112"
|
||||
version = "0.2.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
||||
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
@@ -386,6 +410,12 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "managed"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.1"
|
||||
@@ -428,6 +458,15 @@ dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.9.0"
|
||||
@@ -447,6 +486,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5"
|
||||
|
||||
[[package]]
|
||||
name = "pci"
|
||||
version = "0.1.0"
|
||||
@@ -456,6 +501,8 @@ dependencies = [
|
||||
"hypervisor",
|
||||
"libc",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"thiserror",
|
||||
"versionize",
|
||||
"versionize_derive",
|
||||
@@ -491,9 +538,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.14"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
|
||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -544,21 +591,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.4"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
|
||||
checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.133"
|
||||
version = "1.0.136"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
|
||||
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.133"
|
||||
version = "1.0.136"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
|
||||
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -567,9 +614,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.75"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c059c05b48c5c0067d4b4b2b4f0732dd65feb52daf7e0ea09cd87e7dadc1af79"
|
||||
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -609,9 +656,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.85"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7"
|
||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -620,9 +667,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
@@ -639,9 +686,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.14.2"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
dependencies = [
|
||||
"terminal_size",
|
||||
]
|
||||
@@ -720,12 +767,14 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "vfio-ioctls"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/vfio-ioctls?branch=main#8f7f2210ebe71660938b05b2ed7eec9253478bc5"
|
||||
source = "git+https://github.com/rust-vmm/vfio?branch=main#4630612f2ff300e78b6d55be324fb5481aa84661"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"kvm-bindings",
|
||||
"kvm-ioctls",
|
||||
"libc",
|
||||
"log",
|
||||
"thiserror",
|
||||
"vfio-bindings",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
@@ -816,6 +865,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "virtio-queue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/vm-virtio?branch=main#bbb22d43558a76cc7cc9c262c4be870a9e682a86"
|
||||
dependencies = [
|
||||
"log",
|
||||
"vm-memory",
|
||||
@@ -899,6 +949,8 @@ dependencies = [
|
||||
"devices",
|
||||
"epoll",
|
||||
"event_monitor",
|
||||
"gdbstub",
|
||||
"gdbstub_arch",
|
||||
"hypervisor",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
|
||||
@@ -10,13 +10,13 @@ cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
block_util = { path = "../block_util" }
|
||||
libc = "0.2.112"
|
||||
libc = "0.2.119"
|
||||
libfuzzer-sys = "0.4.2"
|
||||
qcow = { path = "../qcow" }
|
||||
seccompiler = "0.2.0"
|
||||
vhdx = { path = "../vhdx" }
|
||||
virtio-devices = { path = "../virtio-devices" }
|
||||
virtio-queue = { path = "../virtio-queue" }
|
||||
virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", branch = "main" }
|
||||
vmm-sys-util = "0.9.0"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vm-memory = "0.7.0"
|
||||
@@ -25,7 +25,7 @@ vm-memory = "0.7.0"
|
||||
path = ".."
|
||||
|
||||
[patch.crates-io]
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.5.0", features = ["with-serde", "fam-wrappers"] }
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.5.0-tdx" }
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "main" }
|
||||
versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch" }
|
||||
|
||||
|
||||
@@ -139,8 +139,7 @@ pub struct NoopVirtioInterrupt {}
|
||||
impl VirtioInterrupt for NoopVirtioInterrupt {
|
||||
fn trigger(
|
||||
&self,
|
||||
_int_type: &VirtioInterruptType,
|
||||
_queue: Option<&Queue<GuestMemoryAtomic<GuestMemoryMmap>>>,
|
||||
_int_type: VirtioInterruptType,
|
||||
) -> std::result::Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -11,23 +11,23 @@ mshv = ["mshv-ioctls", "mshv-bindings"]
|
||||
tdx = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.52"
|
||||
anyhow = "1.0.55"
|
||||
epoll = "4.3.1"
|
||||
thiserror = "1.0.30"
|
||||
libc = "0.2.112"
|
||||
libc = "0.2.119"
|
||||
log = "0.4.14"
|
||||
kvm-ioctls = { version = "0.11.0", optional = true }
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.5.0", features = ["with-serde", "fam-wrappers"], optional = true }
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.5.0-tdx", features = ["with-serde", "fam-wrappers"], optional = true }
|
||||
mshv-bindings = {git = "https://github.com/rust-vmm/mshv", branch = "main", features = ["with-serde", "fam-wrappers"], optional = true }
|
||||
mshv-ioctls = { git = "https://github.com/rust-vmm/mshv", branch = "main", optional = true}
|
||||
serde = { version = "1.0.133", features = ["rc"] }
|
||||
serde_derive = "1.0.133"
|
||||
serde_json = "1.0.75"
|
||||
serde = { version = "1.0.136", features = ["rc"] }
|
||||
serde_derive = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
vm-memory = { version = "0.7.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vmm-sys-util = { version = "0.9.0", features = ["with-serde"] }
|
||||
|
||||
[target.'cfg(target_arch = "x86_64")'.dependencies.iced-x86]
|
||||
version = "1.15.0"
|
||||
version = "1.17.0"
|
||||
default-features = false
|
||||
features = ["std", "decoder", "op_code_info", "instr_info", "fast_fmt"]
|
||||
|
||||
|
||||
@@ -19,15 +19,19 @@ use crate::x86_64::{
|
||||
ExtendedControlRegisters, FpuState, MsrEntries, SpecialRegisters, StandardRegisters, VcpuEvents,
|
||||
};
|
||||
use crate::CpuState;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::DeviceAttr;
|
||||
#[cfg(feature = "kvm")]
|
||||
use crate::MpState;
|
||||
#[cfg(all(feature = "mshv", target_arch = "x86_64"))]
|
||||
use crate::SuspendRegisters;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::Xsave;
|
||||
#[cfg(feature = "mshv")]
|
||||
use mshv_bindings::*;
|
||||
#[cfg(feature = "tdx")]
|
||||
use crate::{TdxExitDetails, TdxExitStatus};
|
||||
use thiserror::Error;
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
///
|
||||
@@ -223,11 +227,27 @@ pub enum HypervisorCpuError {
|
||||
#[error("Failed to translate GVA: {0}")]
|
||||
TranslateVirtualAddress(#[source] anyhow::Error),
|
||||
///
|
||||
/// Set cpu attribute error
|
||||
///
|
||||
#[error("Failed to set vcpu attribute: {0}")]
|
||||
SetVcpuAttribute(#[source] anyhow::Error),
|
||||
///
|
||||
/// Check if cpu has a certain attribute error
|
||||
///
|
||||
#[error("Failed to check if vcpu has attribute: {0}")]
|
||||
HasVcpuAttribute(#[source] anyhow::Error),
|
||||
///
|
||||
/// Failed to initialize TDX on CPU
|
||||
///
|
||||
#[cfg(feature = "tdx")]
|
||||
#[error("Failed to initialize TDX: {0}")]
|
||||
InitializeTdx(#[source] std::io::Error),
|
||||
///
|
||||
/// Unknown TDX VM call
|
||||
///
|
||||
#[cfg(feature = "tdx")]
|
||||
#[error("Unknown TDX VM call")]
|
||||
UnknownTdxVmCall,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -244,6 +264,10 @@ pub enum VmExit<'a> {
|
||||
Reset,
|
||||
Shutdown,
|
||||
Hyperv,
|
||||
#[cfg(feature = "tdx")]
|
||||
Tdx,
|
||||
#[cfg(feature = "kvm")]
|
||||
Debug,
|
||||
}
|
||||
|
||||
///
|
||||
@@ -259,7 +283,16 @@ pub trait Vcpu: Send + Sync {
|
||||
/// Returns the vCPU general purpose registers.
|
||||
///
|
||||
fn get_regs(&self) -> Result<StandardRegisters>;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
///
|
||||
/// Sets vcpu attribute
|
||||
///
|
||||
fn set_vcpu_attr(&self, attr: &DeviceAttr) -> Result<()>;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
///
|
||||
/// Check if vcpu has attribute.
|
||||
///
|
||||
fn has_vcpu_attr(&self, attr: &DeviceAttr) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets the vCPU general purpose registers.
|
||||
@@ -368,6 +401,11 @@ pub trait Vcpu: Send + Sync {
|
||||
/// potential soft lockups when being resumed.
|
||||
///
|
||||
fn notify_guest_clock_paused(&self) -> Result<()>;
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
///
|
||||
/// Sets debug registers to set hardware breakpoints and/or enable single step.
|
||||
///
|
||||
fn set_guest_debug(&self, addrs: &[GuestAddress], singlestep: bool) -> Result<()>;
|
||||
///
|
||||
/// Sets the type of CPU to be exposed to the guest and optional features.
|
||||
///
|
||||
@@ -428,11 +466,11 @@ pub trait Vcpu: Send + Sync {
|
||||
/// Triggers the running of the current virtual CPU returning an exit reason.
|
||||
///
|
||||
fn run(&self) -> std::result::Result<VmExit, HypervisorCpuError>;
|
||||
#[cfg(all(feature = "mshv", target_arch = "x86_64"))]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Translate guest virtual address to guest physical address
|
||||
///
|
||||
fn translate_gva(&self, gva: u64, flags: u64) -> Result<(u64, hv_translate_gva_result)>;
|
||||
fn translate_gva(&self, gva: u64, flags: u64) -> Result<(u64, u32)>;
|
||||
///
|
||||
/// Initialize TDX support on the vCPU
|
||||
///
|
||||
@@ -448,4 +486,14 @@ pub trait Vcpu: Send + Sync {
|
||||
/// Set the "immediate_exit" state
|
||||
///
|
||||
fn set_immediate_exit(&self, exit: bool);
|
||||
#[cfg(feature = "tdx")]
|
||||
///
|
||||
/// Returns the details about TDX exit reason
|
||||
///
|
||||
fn get_tdx_exit_details(&mut self) -> Result<TdxExitDetails>;
|
||||
#[cfg(feature = "tdx")]
|
||||
///
|
||||
/// Set the status code for TDX exit
|
||||
///
|
||||
fn set_tdx_status(&mut self, status: TdxExitStatus);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ use crate::arch::x86::NUM_IOAPIC_PINS;
|
||||
use aarch64::{RegList, Register, StandardRegisters};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use kvm_bindings::{
|
||||
kvm_enable_cap, kvm_msr_entry, MsrList, KVM_CAP_HYPERV_SYNIC, KVM_CAP_SPLIT_IRQCHIP,
|
||||
kvm_enable_cap, kvm_guest_debug, kvm_msr_entry, MsrList, KVM_CAP_HYPERV_SYNIC,
|
||||
KVM_CAP_SPLIT_IRQCHIP, KVM_GUESTDBG_ENABLE, KVM_GUESTDBG_SINGLESTEP, KVM_GUESTDBG_USE_HW_BP,
|
||||
};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use x86_64::{check_required_kvm_extensions, FpuState, SpecialRegisters, StandardRegisters};
|
||||
@@ -81,7 +82,7 @@ pub use {
|
||||
kvm_bindings::kvm_clock_data as ClockData, kvm_bindings::kvm_create_device as CreateDevice,
|
||||
kvm_bindings::kvm_device_attr as DeviceAttr,
|
||||
kvm_bindings::kvm_irq_routing_entry as IrqRoutingEntry, kvm_bindings::kvm_mp_state as MpState,
|
||||
kvm_bindings::kvm_userspace_memory_region as MemoryRegion,
|
||||
kvm_bindings::kvm_run, kvm_bindings::kvm_userspace_memory_region as MemoryRegion,
|
||||
kvm_bindings::kvm_vcpu_events as VcpuEvents, kvm_ioctls::DeviceFd, kvm_ioctls::IoEventAddress,
|
||||
kvm_ioctls::VcpuExit,
|
||||
};
|
||||
@@ -89,6 +90,17 @@ pub use {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const KVM_CAP_SGX_ATTRIBUTE: u32 = 196;
|
||||
|
||||
#[cfg(feature = "tdx")]
|
||||
const KVM_EXIT_TDX: u32 = 35;
|
||||
#[cfg(feature = "tdx")]
|
||||
const TDG_VP_VMCALL_GET_QUOTE: u64 = 0x10002;
|
||||
#[cfg(feature = "tdx")]
|
||||
const TDG_VP_VMCALL_SETUP_EVENT_NOTIFY_INTERRUPT: u64 = 0x10004;
|
||||
#[cfg(feature = "tdx")]
|
||||
const TDG_VP_VMCALL_SUCCESS: u64 = 0;
|
||||
#[cfg(feature = "tdx")]
|
||||
const TDG_VP_VMCALL_INVALID_OPERAND: u64 = 0x8000000000000000;
|
||||
|
||||
#[cfg(feature = "tdx")]
|
||||
ioctl_iowr_nr!(KVM_MEMORY_ENCRYPT_OP, KVMIO, 0xba, std::os::raw::c_ulong);
|
||||
|
||||
@@ -103,6 +115,18 @@ enum TdxCommand {
|
||||
Finalize,
|
||||
}
|
||||
|
||||
#[cfg(feature = "tdx")]
|
||||
pub enum TdxExitDetails {
|
||||
GetQuote,
|
||||
SetupEventNotifyInterrupt,
|
||||
}
|
||||
|
||||
#[cfg(feature = "tdx")]
|
||||
pub enum TdxExitStatus {
|
||||
Success,
|
||||
InvalidOperand,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct KvmVmState {}
|
||||
|
||||
@@ -777,6 +801,27 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
.set_regs(regs)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetStandardRegs(e.into()))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
///
|
||||
/// Set attribute for vcpu.
|
||||
///
|
||||
fn set_vcpu_attr(&self, attr: &DeviceAttr) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_device_attr(attr)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetVcpuAttribute(e.into()))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
///
|
||||
/// Check if vcpu has a certain attribute.
|
||||
///
|
||||
fn has_vcpu_attr(&self, attr: &DeviceAttr) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.has_device_attr(attr)
|
||||
.map_err(|e| cpu::HypervisorCpuError::HasVcpuAttribute(e.into()))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the vCPU special registers.
|
||||
@@ -937,6 +982,24 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
.set_xcrs(xcrs)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetXcsr(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Translates guest virtual address to guest physical address using the `KVM_TRANSLATE` ioctl.
|
||||
///
|
||||
fn translate_gva(&self, gva: u64, _flags: u64) -> cpu::Result<(u64, u32)> {
|
||||
let tr = self
|
||||
.fd
|
||||
.translate_gva(gva)
|
||||
.map_err(|e| cpu::HypervisorCpuError::TranslateVirtualAddress(e.into()))?;
|
||||
// tr.valid is set if the GVA is mapped to valid GPA.
|
||||
match tr.valid {
|
||||
0 => Err(cpu::HypervisorCpuError::TranslateVirtualAddress(anyhow!(
|
||||
"Invalid GVA: {:#x}",
|
||||
gva
|
||||
))),
|
||||
_ => Ok((tr.physical_address, 0)),
|
||||
}
|
||||
}
|
||||
///
|
||||
/// Triggers the running of the current virtual CPU returning an exit reason.
|
||||
///
|
||||
@@ -1009,6 +1072,9 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
Ok(cpu::VmExit::MmioWrite(addr, data))
|
||||
}
|
||||
VcpuExit::Hyperv => Ok(cpu::VmExit::Hyperv),
|
||||
#[cfg(feature = "tdx")]
|
||||
VcpuExit::Unsupported(KVM_EXIT_TDX) => Ok(cpu::VmExit::Tdx),
|
||||
VcpuExit::Debug(_) => Ok(cpu::VmExit::Debug),
|
||||
|
||||
r => Err(cpu::HypervisorCpuError::RunVcpu(anyhow!(
|
||||
"Unexpected exit reason on vcpu run: {:?}",
|
||||
@@ -1062,6 +1128,45 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets debug registers to set hardware breakpoints and/or enable single step.
|
||||
///
|
||||
fn set_guest_debug(
|
||||
&self,
|
||||
addrs: &[vm_memory::GuestAddress],
|
||||
singlestep: bool,
|
||||
) -> cpu::Result<()> {
|
||||
if addrs.len() > 4 {
|
||||
return Err(cpu::HypervisorCpuError::SetDebugRegs(anyhow!(
|
||||
"Support 4 breakpoints at most but {} addresses are passed",
|
||||
addrs.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let mut dbg = kvm_guest_debug {
|
||||
control: KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP,
|
||||
..Default::default()
|
||||
};
|
||||
if singlestep {
|
||||
dbg.control |= KVM_GUESTDBG_SINGLESTEP;
|
||||
}
|
||||
|
||||
// Set bits 9 and 10.
|
||||
// bit 9: GE (global exact breakpoint enable) flag.
|
||||
// bit 10: always 1.
|
||||
dbg.arch.debugreg[7] = 0x0600;
|
||||
|
||||
for (i, addr) in addrs.iter().enumerate() {
|
||||
dbg.arch.debugreg[i] = addr.0;
|
||||
// Set global breakpoint enable flag
|
||||
dbg.arch.debugreg[7] |= 2 << (i * 2);
|
||||
}
|
||||
|
||||
self.fd
|
||||
.set_guest_debug(&dbg)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetDebugRegs(e.into()))
|
||||
}
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn vcpu_init(&self, kvi: &VcpuInit) -> cpu::Result<()> {
|
||||
self.fd
|
||||
@@ -1581,6 +1686,43 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
fn set_immediate_exit(&self, exit: bool) {
|
||||
self.fd.set_kvm_immediate_exit(exit.into());
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the details about TDX exit reason
|
||||
///
|
||||
#[cfg(feature = "tdx")]
|
||||
fn get_tdx_exit_details(&mut self) -> cpu::Result<TdxExitDetails> {
|
||||
let kvm_run = self.fd.get_kvm_run();
|
||||
let tdx_vmcall = unsafe { &mut kvm_run.__bindgen_anon_1.tdx.u.vmcall };
|
||||
|
||||
tdx_vmcall.status_code = TDG_VP_VMCALL_INVALID_OPERAND;
|
||||
|
||||
if tdx_vmcall.type_ != 0 {
|
||||
return Err(cpu::HypervisorCpuError::UnknownTdxVmCall);
|
||||
}
|
||||
|
||||
match tdx_vmcall.subfunction {
|
||||
TDG_VP_VMCALL_GET_QUOTE => Ok(TdxExitDetails::GetQuote),
|
||||
TDG_VP_VMCALL_SETUP_EVENT_NOTIFY_INTERRUPT => {
|
||||
Ok(TdxExitDetails::SetupEventNotifyInterrupt)
|
||||
}
|
||||
_ => Err(cpu::HypervisorCpuError::UnknownTdxVmCall),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Set the status code for TDX exit
|
||||
///
|
||||
#[cfg(feature = "tdx")]
|
||||
fn set_tdx_status(&mut self, status: TdxExitStatus) {
|
||||
let kvm_run = self.fd.get_kvm_run();
|
||||
let tdx_vmcall = unsafe { &mut kvm_run.__bindgen_anon_1.tdx.u.vmcall };
|
||||
|
||||
tdx_vmcall.status_code = match status {
|
||||
TdxExitStatus::Success => TDG_VP_VMCALL_SUCCESS,
|
||||
TdxExitStatus::InvalidOperand => TDG_VP_VMCALL_INVALID_OPERAND,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Device struct for KVM
|
||||
|
||||
@@ -561,13 +561,17 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
///
|
||||
/// Translate guest virtual address to guest physical address
|
||||
///
|
||||
fn translate_gva(&self, gva: u64, flags: u64) -> cpu::Result<(u64, hv_translate_gva_result)> {
|
||||
fn translate_gva(&self, gva: u64, flags: u64) -> cpu::Result<(u64, u32)> {
|
||||
let r = self
|
||||
.fd
|
||||
.translate_gva(gva, flags)
|
||||
.map_err(|e| cpu::HypervisorCpuError::TranslateVirtualAddress(e.into()))?;
|
||||
|
||||
Ok(r)
|
||||
let gpa = r.0;
|
||||
// SAFETY: r is valid, otherwise this function will have returned
|
||||
let result_code = unsafe { r.1.__bindgen_anon_1.result_code };
|
||||
|
||||
Ok((gpa, result_code))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
@@ -626,15 +630,13 @@ impl<'a> MshvEmulatorContext<'a> {
|
||||
// TODO: More fine-grained control for the flags
|
||||
let flags = HV_TRANSLATE_GVA_VALIDATE_READ | HV_TRANSLATE_GVA_VALIDATE_WRITE;
|
||||
|
||||
let r = self
|
||||
let (gpa, result_code) = self
|
||||
.vcpu
|
||||
.translate_gva(gva, flags.into())
|
||||
.map_err(|e| PlatformError::TranslateVirtualAddress(anyhow!(e)))?;
|
||||
|
||||
// SAFETY: r is valid, otherwise this function will have returned
|
||||
let result_code = unsafe { r.1.__bindgen_anon_1.result_code };
|
||||
match result_code {
|
||||
hv_translate_gva_result_code_HV_TRANSLATE_GVA_SUCCESS => Ok(r.0),
|
||||
hv_translate_gva_result_code_HV_TRANSLATE_GVA_SUCCESS => Ok(gpa),
|
||||
_ => Err(PlatformError::TranslateVirtualAddress(anyhow!(result_code))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,15 @@ edition = "2018"
|
||||
[dependencies]
|
||||
epoll = "4.3.1"
|
||||
getrandom = "0.2"
|
||||
libc = "0.2.112"
|
||||
libc = "0.2.119"
|
||||
log = "0.4.14"
|
||||
net_gen = { path = "../net_gen" }
|
||||
rate_limiter = { path = "../rate_limiter" }
|
||||
serde = "1.0.133"
|
||||
serde = "1.0.136"
|
||||
versionize = "0.1.6"
|
||||
versionize_derive = "0.1.4"
|
||||
virtio-bindings = "0.1.0"
|
||||
virtio-queue = { path = "../virtio-queue" }
|
||||
virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", branch = "main" }
|
||||
vm-memory = { version = "0.7.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = "0.9.0"
|
||||
@@ -23,4 +23,4 @@ vmm-sys-util = "0.9.0"
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
pnet = "0.29.0"
|
||||
serde_json = "1.0.75"
|
||||
serde_json = "1.0.79"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
use crate::GuestMemoryMmap;
|
||||
use crate::Tap;
|
||||
use libc::c_uint;
|
||||
use std::sync::Arc;
|
||||
use virtio_bindings::bindings::virtio_net::{
|
||||
VIRTIO_NET_CTRL_GUEST_OFFLOADS, VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET, VIRTIO_NET_CTRL_MQ,
|
||||
VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX, VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN,
|
||||
@@ -14,6 +15,7 @@ use virtio_bindings::bindings::virtio_net::{
|
||||
};
|
||||
use virtio_queue::Queue;
|
||||
use vm_memory::{ByteValued, Bytes, GuestMemoryAtomic, GuestMemoryError};
|
||||
use vm_virtio::{AccessPlatform, Translatable};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -57,6 +59,7 @@ impl CtrlQueue {
|
||||
pub fn process(
|
||||
&mut self,
|
||||
queue: &mut Queue<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
||||
) -> Result<bool> {
|
||||
let mut used_desc_heads = Vec::new();
|
||||
for mut desc_chain in queue.iter().map_err(Error::QueueIterator)? {
|
||||
@@ -64,16 +67,25 @@ impl CtrlQueue {
|
||||
|
||||
let ctrl_hdr: ControlHeader = desc_chain
|
||||
.memory()
|
||||
.read_obj(ctrl_desc.addr())
|
||||
.read_obj(
|
||||
ctrl_desc
|
||||
.addr()
|
||||
.translate(access_platform, ctrl_desc.len() as usize),
|
||||
)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
let data_desc = desc_chain.next().ok_or(Error::NoDataDescriptor)?;
|
||||
|
||||
let data_desc_addr = data_desc
|
||||
.addr()
|
||||
.translate(access_platform, data_desc.len() as usize);
|
||||
|
||||
let status_desc = desc_chain.next().ok_or(Error::NoStatusDescriptor)?;
|
||||
|
||||
let ok = match u32::from(ctrl_hdr.class) {
|
||||
VIRTIO_NET_CTRL_MQ => {
|
||||
let queue_pairs = desc_chain
|
||||
.memory()
|
||||
.read_obj::<u16>(data_desc.addr())
|
||||
.read_obj::<u16>(data_desc_addr)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
if u32::from(ctrl_hdr.cmd) != VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET {
|
||||
warn!("Unsupported command: {}", ctrl_hdr.cmd);
|
||||
@@ -91,7 +103,7 @@ impl CtrlQueue {
|
||||
VIRTIO_NET_CTRL_GUEST_OFFLOADS => {
|
||||
let features = desc_chain
|
||||
.memory()
|
||||
.read_obj::<u64>(data_desc.addr())
|
||||
.read_obj::<u64>(data_desc_addr)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
if u32::from(ctrl_hdr.cmd) != VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET {
|
||||
warn!("Unsupported command: {}", ctrl_hdr.cmd);
|
||||
@@ -120,7 +132,9 @@ impl CtrlQueue {
|
||||
.memory()
|
||||
.write_obj(
|
||||
if ok { VIRTIO_NET_OK } else { VIRTIO_NET_ERR } as u8,
|
||||
status_desc.addr(),
|
||||
status_desc
|
||||
.addr()
|
||||
.translate(access_platform, status_desc.len() as usize),
|
||||
)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
let len = ctrl_desc.len() + data_desc.len() + status_desc.len();
|
||||
|
||||
@@ -77,7 +77,7 @@ fn create_sockaddr(ip_addr: net::Ipv4Addr) -> net_gen::sockaddr {
|
||||
unsafe { mem::transmute(addr_in) }
|
||||
}
|
||||
|
||||
fn create_socket() -> Result<net::UdpSocket> {
|
||||
fn create_inet_socket() -> Result<net::UdpSocket> {
|
||||
// This is safe since we check the return value.
|
||||
let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) };
|
||||
if sock < 0 {
|
||||
@@ -88,6 +88,17 @@ fn create_socket() -> Result<net::UdpSocket> {
|
||||
Ok(unsafe { net::UdpSocket::from_raw_fd(sock) })
|
||||
}
|
||||
|
||||
fn create_unix_socket() -> Result<net::UdpSocket> {
|
||||
// This is safe since we check the return value.
|
||||
let sock = unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_DGRAM, 0) };
|
||||
if sock < 0 {
|
||||
return Err(Error::CreateSocket(IoError::last_os_error()));
|
||||
}
|
||||
|
||||
// This is safe; nothing else will use or hold onto the raw sock fd.
|
||||
Ok(unsafe { net::UdpSocket::from_raw_fd(sock) })
|
||||
}
|
||||
|
||||
fn vnet_hdr_len() -> usize {
|
||||
std::mem::size_of::<virtio_net_hdr_v1>()
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use virtio_queue::Queue;
|
||||
use vm_memory::{Bytes, GuestMemory, GuestMemoryAtomic};
|
||||
use vm_virtio::{AccessPlatform, Translatable};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TxVirtio {
|
||||
@@ -38,6 +39,7 @@ impl TxVirtio {
|
||||
tap: &mut Tap,
|
||||
queue: &mut Queue<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
rate_limiter: &mut Option<RateLimiter>,
|
||||
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
||||
) -> Result<bool, NetQueuePairError> {
|
||||
let mut retry_write = false;
|
||||
let mut rate_limit_reached = false;
|
||||
@@ -58,10 +60,11 @@ impl TxVirtio {
|
||||
|
||||
let mut iovecs = Vec::new();
|
||||
while let Some(desc) = next_desc {
|
||||
let desc_addr = desc.addr().translate(access_platform, desc.len() as usize);
|
||||
if !desc.is_write_only() && desc.len() > 0 {
|
||||
let buf = desc_chain
|
||||
.memory()
|
||||
.get_slice(desc.addr(), desc.len() as usize)
|
||||
.get_slice(desc_addr, desc.len() as usize)
|
||||
.map_err(NetQueuePairError::GuestMemory)?
|
||||
.as_ptr();
|
||||
let iovec = libc::iovec {
|
||||
@@ -72,7 +75,7 @@ impl TxVirtio {
|
||||
} else {
|
||||
error!(
|
||||
"Invalid descriptor chain: address = 0x{:x} length = {} write_only = {}",
|
||||
desc.addr().0,
|
||||
desc_addr.0,
|
||||
desc.len(),
|
||||
desc.is_write_only()
|
||||
);
|
||||
@@ -161,6 +164,7 @@ impl RxVirtio {
|
||||
tap: &mut Tap,
|
||||
queue: &mut Queue<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
rate_limiter: &mut Option<RateLimiter>,
|
||||
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
||||
) -> Result<bool, NetQueuePairError> {
|
||||
let mut exhausted_descs = true;
|
||||
let mut rate_limit_reached = false;
|
||||
@@ -181,15 +185,23 @@ impl RxVirtio {
|
||||
let desc = desc_chain
|
||||
.next()
|
||||
.ok_or(NetQueuePairError::DescriptorChainTooShort)?;
|
||||
let num_buffers_addr = desc_chain.memory().checked_offset(desc.addr(), 10).unwrap();
|
||||
|
||||
let num_buffers_addr = desc_chain
|
||||
.memory()
|
||||
.checked_offset(
|
||||
desc.addr().translate(access_platform, desc.len() as usize),
|
||||
10,
|
||||
)
|
||||
.unwrap();
|
||||
let mut next_desc = Some(desc);
|
||||
|
||||
let mut iovecs = Vec::new();
|
||||
while let Some(desc) = next_desc {
|
||||
let desc_addr = desc.addr().translate(access_platform, desc.len() as usize);
|
||||
if desc.is_write_only() && desc.len() > 0 {
|
||||
let buf = desc_chain
|
||||
.memory()
|
||||
.get_slice(desc.addr(), desc.len() as usize)
|
||||
.get_slice(desc_addr, desc.len() as usize)
|
||||
.map_err(NetQueuePairError::GuestMemory)?
|
||||
.as_ptr();
|
||||
let iovec = libc::iovec {
|
||||
@@ -200,7 +212,7 @@ impl RxVirtio {
|
||||
} else {
|
||||
error!(
|
||||
"Invalid descriptor chain: address = 0x{:x} length = {} write_only = {}",
|
||||
desc.addr().0,
|
||||
desc_addr.0,
|
||||
desc.len(),
|
||||
desc.is_write_only()
|
||||
);
|
||||
@@ -326,6 +338,7 @@ pub struct NetQueuePair {
|
||||
pub rx_desc_avail: bool,
|
||||
pub rx_rate_limiter: Option<RateLimiter>,
|
||||
pub tx_rate_limiter: Option<RateLimiter>,
|
||||
pub access_platform: Option<Arc<dyn AccessPlatform>>,
|
||||
}
|
||||
|
||||
impl NetQueuePair {
|
||||
@@ -333,9 +346,12 @@ impl NetQueuePair {
|
||||
&mut self,
|
||||
queue: &mut Queue<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
) -> Result<bool, NetQueuePairError> {
|
||||
let tx_tap_retry =
|
||||
self.tx
|
||||
.process_desc_chain(&mut self.tap, queue, &mut self.tx_rate_limiter)?;
|
||||
let tx_tap_retry = self.tx.process_desc_chain(
|
||||
&mut self.tap,
|
||||
queue,
|
||||
&mut self.tx_rate_limiter,
|
||||
self.access_platform.as_ref(),
|
||||
)?;
|
||||
|
||||
// We got told to try again when writing to the tap. Wait for the TAP to be writable
|
||||
if tx_tap_retry && !self.tx_tap_listening {
|
||||
@@ -378,10 +394,12 @@ impl NetQueuePair {
|
||||
&mut self,
|
||||
queue: &mut Queue<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
) -> Result<bool, NetQueuePairError> {
|
||||
self.rx_desc_avail =
|
||||
!self
|
||||
.rx
|
||||
.process_desc_chain(&mut self.tap, queue, &mut self.rx_rate_limiter)?;
|
||||
self.rx_desc_avail = !self.rx.process_desc_chain(
|
||||
&mut self.tap,
|
||||
queue,
|
||||
&mut self.rx_rate_limiter,
|
||||
self.access_platform.as_ref(),
|
||||
)?;
|
||||
let rate_limit_reached = self
|
||||
.rx_rate_limiter
|
||||
.as_ref()
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use super::{create_sockaddr, create_socket, vnet_hdr_len, Error as NetUtilError, MacAddr};
|
||||
use super::{
|
||||
create_inet_socket, create_sockaddr, create_unix_socket, vnet_hdr_len, Error as NetUtilError,
|
||||
MacAddr,
|
||||
};
|
||||
use crate::mac::MAC_ADDR_LEN;
|
||||
use std::fs::File;
|
||||
use std::io::{Error as IoError, Read, Result as IoResult, Write};
|
||||
@@ -196,7 +199,7 @@ impl Tap {
|
||||
|
||||
/// Set the host-side IP address for the tap interface.
|
||||
pub fn set_ip_addr(&self, ip_addr: net::Ipv4Addr) -> Result<()> {
|
||||
let sock = create_socket().map_err(Error::NetUtil)?;
|
||||
let sock = create_inet_socket().map_err(Error::NetUtil)?;
|
||||
let addr = create_sockaddr(ip_addr);
|
||||
|
||||
let mut ifreq = self.get_ifreq();
|
||||
@@ -224,7 +227,7 @@ impl Tap {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let sock = create_socket().map_err(Error::NetUtil)?;
|
||||
let sock = create_unix_socket().map_err(Error::NetUtil)?;
|
||||
|
||||
let mut ifreq = self.get_ifreq();
|
||||
|
||||
@@ -254,7 +257,7 @@ impl Tap {
|
||||
|
||||
/// Get mac addr for tap interface.
|
||||
pub fn get_mac_addr(&self) -> Result<MacAddr> {
|
||||
let sock = create_socket().map_err(Error::NetUtil)?;
|
||||
let sock = create_unix_socket().map_err(Error::NetUtil)?;
|
||||
|
||||
let ifreq = self.get_ifreq();
|
||||
|
||||
@@ -275,7 +278,7 @@ impl Tap {
|
||||
|
||||
/// Set the netmask for the subnet that the tap interface will exist on.
|
||||
pub fn set_netmask(&self, netmask: net::Ipv4Addr) -> Result<()> {
|
||||
let sock = create_socket().map_err(Error::NetUtil)?;
|
||||
let sock = create_inet_socket().map_err(Error::NetUtil)?;
|
||||
let addr = create_sockaddr(netmask);
|
||||
|
||||
let mut ifreq = self.get_ifreq();
|
||||
@@ -306,7 +309,7 @@ impl Tap {
|
||||
|
||||
/// Enable the tap interface.
|
||||
pub fn enable(&self) -> Result<()> {
|
||||
let sock = create_socket().map_err(Error::NetUtil)?;
|
||||
let sock = create_unix_socket().map_err(Error::NetUtil)?;
|
||||
|
||||
let mut ifreq = self.get_ifreq();
|
||||
|
||||
|
||||
@@ -10,14 +10,16 @@ kvm = ["vfio-ioctls/kvm"]
|
||||
mshv = ["vfio-ioctls/mshv"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.52"
|
||||
anyhow = "1.0.55"
|
||||
byteorder = "1.4.3"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
vfio-ioctls = { git = "https://github.com/rust-vmm/vfio-ioctls", branch = "main", default-features = false }
|
||||
vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false }
|
||||
vfio_user = { path = "../vfio_user" }
|
||||
vmm-sys-util = "0.9.0"
|
||||
libc = "0.2.112"
|
||||
libc = "0.2.119"
|
||||
log = "0.4.14"
|
||||
serde = "1.0.136"
|
||||
serde_derive = "1.0.136"
|
||||
thiserror = "1.0.30"
|
||||
versionize = "0.1.6"
|
||||
versionize_derive = "0.1.4"
|
||||
|
||||
@@ -21,6 +21,8 @@ const ROM_BAR_REG: usize = 12;
|
||||
const BAR_IO_ADDR_MASK: u32 = 0xffff_fffc;
|
||||
const BAR_MEM_ADDR_MASK: u32 = 0xffff_fff0;
|
||||
const ROM_BAR_ADDR_MASK: u32 = 0xffff_f800;
|
||||
const MSI_CAPABILITY_REGISTER_MASK: u32 = 0x0071_0000;
|
||||
const MSIX_CAPABILITY_REGISTER_MASK: u32 = 0xc000_0000;
|
||||
const NUM_BAR_REGS: usize = 6;
|
||||
const CAPABILITY_LIST_HEAD_OFFSET: usize = 0x34;
|
||||
const FIRST_CAPABILITY_OFFSET: usize = 0x40;
|
||||
@@ -714,8 +716,15 @@ impl PciConfiguration {
|
||||
}
|
||||
self.last_capability = Some((cap_offset, total_len));
|
||||
|
||||
if cap_data.id() == PciCapabilityId::MsiX {
|
||||
self.msix_cap_reg_idx = Some(cap_offset / 4);
|
||||
match cap_data.id() {
|
||||
PciCapabilityId::MessageSignalledInterrupts => {
|
||||
self.writable_bits[cap_offset / 4] = MSI_CAPABILITY_REGISTER_MASK;
|
||||
}
|
||||
PciCapabilityId::MsiX => {
|
||||
self.msix_cap_reg_idx = Some(cap_offset / 4);
|
||||
self.writable_bits[self.msix_cap_reg_idx.unwrap()] = MSIX_CAPABILITY_REGISTER_MASK;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(cap_offset)
|
||||
|
||||
@@ -27,7 +27,10 @@ pub use self::msi::{msi_num_enabled_vectors, MsiCap, MsiConfig};
|
||||
pub use self::msix::{MsixCap, MsixConfig, MsixTableEntry, MSIX_TABLE_ENTRY_SIZE};
|
||||
pub use self::vfio::{VfioPciDevice, VfioPciError};
|
||||
pub use self::vfio_user::{VfioUserDmaMapping, VfioUserPciDevice, VfioUserPciDeviceError};
|
||||
use std::fmt::Display;
|
||||
use serde::de::Visitor;
|
||||
use std::fmt::{self, Display};
|
||||
use std::num::ParseIntError;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// PCI has four interrupt pins A->D.
|
||||
#[derive(Copy, Clone)]
|
||||
@@ -52,6 +55,41 @@ pub const PCI_CONFIG_IO_PORT_SIZE: u64 = 0x8;
|
||||
#[derive(Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct PciBdf(u32);
|
||||
|
||||
struct PciBdfVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for PciBdfVisitor {
|
||||
type Value = PciBdf;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("struct PciBdf")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for PciBdf {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(PciBdfVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for PciBdf {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.collect_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl PciBdf {
|
||||
pub fn segment(&self) -> u16 {
|
||||
((self.0 >> 16) & 0xffff) as u16
|
||||
@@ -121,3 +159,25 @@ impl Display for PciBdf {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PciBdf {
|
||||
type Err = ParseIntError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let items: Vec<&str> = s.split('.').collect();
|
||||
assert_eq!(items.len(), 2);
|
||||
let function = u8::from_str_radix(items[1], 16)?;
|
||||
let items: Vec<&str> = items[0].split(':').collect();
|
||||
assert_eq!(items.len(), 3);
|
||||
let segment = u16::from_str_radix(items[0], 16)?;
|
||||
let bus = u8::from_str_radix(items[1], 16)?;
|
||||
let device = u8::from_str_radix(items[2], 16)?;
|
||||
Ok(PciBdf::new(segment, bus, device, function))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for PciBdf {
|
||||
fn from(bdf: &str) -> Self {
|
||||
Self::from_str(bdf).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,7 @@ impl MsixConfig {
|
||||
// Update interrupt routing
|
||||
if old_masked != self.masked || old_enabled != self.enabled {
|
||||
if self.enabled && !self.masked {
|
||||
debug!("MSI-X enabled for device 0x{:x}", self.devid);
|
||||
for (idx, table_entry) in self.table_entries.iter().enumerate() {
|
||||
let config = MsiIrqSourceConfig {
|
||||
high_addr: table_entry.msg_addr_hi,
|
||||
@@ -187,6 +188,7 @@ impl MsixConfig {
|
||||
}
|
||||
}
|
||||
} else if old_enabled || !old_masked {
|
||||
debug!("MSI-X disabled for device 0x{:x}", self.devid);
|
||||
if let Err(e) = self.interrupt_source_group.disable() {
|
||||
error!("Failed disabling irq_fd: {:?}", e);
|
||||
}
|
||||
|
||||
19
performance-metrics/Cargo.toml
Normal file
19
performance-metrics/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "performance-metrics"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "3.1.5", features = ["wrap_help","cargo"] }
|
||||
dirs = "4.0.0"
|
||||
serde = { version = "1.0.136", features = ["rc"] }
|
||||
serde_derive = "1.0.136"
|
||||
serde_json = "1.0.78"
|
||||
test_infra = { path = "../test_infra" }
|
||||
thiserror = "1.0.30"
|
||||
wait-timeout = "0.2.0"
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version = "3.1.5", features = ["wrap_help"] }
|
||||
26
performance-metrics/build.rs
Normal file
26
performance-metrics/build.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#[macro_use(crate_version)]
|
||||
extern crate clap;
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
let mut git_human_readable = "v".to_owned() + crate_version!();
|
||||
if let Ok(git_out) = Command::new("git").args(&["describe", "--dirty"]).output() {
|
||||
if git_out.status.success() {
|
||||
if let Ok(git_out_str) = String::from_utf8(git_out.stdout) {
|
||||
git_human_readable = git_out_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This println!() has a special behavior, as it will set the environment
|
||||
// variable GIT_HUMAN_READABLE, so that it can be reused from the binary.
|
||||
// Particularly, this is used from the main.rs to display the exact
|
||||
// version information.
|
||||
println!("cargo:rustc-env=GIT_HUMAN_READABLE={}", git_human_readable);
|
||||
}
|
||||
474
performance-metrics/src/main.rs
Normal file
474
performance-metrics/src/main.rs
Normal file
@@ -0,0 +1,474 @@
|
||||
// Copyright © 2022 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// Custom harness to run performance tests
|
||||
extern crate test_infra;
|
||||
#[macro_use(crate_authors)]
|
||||
extern crate clap;
|
||||
|
||||
mod performance_tests;
|
||||
|
||||
use clap::{Arg, Command as ClapCommand};
|
||||
use performance_tests::*;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{env, fmt, process::Command, sync::mpsc::channel, thread, time::Duration};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum Error {
|
||||
#[error("Error: test timed-out")]
|
||||
TestTimeout,
|
||||
#[error("Error: test failed")]
|
||||
TestFailed,
|
||||
#[error("Error creating log file: {0}")]
|
||||
ReportFileCreation(std::io::Error),
|
||||
#[error("Error writing log file: {0}")]
|
||||
ReportFileWrite(std::io::Error),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct PerformanceTestResult {
|
||||
name: String,
|
||||
mean: f64,
|
||||
std_dev: f64,
|
||||
max: f64,
|
||||
min: f64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct MetricsReport {
|
||||
pub git_human_readable: String,
|
||||
pub git_revision: String,
|
||||
pub git_commit_date: String,
|
||||
pub date: String,
|
||||
pub results: Vec<PerformanceTestResult>,
|
||||
}
|
||||
|
||||
impl Default for MetricsReport {
|
||||
fn default() -> Self {
|
||||
let mut git_human_readable = "".to_string();
|
||||
if let Ok(git_out) = Command::new("git").args(&["describe", "--dirty"]).output() {
|
||||
if git_out.status.success() {
|
||||
if let Ok(git_out_str) = String::from_utf8(git_out.stdout) {
|
||||
git_human_readable = git_out_str.trim().to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut git_revision = "".to_string();
|
||||
if let Ok(git_out) = Command::new("git").args(&["rev-parse", "HEAD"]).output() {
|
||||
if git_out.status.success() {
|
||||
if let Ok(git_out_str) = String::from_utf8(git_out.stdout) {
|
||||
git_revision = git_out_str.trim().to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut git_commit_date = "".to_string();
|
||||
if let Ok(git_out) = Command::new("git")
|
||||
.args(&["show", "-s", "--format=%cd"])
|
||||
.output()
|
||||
{
|
||||
if git_out.status.success() {
|
||||
if let Ok(git_out_str) = String::from_utf8(git_out.stdout) {
|
||||
git_commit_date = git_out_str.trim().to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetricsReport {
|
||||
git_human_readable,
|
||||
git_revision,
|
||||
git_commit_date,
|
||||
date: date(),
|
||||
results: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PerformanceTestControl {
|
||||
test_timeout: u32,
|
||||
test_iterations: u32,
|
||||
num_queues: Option<u32>,
|
||||
queue_size: Option<u32>,
|
||||
net_rx: Option<bool>,
|
||||
fio_ops: Option<FioOps>,
|
||||
}
|
||||
|
||||
impl fmt::Display for PerformanceTestControl {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut output = format!(
|
||||
"test_timeout = {}s, test_iterations = {}",
|
||||
self.test_timeout, self.test_iterations
|
||||
);
|
||||
if let Some(o) = self.num_queues {
|
||||
output = format!("{}, num_queues = {}", output, o);
|
||||
}
|
||||
if let Some(o) = self.queue_size {
|
||||
output = format!("{}, queue_size = {}", output, o);
|
||||
}
|
||||
if let Some(o) = self.net_rx {
|
||||
output = format!("{}, net_rx = {}", output, o);
|
||||
}
|
||||
if let Some(o) = &self.fio_ops {
|
||||
output = format!("{}, fio_ops = {}", output, o);
|
||||
}
|
||||
|
||||
write!(f, "{}", output)
|
||||
}
|
||||
}
|
||||
|
||||
impl PerformanceTestControl {
|
||||
const fn default() -> Self {
|
||||
Self {
|
||||
test_timeout: 10,
|
||||
test_iterations: 5,
|
||||
num_queues: None,
|
||||
queue_size: None,
|
||||
net_rx: None,
|
||||
fio_ops: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A performance test should finish within the a certain time-out and
|
||||
/// return a performance metrics number (including the average number and
|
||||
/// standard deviation)
|
||||
struct PerformanceTest {
|
||||
pub name: &'static str,
|
||||
pub func_ptr: fn(&PerformanceTestControl) -> f64,
|
||||
pub control: PerformanceTestControl,
|
||||
}
|
||||
|
||||
impl PerformanceTest {
|
||||
pub fn run(&self) -> PerformanceTestResult {
|
||||
let mut metrics = Vec::new();
|
||||
for _ in 0..self.control.test_iterations {
|
||||
metrics.push((self.func_ptr)(&self.control));
|
||||
}
|
||||
|
||||
let mean = mean(&metrics).unwrap();
|
||||
let std_dev = std_deviation(&metrics).unwrap();
|
||||
let max = metrics.clone().into_iter().reduce(f64::max).unwrap();
|
||||
let min = metrics.clone().into_iter().reduce(f64::min).unwrap();
|
||||
|
||||
PerformanceTestResult {
|
||||
name: self.name.to_string(),
|
||||
mean,
|
||||
std_dev,
|
||||
max,
|
||||
min,
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the timeout for each test
|
||||
// Note: To cover the setup/cleanup time, 20s is added for each iteration of the test
|
||||
pub fn calc_timeout(&self) -> u64 {
|
||||
((self.control.test_timeout + 20) * self.control.test_iterations) as u64
|
||||
}
|
||||
}
|
||||
|
||||
fn mean(data: &[f64]) -> Option<f64> {
|
||||
let count = data.len();
|
||||
|
||||
if count > 0 {
|
||||
Some(data.iter().sum::<f64>() / count as f64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn std_deviation(data: &[f64]) -> Option<f64> {
|
||||
let count = data.len();
|
||||
|
||||
if count > 0 {
|
||||
let mean = mean(data).unwrap();
|
||||
let variance = data
|
||||
.iter()
|
||||
.map(|value| {
|
||||
let diff = mean - *value;
|
||||
diff * diff
|
||||
})
|
||||
.sum::<f64>()
|
||||
/ count as f64;
|
||||
|
||||
Some(variance.sqrt())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_LIST: [PerformanceTest; 15] = [
|
||||
PerformanceTest {
|
||||
name: "boot_time_s",
|
||||
func_ptr: performance_boot_time,
|
||||
control: PerformanceTestControl {
|
||||
test_timeout: 2,
|
||||
test_iterations: 10,
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "boot_time_pmem_s",
|
||||
func_ptr: performance_boot_time_pmem,
|
||||
control: PerformanceTestControl {
|
||||
test_timeout: 2,
|
||||
test_iterations: 10,
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "virtio_net_latency_us",
|
||||
func_ptr: performance_net_latency,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(2),
|
||||
queue_size: Some(256),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "virtio_net_throughput_single_queue_rx_bps",
|
||||
func_ptr: performance_net_throughput,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(2),
|
||||
queue_size: Some(256),
|
||||
net_rx: Some(true),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "virtio_net_throughput_single_queue_tx_bps",
|
||||
func_ptr: performance_net_throughput,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(2),
|
||||
queue_size: Some(256),
|
||||
net_rx: Some(false),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "virtio_net_throughput_multi_queue_rx_bps",
|
||||
func_ptr: performance_net_throughput,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(256),
|
||||
net_rx: Some(true),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "virtio_net_throughput_multi_queue_tx_bps",
|
||||
func_ptr: performance_net_throughput,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(4),
|
||||
queue_size: Some(256),
|
||||
net_rx: Some(false),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_read_Bps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
fio_ops: Some(FioOps::Read),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_write_Bps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
fio_ops: Some(FioOps::Write),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_random_read_Bps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
fio_ops: Some(FioOps::RandomRead),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_random_write_Bps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(1),
|
||||
queue_size: Some(128),
|
||||
fio_ops: Some(FioOps::RandomWrite),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_multi_queue_read_Bps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(2),
|
||||
queue_size: Some(128),
|
||||
fio_ops: Some(FioOps::Read),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_multi_queue_write_Bps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(2),
|
||||
queue_size: Some(128),
|
||||
fio_ops: Some(FioOps::Write),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_multi_queue_random_read_Bps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(2),
|
||||
queue_size: Some(128),
|
||||
fio_ops: Some(FioOps::RandomRead),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
},
|
||||
PerformanceTest {
|
||||
name: "block_multi_queue_random_write_Bps",
|
||||
func_ptr: performance_block_io,
|
||||
control: PerformanceTestControl {
|
||||
num_queues: Some(2),
|
||||
queue_size: Some(128),
|
||||
fio_ops: Some(FioOps::RandomWrite),
|
||||
..PerformanceTestControl::default()
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
fn run_test_with_timetout(test: &'static PerformanceTest) -> Result<PerformanceTestResult, Error> {
|
||||
let (sender, receiver) = channel::<Result<PerformanceTestResult, Error>>();
|
||||
thread::spawn(move || {
|
||||
println!("Test '{}' running .. ({})", test.name, test.control);
|
||||
|
||||
let output = match std::panic::catch_unwind(|| test.run()) {
|
||||
Ok(test_result) => {
|
||||
println!(
|
||||
"Test '{}' .. ok: mean = {}, std_dev = {}",
|
||||
test_result.name, test_result.mean, test_result.std_dev
|
||||
);
|
||||
Ok(test_result)
|
||||
}
|
||||
Err(_) => Err(Error::TestFailed),
|
||||
};
|
||||
|
||||
let _ = sender.send(output);
|
||||
});
|
||||
|
||||
// Todo: Need to cleanup/kill all hanging child processes
|
||||
let test_timeout = test.calc_timeout();
|
||||
receiver
|
||||
.recv_timeout(Duration::from_secs(test_timeout))
|
||||
.map_err(|_| {
|
||||
eprintln!(
|
||||
"[Error] Test '{}' time-out after {} seconds",
|
||||
test.name, test_timeout
|
||||
);
|
||||
Error::TestTimeout
|
||||
})?
|
||||
}
|
||||
|
||||
fn date() -> String {
|
||||
let output = test_infra::exec_host_command_output("date");
|
||||
String::from_utf8_lossy(&output.stdout).trim().to_string()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cmd_arguments = ClapCommand::new("performance-metrics")
|
||||
.version(env!("GIT_HUMAN_READABLE"))
|
||||
.author(crate_authors!())
|
||||
.about("Generate the performance metrics data for Cloud Hypervisor")
|
||||
.arg(
|
||||
Arg::new("test-filter")
|
||||
.long("test-filter")
|
||||
.help("Filter metrics tests to run based on provided keywords")
|
||||
.multiple_occurrences(true)
|
||||
.takes_value(true)
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("list-tests")
|
||||
.long("list-tests")
|
||||
.help("Print the list of availale metrics tests")
|
||||
.multiple_occurrences(true)
|
||||
.takes_value(false)
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("report-file")
|
||||
.long("report-file")
|
||||
.help("Report file. Standard error is used if not specified")
|
||||
.takes_value(true),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if cmd_arguments.is_present("list-tests") {
|
||||
println!("List of available metrics tests:\n");
|
||||
for test in TEST_LIST.iter() {
|
||||
println!("\"{}\" ({})", test.name, test.control);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let test_filter = match cmd_arguments.values_of("test-filter") {
|
||||
Some(s) => s.collect(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
|
||||
let mut report_file: Box<dyn std::io::Write + Send> =
|
||||
if let Some(file) = cmd_arguments.value_of("report-file") {
|
||||
Box::new(
|
||||
std::fs::File::create(std::path::Path::new(file))
|
||||
.map_err(Error::ReportFileCreation)
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
Box::new(std::io::stderr())
|
||||
};
|
||||
|
||||
// Run performance tests sequentially and report results (in both readable/json format)
|
||||
let mut metrics_report: MetricsReport = Default::default();
|
||||
|
||||
init_tests();
|
||||
|
||||
for test in TEST_LIST.iter() {
|
||||
if test_filter.is_empty() || test_filter.iter().any(|&s| test.name.contains(s)) {
|
||||
match run_test_with_timetout(test) {
|
||||
Ok(r) => {
|
||||
metrics_report.results.push(r);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Aborting test due to error: '{:?}'", e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
cleanup_tests();
|
||||
|
||||
// Todo: Report/upload to the metrics database
|
||||
report_file
|
||||
.write(
|
||||
serde_json::to_string_pretty(&metrics_report)
|
||||
.unwrap()
|
||||
.as_bytes(),
|
||||
)
|
||||
.map_err(Error::ReportFileWrite)
|
||||
.unwrap();
|
||||
}
|
||||
834
performance-metrics/src/performance_tests.rs
Normal file
834
performance-metrics/src/performance_tests.rs
Normal file
@@ -0,0 +1,834 @@
|
||||
// Copyright © 2022 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// Performance tests
|
||||
|
||||
use crate::{mean, PerformanceTestControl};
|
||||
use serde_json::Value;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::string::String;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::{fmt, fs};
|
||||
use test_infra::Error as InfraError;
|
||||
use test_infra::*;
|
||||
use wait_timeout::ChildExt;
|
||||
|
||||
pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-amd64-custom-20210609-0.raw";
|
||||
|
||||
#[derive(Debug)]
|
||||
enum WaitTimeoutError {
|
||||
Timedout,
|
||||
ExitStatus,
|
||||
General(std::io::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
BootTimeParse,
|
||||
EthrLogFile(std::io::Error),
|
||||
EthrLogParse,
|
||||
FioOutputParse,
|
||||
Iperf3Parse,
|
||||
Infra(InfraError),
|
||||
Spawn(std::io::Error),
|
||||
Scp(SshCommandError),
|
||||
WaitTimeout(WaitTimeoutError),
|
||||
}
|
||||
|
||||
impl From<InfraError> for Error {
|
||||
fn from(e: InfraError) -> Self {
|
||||
Self::Infra(e)
|
||||
}
|
||||
}
|
||||
|
||||
const BLK_IO_TEST_IMG: &str = "/var/tmp/ch-blk-io-test.img";
|
||||
|
||||
pub fn init_tests() {
|
||||
// The test image can not be created on tmpfs (e.g. /tmp) filesystem,
|
||||
// as tmpfs does not support O_DIRECT
|
||||
assert!(exec_host_command_output(&format!(
|
||||
"dd if=/dev/zero of={} bs=1M count=4096",
|
||||
BLK_IO_TEST_IMG
|
||||
))
|
||||
.status
|
||||
.success());
|
||||
}
|
||||
|
||||
pub fn cleanup_tests() {
|
||||
fs::remove_file(BLK_IO_TEST_IMG)
|
||||
.unwrap_or_else(|_| panic!("Failed to remove file '{}'.", BLK_IO_TEST_IMG));
|
||||
}
|
||||
|
||||
// Performance tests are expected to be executed sequentially, so we can
|
||||
// start VM guests with the same IP while putting them on a different
|
||||
// private network. The default constructor "Guest::new()" does not work
|
||||
// well, as we can easily create more than 256 VMs from repeating various
|
||||
// performance tests dozens times in a single run.
|
||||
fn performance_test_new_guest(disk_config: Box<dyn DiskConfig>) -> Guest {
|
||||
Guest::new_from_ip_range(disk_config, "172.19", 0)
|
||||
}
|
||||
|
||||
const DIRECT_KERNEL_BOOT_CMDLINE: &str =
|
||||
"root=/dev/vda1 console=hvc0 rw systemd.journald.forward_to_console=1";
|
||||
|
||||
// Creates the path for direct kernel boot and return the path.
|
||||
// For x86_64, this function returns the vmlinux kernel path.
|
||||
// For AArch64, this function returns the PE kernel path.
|
||||
fn direct_kernel_boot_path() -> PathBuf {
|
||||
let mut workload_path = dirs::home_dir().unwrap();
|
||||
workload_path.push("workloads");
|
||||
|
||||
let mut kernel_path = workload_path;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
kernel_path.push("vmlinux");
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
kernel_path.push("Image");
|
||||
|
||||
kernel_path
|
||||
}
|
||||
|
||||
// Wait the child process for a given timeout
|
||||
fn child_wait_timeout(child: &mut Child, timeout: u64) -> Result<(), WaitTimeoutError> {
|
||||
match child.wait_timeout(Duration::from_secs(timeout)) {
|
||||
Err(e) => {
|
||||
return Err(WaitTimeoutError::General(e));
|
||||
}
|
||||
Ok(s) => match s {
|
||||
None => {
|
||||
return Err(WaitTimeoutError::Timedout);
|
||||
}
|
||||
Some(s) => {
|
||||
if !s.success() {
|
||||
return Err(WaitTimeoutError::ExitStatus);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_iperf3_output(output: &[u8], sender: bool) -> Result<f64, Error> {
|
||||
std::panic::catch_unwind(|| {
|
||||
let s = String::from_utf8_lossy(output);
|
||||
let v: Value = serde_json::from_str(&s).expect("'iperf3' parse error: invalid json output");
|
||||
|
||||
let bps: f64 = if sender {
|
||||
v["end"]["sum_sent"]["bits_per_second"]
|
||||
.as_f64()
|
||||
.expect("'iperf3' parse error: missing entry 'end.sum_sent.bits_per_second'")
|
||||
} else {
|
||||
v["end"]["sum_received"]["bits_per_second"]
|
||||
.as_f64()
|
||||
.expect("'iperf3' parse error: missing entry 'end.sum_received.bits_per_second'")
|
||||
};
|
||||
|
||||
bps
|
||||
})
|
||||
.map_err(|_| {
|
||||
eprintln!(
|
||||
"=============== iperf3 output ===============\n\n{}\n\n===========end============\n\n",
|
||||
String::from_utf8_lossy(output)
|
||||
);
|
||||
Error::Iperf3Parse
|
||||
})
|
||||
}
|
||||
|
||||
fn measure_virtio_net_throughput(
|
||||
test_timeout: u32,
|
||||
queue_pairs: u32,
|
||||
guest: &Guest,
|
||||
receive: bool,
|
||||
) -> Result<f64, Error> {
|
||||
let default_port = 5201;
|
||||
|
||||
// 1. start the iperf3 server on the guest
|
||||
for n in 0..queue_pairs {
|
||||
guest
|
||||
.ssh_command(&format!("iperf3 -s -p {} -D", default_port + n))
|
||||
.map_err(InfraError::SshCommand)?;
|
||||
}
|
||||
|
||||
thread::sleep(Duration::new(1, 0));
|
||||
|
||||
// 2. start the iperf3 client on host to measure RX through-put
|
||||
let mut clients = Vec::new();
|
||||
for n in 0..queue_pairs {
|
||||
let mut cmd = Command::new("iperf3");
|
||||
cmd.args(&[
|
||||
"-J", // Output in JSON format
|
||||
"-c",
|
||||
&guest.network.guest_ip,
|
||||
"-p",
|
||||
&format!("{}", default_port + n),
|
||||
"-t",
|
||||
&format!("{}", test_timeout),
|
||||
]);
|
||||
// For measuring the guest transmit throughput (as a sender),
|
||||
// use reverse mode of the iperf3 client on the host
|
||||
if !receive {
|
||||
cmd.args(&["-R"]);
|
||||
}
|
||||
let client = cmd
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(Error::Spawn)?;
|
||||
|
||||
clients.push(client);
|
||||
}
|
||||
|
||||
let mut err: Option<Error> = None;
|
||||
let mut bps = Vec::new();
|
||||
let mut failed = false;
|
||||
for c in clients {
|
||||
let mut c = c;
|
||||
if let Err(e) = child_wait_timeout(&mut c, test_timeout as u64 + 5) {
|
||||
err = Some(Error::WaitTimeout(e));
|
||||
failed = true;
|
||||
}
|
||||
|
||||
if !failed {
|
||||
// Safe to unwrap as we know the child has terminated succesffully
|
||||
let output = c.wait_with_output().unwrap();
|
||||
bps.push(parse_iperf3_output(&output.stdout, receive)?);
|
||||
} else {
|
||||
let _ = c.kill();
|
||||
let output = c.wait_with_output().unwrap();
|
||||
println!(
|
||||
"=============== Client output [Error] ===============\n\n{}\n\n===========end============\n\n",
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(e) = err {
|
||||
Err(e)
|
||||
} else {
|
||||
Ok(bps.iter().sum())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 {
|
||||
let test_timeout = control.test_timeout;
|
||||
let rx = control.net_rx.unwrap();
|
||||
|
||||
let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
let guest = performance_test_new_guest(Box::new(focal));
|
||||
|
||||
let num_queues = control.num_queues.unwrap();
|
||||
let queue_size = control.queue_size.unwrap();
|
||||
let net_params = format!(
|
||||
"tap=,mac={},ip={},mask=255.255.255.0,num_queues={},queue_size={}",
|
||||
guest.network.guest_mac, guest.network.host_ip, num_queues, queue_size,
|
||||
);
|
||||
|
||||
let mut child = GuestCommand::new(&guest)
|
||||
.args(&["--cpus", &format!("boot={}", num_queues)])
|
||||
.args(&["--memory", "size=4G"])
|
||||
.args(&["--kernel", direct_kernel_boot_path().to_str().unwrap()])
|
||||
.args(&["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
|
||||
.default_disks()
|
||||
.args(&["--net", net_params.as_str()])
|
||||
.capture_output()
|
||||
.set_print_cmd(false)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
let r = std::panic::catch_unwind(|| {
|
||||
guest.wait_vm_boot(None).unwrap();
|
||||
measure_virtio_net_throughput(test_timeout, num_queues / 2, &guest, rx).unwrap()
|
||||
});
|
||||
|
||||
let _ = child.kill();
|
||||
let output = child.wait_with_output().unwrap();
|
||||
|
||||
match r {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
handle_child_output(Err(e), &output);
|
||||
panic!("test failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ethr_latency_output(output: &[u8]) -> Result<Vec<f64>, Error> {
|
||||
std::panic::catch_unwind(|| {
|
||||
let s = String::from_utf8_lossy(output);
|
||||
let mut latency = Vec::new();
|
||||
for l in s.lines() {
|
||||
let v: Value = serde_json::from_str(l).expect("'ethr' parse error: invalid json line");
|
||||
// Skip header/summary lines
|
||||
if let Some(avg) = v["Avg"].as_str() {
|
||||
// Assume the latency unit is always "us"
|
||||
latency.push(
|
||||
avg.split("us").collect::<Vec<&str>>()[0]
|
||||
.parse::<f64>()
|
||||
.expect("'ethr' parse error: invalid 'Avg' entry"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
assert!(
|
||||
!latency.is_empty(),
|
||||
"'ethr' parse error: no valid latency data found"
|
||||
);
|
||||
|
||||
latency
|
||||
})
|
||||
.map_err(|_| {
|
||||
eprintln!(
|
||||
"=============== ethr output ===============\n\n{}\n\n===========end============\n\n",
|
||||
String::from_utf8_lossy(output)
|
||||
);
|
||||
Error::EthrLogParse
|
||||
})
|
||||
}
|
||||
|
||||
fn measure_virtio_net_latency(guest: &Guest, test_timeout: u32) -> Result<Vec<f64>, Error> {
|
||||
// copy the 'ethr' tool to the guest image
|
||||
let ethr_path = "/usr/local/bin/ethr";
|
||||
let ethr_remote_path = "/tmp/ethr";
|
||||
scp_to_guest(
|
||||
Path::new(ethr_path),
|
||||
Path::new(ethr_remote_path),
|
||||
&guest.network.guest_ip,
|
||||
//DEFAULT_SSH_RETRIES,
|
||||
1,
|
||||
DEFAULT_SSH_TIMEOUT,
|
||||
)
|
||||
.map_err(Error::Scp)?;
|
||||
|
||||
// Start the ethr server on the guest
|
||||
guest
|
||||
.ssh_command(&format!("{} -s &> /dev/null &", ethr_remote_path))
|
||||
.map_err(InfraError::SshCommand)?;
|
||||
|
||||
thread::sleep(Duration::new(1, 0));
|
||||
|
||||
// Start the ethr client on the host
|
||||
let log_file = guest
|
||||
.tmp_dir
|
||||
.as_path()
|
||||
.join("ethr.client.log")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let mut c = Command::new(ethr_path)
|
||||
.args(&[
|
||||
"-c",
|
||||
&guest.network.guest_ip,
|
||||
"-t",
|
||||
"l",
|
||||
"-o",
|
||||
&log_file, // file output is JSON format
|
||||
"-d",
|
||||
&format!("{}s", test_timeout),
|
||||
])
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(Error::Spawn)?;
|
||||
|
||||
if let Err(e) = child_wait_timeout(&mut c, test_timeout as u64 + 5).map_err(Error::WaitTimeout)
|
||||
{
|
||||
let _ = c.kill();
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
// Parse the ethr latency test output
|
||||
let content = fs::read(log_file).map_err(Error::EthrLogFile)?;
|
||||
parse_ethr_latency_output(&content)
|
||||
}
|
||||
|
||||
pub fn performance_net_latency(control: &PerformanceTestControl) -> f64 {
|
||||
let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
let guest = performance_test_new_guest(Box::new(focal));
|
||||
|
||||
let num_queues = control.num_queues.unwrap();
|
||||
let queue_size = control.queue_size.unwrap();
|
||||
let net_params = format!(
|
||||
"tap=,mac={},ip={},mask=255.255.255.0,num_queues={},queue_size={}",
|
||||
guest.network.guest_mac, guest.network.host_ip, num_queues, queue_size,
|
||||
);
|
||||
|
||||
let mut child = GuestCommand::new(&guest)
|
||||
.args(&["--cpus", &format!("boot={}", num_queues)])
|
||||
.args(&["--memory", "size=4G"])
|
||||
.args(&["--kernel", direct_kernel_boot_path().to_str().unwrap()])
|
||||
.args(&["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
|
||||
.default_disks()
|
||||
.args(&["--net", net_params.as_str()])
|
||||
.capture_output()
|
||||
.set_print_cmd(false)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
let r = std::panic::catch_unwind(|| {
|
||||
guest.wait_vm_boot(None).unwrap();
|
||||
|
||||
// 'ethr' tool will measure the latency multiple times with provided test time
|
||||
let latency = measure_virtio_net_latency(&guest, control.test_timeout).unwrap();
|
||||
mean(&latency).unwrap()
|
||||
});
|
||||
|
||||
let _ = child.kill();
|
||||
let output = child.wait_with_output().unwrap();
|
||||
|
||||
match r {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
handle_child_output(Err(e), &output);
|
||||
panic!("test failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_boot_time_output(output: &[u8]) -> Result<f64, Error> {
|
||||
std::panic::catch_unwind(|| {
|
||||
let l: Vec<String> = String::from_utf8_lossy(output)
|
||||
.lines()
|
||||
.into_iter()
|
||||
.filter(|l| l.contains("Debug I/O port: Kernel code"))
|
||||
.map(|l| l.to_string())
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
l.len(),
|
||||
2,
|
||||
"Expecting two matching lines for 'Debug I/O port: Kernel code'"
|
||||
);
|
||||
|
||||
let time_stamp_kernel_start = {
|
||||
let s = l[0].split("--").collect::<Vec<&str>>();
|
||||
assert_eq!(
|
||||
s.len(),
|
||||
2,
|
||||
"Expecting '--' for the matching line of 'Debug I/O port' output"
|
||||
);
|
||||
|
||||
// Sample output: "[Debug I/O port: Kernel code 0x40] 0.096537 seconds"
|
||||
assert!(
|
||||
s[1].contains("0x40"),
|
||||
"Expecting kernel code '0x40' for 'linux_kernel_start' time stamp output"
|
||||
);
|
||||
let t = s[1].split_whitespace().collect::<Vec<&str>>();
|
||||
assert_eq!(
|
||||
t.len(),
|
||||
8,
|
||||
"Expecting exact '8' words from the 'Debug I/O port' output"
|
||||
);
|
||||
assert!(
|
||||
t[7].eq("seconds"),
|
||||
"Expecting 'seconds' as the the last word of the 'Debug I/O port' output"
|
||||
);
|
||||
|
||||
t[6].parse::<f64>().unwrap()
|
||||
};
|
||||
|
||||
let time_stamp_user_start = {
|
||||
let s = l[1].split("--").collect::<Vec<&str>>();
|
||||
assert_eq!(
|
||||
s.len(),
|
||||
2,
|
||||
"Expecting '--' for the matching line of 'Debug I/O port' output"
|
||||
);
|
||||
|
||||
// Sample output: "Debug I/O port: Kernel code 0x41] 0.198980 seconds"
|
||||
assert!(
|
||||
s[1].contains("0x41"),
|
||||
"Expecting kernel code '0x41' for 'linux_kernel_start' time stamp output"
|
||||
);
|
||||
let t = s[1].split_whitespace().collect::<Vec<&str>>();
|
||||
assert_eq!(
|
||||
t.len(),
|
||||
8,
|
||||
"Expecting exact '8' words from the 'Debug I/O port' output"
|
||||
);
|
||||
assert!(
|
||||
t[7].eq("seconds"),
|
||||
"Expecting 'seconds' as the the last word of the 'Debug I/O port' output"
|
||||
);
|
||||
|
||||
t[6].parse::<f64>().unwrap()
|
||||
};
|
||||
|
||||
time_stamp_user_start - time_stamp_kernel_start
|
||||
})
|
||||
.map_err(|_| {
|
||||
eprintln!(
|
||||
"=============== boot-time output ===============\n\n{}\n\n===========end============\n\n",
|
||||
String::from_utf8_lossy(output)
|
||||
);
|
||||
Error::BootTimeParse
|
||||
})
|
||||
}
|
||||
|
||||
fn measure_boot_time(cmd: &mut GuestCommand, test_timeout: u32) -> Result<f64, Error> {
|
||||
let mut child = cmd.capture_output().set_print_cmd(false).spawn().unwrap();
|
||||
|
||||
thread::sleep(Duration::new(test_timeout as u64, 0));
|
||||
let _ = child.kill();
|
||||
let output = child.wait_with_output().unwrap();
|
||||
|
||||
parse_boot_time_output(&output.stderr).map_err(|e| {
|
||||
eprintln!(
|
||||
"\n\n==== Start child stdout ====\n\n{}\n\n==== End child stdout ====",
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
);
|
||||
eprintln!(
|
||||
"\n\n==== Start child stderr ====\n\n{}\n\n==== End child stderr ====",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
pub fn performance_boot_time(control: &PerformanceTestControl) -> f64 {
|
||||
let r = std::panic::catch_unwind(|| {
|
||||
let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
let guest = performance_test_new_guest(Box::new(focal));
|
||||
let mut cmd = GuestCommand::new(&guest);
|
||||
|
||||
let c = cmd
|
||||
.args(&["--memory", "size=1G"])
|
||||
.args(&["--kernel", direct_kernel_boot_path().to_str().unwrap()])
|
||||
.args(&["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
|
||||
.args(&["--console", "off"])
|
||||
.default_disks();
|
||||
|
||||
measure_boot_time(c, control.test_timeout).unwrap()
|
||||
});
|
||||
|
||||
match r {
|
||||
Ok(r) => r,
|
||||
Err(_) => {
|
||||
panic!("test failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn performance_boot_time_pmem(control: &PerformanceTestControl) -> f64 {
|
||||
let r = std::panic::catch_unwind(|| {
|
||||
let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
let guest = performance_test_new_guest(Box::new(focal));
|
||||
let mut cmd = GuestCommand::new(&guest);
|
||||
let c = cmd
|
||||
.args(&["--memory", "size=1G,hugepages=on"])
|
||||
.args(&["--kernel", direct_kernel_boot_path().to_str().unwrap()])
|
||||
.args(&["--cmdline", "root=/dev/pmem0p1 console=ttyS0 quiet rw"])
|
||||
.args(&["--console", "off"])
|
||||
.args(&[
|
||||
"--pmem",
|
||||
format!(
|
||||
"file={}",
|
||||
guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
|
||||
)
|
||||
.as_str(),
|
||||
]);
|
||||
|
||||
measure_boot_time(c, control.test_timeout).unwrap()
|
||||
});
|
||||
|
||||
match r {
|
||||
Ok(r) => r,
|
||||
Err(_) => {
|
||||
panic!("test failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FioOps {
|
||||
Read,
|
||||
RandomRead,
|
||||
Write,
|
||||
RandomWrite,
|
||||
}
|
||||
|
||||
impl fmt::Display for FioOps {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
FioOps::Read => write!(f, "read"),
|
||||
FioOps::RandomRead => write!(f, "randread"),
|
||||
FioOps::Write => write!(f, "write"),
|
||||
FioOps::RandomWrite => write!(f, "randwrite"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_fio_output(output: &str, fio_ops: &FioOps, num_jobs: u32) -> Result<f64, Error> {
|
||||
std::panic::catch_unwind(|| {
|
||||
let v: Value =
|
||||
serde_json::from_str(output).expect("'fio' parse error: invalid json output");
|
||||
let jobs = v["jobs"]
|
||||
.as_array()
|
||||
.expect("'fio' parse error: missing entry 'jobs'");
|
||||
assert_eq!(
|
||||
jobs.len(),
|
||||
num_jobs as usize,
|
||||
"'fio' parse error: Unexpected number of 'fio' jobs."
|
||||
);
|
||||
|
||||
let read = match fio_ops {
|
||||
FioOps::Read | FioOps::RandomRead => true,
|
||||
FioOps::Write | FioOps::RandomWrite => false,
|
||||
};
|
||||
|
||||
let mut total_bps = 0_f64;
|
||||
for j in jobs {
|
||||
if read {
|
||||
let bytes = j["read"]["io_bytes"]
|
||||
.as_u64()
|
||||
.expect("'fio' parse error: missing entry 'read.io_bytes'");
|
||||
let runtime = j["read"]["runtime"]
|
||||
.as_u64()
|
||||
.expect("'fio' parse error: missing entry 'read.runtime'")
|
||||
as f64
|
||||
/ 1000_f64;
|
||||
total_bps += bytes as f64 / runtime as f64;
|
||||
} else {
|
||||
let bytes = j["write"]["io_bytes"]
|
||||
.as_u64()
|
||||
.expect("'fio' parse error: missing entry 'write.io_bytes'");
|
||||
let runtime = j["write"]["runtime"]
|
||||
.as_u64()
|
||||
.expect("'fio' parse error: missing entry 'write.runtime'")
|
||||
as f64
|
||||
/ 1000_f64;
|
||||
total_bps += bytes as f64 / runtime as f64;
|
||||
}
|
||||
}
|
||||
|
||||
total_bps
|
||||
})
|
||||
.map_err(|_| {
|
||||
eprintln!(
|
||||
"=============== Fio output ===============\n\n{}\n\n===========end============\n\n",
|
||||
output
|
||||
);
|
||||
Error::FioOutputParse
|
||||
})
|
||||
}
|
||||
|
||||
pub fn performance_block_io(control: &PerformanceTestControl) -> f64 {
|
||||
let test_timeout = control.test_timeout;
|
||||
let num_queues = control.num_queues.unwrap();
|
||||
let queue_size = control.queue_size.unwrap();
|
||||
let fio_ops = control.fio_ops.as_ref().unwrap();
|
||||
|
||||
let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
let guest = performance_test_new_guest(Box::new(focal));
|
||||
let api_socket = guest
|
||||
.tmp_dir
|
||||
.as_path()
|
||||
.join("cloud-hypervisor.sock")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let mut child = GuestCommand::new(&guest)
|
||||
.args(&["--cpus", &format!("boot={}", num_queues)])
|
||||
.args(&["--memory", "size=4G"])
|
||||
.args(&["--kernel", direct_kernel_boot_path().to_str().unwrap()])
|
||||
.args(&["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
|
||||
.default_disks()
|
||||
.default_net()
|
||||
.args(&["--api-socket", &api_socket])
|
||||
.capture_output()
|
||||
.set_print_cmd(false)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
let r = std::panic::catch_unwind(|| {
|
||||
guest.wait_vm_boot(None).unwrap();
|
||||
|
||||
// Hotplug test disk
|
||||
assert!(Command::new(clh_command("ch-remote"))
|
||||
.args(&[&format!("--api-socket={}", api_socket)])
|
||||
.args(&[
|
||||
"add-disk",
|
||||
&format!(
|
||||
"path={},num_queues={},queue_size={},direct=on",
|
||||
BLK_IO_TEST_IMG, num_queues, queue_size
|
||||
)
|
||||
])
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait_timeout(Duration::from_secs(5))
|
||||
.unwrap()
|
||||
.expect("Failed to hotplug test disk image")
|
||||
.success());
|
||||
|
||||
let fio_command = format!(
|
||||
"sudo fio --filename=/dev/vdc --name=test --output-format=json \
|
||||
--direct=1 --bs=4k --ioengine=io_uring --iodepth=64 \
|
||||
--rw={} --runtime={} --numjobs={}",
|
||||
fio_ops, test_timeout, num_queues
|
||||
);
|
||||
let output = guest
|
||||
.ssh_command(&fio_command)
|
||||
.map_err(InfraError::SshCommand)
|
||||
.unwrap();
|
||||
|
||||
// Parse fio output
|
||||
parse_fio_output(&output, fio_ops, num_queues).unwrap()
|
||||
});
|
||||
|
||||
let _ = child.kill();
|
||||
let output = child.wait_with_output().unwrap();
|
||||
|
||||
match r {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
handle_child_output(Err(e), &output);
|
||||
panic!("test failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_iperf3_output() {
|
||||
let output = r#"
|
||||
{
|
||||
"end": {
|
||||
"sum_sent": {
|
||||
"start": 0,
|
||||
"end": 5.000196,
|
||||
"seconds": 5.000196,
|
||||
"bytes": 14973836248,
|
||||
"bits_per_second": 23957198874.604115,
|
||||
"retransmits": 0,
|
||||
"sender": false
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
assert_eq!(
|
||||
parse_iperf3_output(output.as_bytes(), true).unwrap(),
|
||||
23957198874.604115
|
||||
);
|
||||
|
||||
let output = r#"
|
||||
{
|
||||
"end": {
|
||||
"sum_received": {
|
||||
"start": 0,
|
||||
"end": 5.000626,
|
||||
"seconds": 5.000626,
|
||||
"bytes": 24703557800,
|
||||
"bits_per_second": 39520744482.79,
|
||||
"sender": true
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
assert_eq!(
|
||||
parse_iperf3_output(output.as_bytes(), false).unwrap(),
|
||||
39520744482.79
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_ethr_latency_output() {
|
||||
let output = r#"{"Time":"2022-02-08T03:52:50Z","Title":"","Type":"INFO","Message":"Using destination: 192.168.249.2, ip: 192.168.249.2, port: 8888"}
|
||||
{"Time":"2022-02-08T03:52:51Z","Title":"","Type":"INFO","Message":"Running latency test: 1000, 1"}
|
||||
{"Time":"2022-02-08T03:52:51Z","Title":"","Type":"LatencyResult","RemoteAddr":"192.168.249.2","Protocol":"TCP","Avg":"80.712us","Min":"61.677us","P50":"257.014us","P90":"74.418us","P95":"107.283us","P99":"119.309us","P999":"142.100us","P9999":"216.341us","Max":"216.341us"}
|
||||
{"Time":"2022-02-08T03:52:52Z","Title":"","Type":"LatencyResult","RemoteAddr":"192.168.249.2","Protocol":"TCP","Avg":"79.826us","Min":"55.129us","P50":"598.996us","P90":"73.849us","P95":"106.552us","P99":"122.152us","P999":"142.459us","P9999":"474.280us","Max":"474.280us"}
|
||||
{"Time":"2022-02-08T03:52:53Z","Title":"","Type":"LatencyResult","RemoteAddr":"192.168.249.2","Protocol":"TCP","Avg":"78.239us","Min":"56.999us","P50":"396.820us","P90":"69.469us","P95":"115.421us","P99":"119.404us","P999":"130.158us","P9999":"258.686us","Max":"258.686us"}"#;
|
||||
|
||||
let ret = parse_ethr_latency_output(output.as_bytes()).unwrap();
|
||||
let reference = vec![80.712_f64, 79.826_f64, 78.239_f64];
|
||||
assert_eq!(ret, reference);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_boot_time_output() {
|
||||
let output = r#"
|
||||
cloud-hypervisor: 161.167103ms: <vcpu0> INFO:vmm/src/vm.rs:392 -- [Debug I/O port: Kernel code 0x40] 0.132 seconds
|
||||
cloud-hypervisor: 613.57361ms: <vcpu0> INFO:vmm/src/vm.rs:392 -- [Debug I/O port: Kernel code 0x41] 0.5845 seconds
|
||||
"#;
|
||||
|
||||
assert_eq!(parse_boot_time_output(output.as_bytes()).unwrap(), 0.4525);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fio_output() {
|
||||
let output = r#"
|
||||
{
|
||||
"jobs" : [
|
||||
{
|
||||
"read" : {
|
||||
"io_bytes" : 1965273088,
|
||||
"io_kbytes" : 1919212,
|
||||
"bw_bytes" : 392976022,
|
||||
"bw" : 383765,
|
||||
"iops" : 95941.411718,
|
||||
"runtime" : 5001,
|
||||
"total_ios" : 479803,
|
||||
"short_ios" : 0,
|
||||
"drop_ios" : 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"#;
|
||||
|
||||
let bps = 1965273088_f64 / (5001_f64 / 1000_f64);
|
||||
assert_eq!(
|
||||
parse_fio_output(output, &FioOps::RandomRead, 1).unwrap(),
|
||||
bps
|
||||
);
|
||||
assert_eq!(parse_fio_output(output, &FioOps::Read, 1).unwrap(), bps);
|
||||
|
||||
let output = r#"
|
||||
{
|
||||
"jobs" : [
|
||||
{
|
||||
"write" : {
|
||||
"io_bytes" : 1172783104,
|
||||
"io_kbytes" : 1145296,
|
||||
"bw_bytes" : 234462835,
|
||||
"bw" : 228967,
|
||||
"iops" : 57241.903239,
|
||||
"runtime" : 5002,
|
||||
"total_ios" : 286324,
|
||||
"short_ios" : 0,
|
||||
"drop_ios" : 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"write" : {
|
||||
"io_bytes" : 1172234240,
|
||||
"io_kbytes" : 1144760,
|
||||
"bw_bytes" : 234353106,
|
||||
"bw" : 228860,
|
||||
"iops" : 57215.113954,
|
||||
"runtime" : 5002,
|
||||
"total_ios" : 286190,
|
||||
"short_ios" : 0,
|
||||
"drop_ios" : 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"#;
|
||||
|
||||
let bps = 1172783104_f64 / (5002_f64 / 1000_f64) + 1172234240_f64 / (5002_f64 / 1000_f64);
|
||||
assert_eq!(
|
||||
parse_fio_output(output, &FioOps::RandomWrite, 2).unwrap(),
|
||||
bps
|
||||
);
|
||||
assert_eq!(parse_fio_output(output, &FioOps::Write, 2).unwrap(), bps);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ path = "src/qcow.rs"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.4.3"
|
||||
libc = "0.2.112"
|
||||
libc = "0.2.119"
|
||||
log = "0.4.14"
|
||||
remain = "0.2.2"
|
||||
vmm-sys-util = "0.9.0"
|
||||
|
||||
@@ -4,6 +4,6 @@ version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.112"
|
||||
libc = "0.2.119"
|
||||
log = "0.4.14"
|
||||
vmm-sys-util = "0.9.0"
|
||||
|
||||
168
release-notes.md
168
release-notes.md
@@ -1,9 +1,20 @@
|
||||
- [v21.1](#v211)
|
||||
- [v22.1](#v221)
|
||||
- [v22.0](#v220)
|
||||
- [GDB Debug Stub Support](#gdb-debug-stub-support)
|
||||
- [`virtio-iommu` Backed Segments](#virtio-iommu-backed-segments)
|
||||
- [Before Boot Configuration Changes](#before-boot-configuration-changes)
|
||||
- [`virtio-balloon` Free Page Reporting](#virtio-balloon-free-page-reporting)
|
||||
- [Support for Direct Kernel Booting with TDX](#support-for-direct-kernel-booting-with-tdx)
|
||||
- [PMU Support for AArch64](#pmu-support-for-aarch64)
|
||||
- [Documentation Under CC-BY-4.0 License](#documentation-under-cc-by-40-license)
|
||||
- [Deprecation of "Classic" `virtiofsd`](#deprecation-of-classic-virtiofsd)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes)
|
||||
- [Contributors](#contributors)
|
||||
- [v21.0](#v210)
|
||||
- [Efficient Local Live Migration (for Live Upgrade)](#efficient-local-live-migration-for-live-upgrade)
|
||||
- [Recommended Kernel is Now 5.15](#recommended-kernel-is-now-515)
|
||||
- [Notable Bug fixes](#notable-bug-fixes)
|
||||
- [Contributors](#contributors)
|
||||
- [Notable Bug fixes](#notable-bug-fixes-1)
|
||||
- [Contributors](#contributors-1)
|
||||
- [v20.2](#v202)
|
||||
- [v20.1](#v201)
|
||||
- [v20.0](#v200)
|
||||
@@ -12,8 +23,8 @@
|
||||
- [Improved VFIO support](#improved-vfio-support)
|
||||
- [Safer code](#safer-code)
|
||||
- [Extended documentation](#extended-documentation)
|
||||
- [Notable bug fixes](#notable-bug-fixes-1)
|
||||
- [Contributors](#contributors-1)
|
||||
- [Notable bug fixes](#notable-bug-fixes-2)
|
||||
- [Contributors](#contributors-2)
|
||||
- [v19.0](#v190)
|
||||
- [Improved PTY handling for serial and `virtio-console`](#improved-pty-handling-for-serial-and-virtio-console)
|
||||
- [PCI boot time optimisations](#pci-boot-time-optimisations)
|
||||
@@ -21,8 +32,8 @@
|
||||
- [Live migration enhancements](#live-migration-enhancements)
|
||||
- [`virtio-mem` support with `vfio-user`](#virtio-mem-support-with-vfio-user)
|
||||
- [AArch64 for `virtio-iommu`](#aarch64-for-virtio-iommu)
|
||||
- [Notable bug fixes](#notable-bug-fixes-2)
|
||||
- [Contributors](#contributors-2)
|
||||
- [Notable bug fixes](#notable-bug-fixes-3)
|
||||
- [Contributors](#contributors-3)
|
||||
- [v18.0](#v180)
|
||||
- [Experimental User Device (`vfio-user`) support](#experimental-user-device-vfio-user-support)
|
||||
- [Migration support for `vhost-user` devices](#migration-support-for-vhost-user-devices)
|
||||
@@ -32,23 +43,23 @@
|
||||
- [Live migration on MSHV hypervisor](#live-migration-on-mshv-hypervisor)
|
||||
- [AArch64 CPU topology support](#aarch64-cpu-topology-support)
|
||||
- [Power button support on AArch64](#power-button-support-on-aarch64)
|
||||
- [Notable bug fixes](#notable-bug-fixes-3)
|
||||
- [Contributors](#contributors-3)
|
||||
- [Notable bug fixes](#notable-bug-fixes-4)
|
||||
- [Contributors](#contributors-4)
|
||||
- [v17.0](#v170)
|
||||
- [ARM64 NUMA support using ACPI](#arm64-numa-support-using-acpi)
|
||||
- [`Seccomp` support for MSHV backend](#seccomp-support-for-mshv-backend)
|
||||
- [Hotplug of `macvtap` devices](#hotplug-of-macvtap-devices)
|
||||
- [Improved SGX support](#improved-sgx-support)
|
||||
- [Inflight tracking for `vhost-user` devices](#inflight-tracking-for-vhost-user-devices)
|
||||
- [Notable bug fixes](#notable-bug-fixes-4)
|
||||
- [Contributors](#contributors-4)
|
||||
- [Notable bug fixes](#notable-bug-fixes-5)
|
||||
- [Contributors](#contributors-5)
|
||||
- [v16.0](#v160)
|
||||
- [Improved live migration support](#improved-live-migration-support)
|
||||
- [Improved `vhost-user` support](#improved-vhost-user-support)
|
||||
- [ARM64 ACPI and UEFI support](#arm64-acpi-and-uefi-support)
|
||||
- [Notable bug fixes](#notable-bug-fixes-5)
|
||||
- [Notable bug fixes](#notable-bug-fixes-6)
|
||||
- [Removed functionality](#removed-functionality)
|
||||
- [Contributors](#contributors-5)
|
||||
- [Contributors](#contributors-6)
|
||||
- [v15.0](#v150)
|
||||
- [Version numbering and stability guarantees](#version-numbering-and-stability-guarantees)
|
||||
- [Network device rate limiting](#network-device-rate-limiting)
|
||||
@@ -56,7 +67,7 @@
|
||||
- [`--api-socket` supports file descriptor parameter](#--api-socket-supports-file-descriptor-parameter)
|
||||
- [Bug fixes](#bug-fixes)
|
||||
- [Deprecations](#deprecations)
|
||||
- [Contributors](#contributors-6)
|
||||
- [Contributors](#contributors-7)
|
||||
- [v0.14.1](#v0141)
|
||||
- [v0.14.0](#v0140)
|
||||
- [Structured event monitoring](#structured-event-monitoring)
|
||||
@@ -66,7 +77,7 @@
|
||||
- [PTY control for serial and `virtio-console`](#pty-control-for-serial-and-virtio-console)
|
||||
- [Block device rate limiting](#block-device-rate-limiting)
|
||||
- [Deprecations](#deprecations-1)
|
||||
- [Contributors](#contributors-7)
|
||||
- [Contributors](#contributors-8)
|
||||
- [v0.13.0](#v0130)
|
||||
- [Wider VFIO device support](#wider-vfio-device-support)
|
||||
- [Improved huge page support](#improved-huge-page-support)
|
||||
@@ -74,13 +85,13 @@
|
||||
- [VHD disk image support](#vhd-disk-image-support)
|
||||
- [Improved Virtio device threading](#improved-virtio-device-threading)
|
||||
- [Clean shutdown support via synthetic power button](#clean-shutdown-support-via-synthetic-power-button)
|
||||
- [Contributors](#contributors-8)
|
||||
- [Contributors](#contributors-9)
|
||||
- [v0.12.0](#v0120)
|
||||
- [ARM64 enhancements](#arm64-enhancements)
|
||||
- [Removal of `vhost-user-net` and `vhost-user-block` self spawning](#removal-of-vhost-user-net-and-vhost-user-block-self-spawning)
|
||||
- [Migration of `vhost-user-fs` backend](#migration-of-vhost-user-fs-backend)
|
||||
- [Enhanced "info" API](#enhanced-info-api)
|
||||
- [Contributors](#contributors-9)
|
||||
- [Contributors](#contributors-10)
|
||||
- [v0.11.0](#v0110)
|
||||
- [`io_uring` support by default for `virtio-block`](#io_uring-support-by-default-for-virtio-block)
|
||||
- [Windows Guest Support](#windows-guest-support)
|
||||
@@ -92,15 +103,15 @@
|
||||
- [Default Log Level Changed](#default-log-level-changed)
|
||||
- [New `--balloon` Parameter Added](#new---balloon-parameter-added)
|
||||
- [Experimental `virtio-watchdog` Support](#experimental-virtio-watchdog-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-6)
|
||||
- [Contributors](#contributors-10)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-7)
|
||||
- [Contributors](#contributors-11)
|
||||
- [v0.10.0](#v0100)
|
||||
- [`virtio-block` Support for Multiple Descriptors](#virtio-block-support-for-multiple-descriptors)
|
||||
- [Memory Zones](#memory-zones)
|
||||
- [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements)
|
||||
- [Preliminary KVM HyperV Emulation Control](#preliminary-kvm-hyperv-emulation-control)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-7)
|
||||
- [Contributors](#contributors-11)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-8)
|
||||
- [Contributors](#contributors-12)
|
||||
- [v0.9.0](#v090)
|
||||
- [`io_uring` Based Block Device Support](#io_uring-based-block-device-support)
|
||||
- [Block and Network Device Statistics](#block-and-network-device-statistics)
|
||||
@@ -113,17 +124,17 @@
|
||||
- [Enhancements to ARM64 Support](#enhancements-to-arm64-support)
|
||||
- [Intel SGX Support](#intel-sgx-support)
|
||||
- [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements-1)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-8)
|
||||
- [Contributors](#contributors-12)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-9)
|
||||
- [Contributors](#contributors-13)
|
||||
- [v0.8.0](#v080)
|
||||
- [Experimental Snapshot and Restore Support](#experimental-snapshot-and-restore-support)
|
||||
- [Experimental ARM64 Support](#experimental-arm64-support)
|
||||
- [Support for Using 5-level Paging in Guests](#support-for-using-5-level-paging-in-guests)
|
||||
- [Virtio Device Interrupt Suppression for Network Devices](#virtio-device-interrupt-suppression-for-network-devices)
|
||||
- [`vhost_user_fs` Improvements](#vhost_user_fs-improvements)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-9)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-10)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes)
|
||||
- [Contributors](#contributors-13)
|
||||
- [Contributors](#contributors-14)
|
||||
- [v0.7.0](#v070)
|
||||
- [Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug](#block-network-persistent-memory-pmem-virtiofs-and-vsock-hotplug)
|
||||
- [Alternative `libc` Support](#alternative-libc-support)
|
||||
@@ -133,14 +144,14 @@
|
||||
- [`Seccomp` Sandboxing](#seccomp-sandboxing)
|
||||
- [Updated Distribution Support](#updated-distribution-support)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes-1)
|
||||
- [Contributors](#contributors-14)
|
||||
- [Contributors](#contributors-15)
|
||||
- [v0.6.0](#v060)
|
||||
- [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug)
|
||||
- [Shared Filesystem Improvements](#shared-filesystem-improvements)
|
||||
- [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading)
|
||||
- [Command Line Interface](#command-line-interface)
|
||||
- [PVH Boot](#pvh-boot)
|
||||
- [Contributors](#contributors-15)
|
||||
- [Contributors](#contributors-16)
|
||||
- [v0.5.1](#v051)
|
||||
- [v0.5.0](#v050)
|
||||
- [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing)
|
||||
@@ -148,7 +159,7 @@
|
||||
- [New Interrupt Management Framework](#new-interrupt-management-framework)
|
||||
- [Development Tools](#development-tools)
|
||||
- [Kata Containers Integration](#kata-containers-integration)
|
||||
- [Contributors](#contributors-16)
|
||||
- [Contributors](#contributors-17)
|
||||
- [v0.4.0](#v040)
|
||||
- [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition)
|
||||
- [Programmatic firmware tables generation](#programmatic-firmware-tables-generation)
|
||||
@@ -157,7 +168,7 @@
|
||||
- [Userspace IOAPIC by default](#userspace-ioapic-by-default)
|
||||
- [PCI BAR reprogramming](#pci-bar-reprogramming)
|
||||
- [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization)
|
||||
- [Contributors](#contributors-17)
|
||||
- [Contributors](#contributors-18)
|
||||
- [v0.3.0](#v030)
|
||||
- [Block device offloading](#block-device-offloading)
|
||||
- [Network device backend](#network-device-backend)
|
||||
@@ -184,16 +195,98 @@
|
||||
- [Unit testing](#unit-testing)
|
||||
- [Integration tests parallelization](#integration-tests-parallelization)
|
||||
|
||||
# v21.1
|
||||
# v22.1
|
||||
|
||||
This is a bug fix release. The following issues have been addressed:
|
||||
|
||||
* Missing `openat()` syscall from seccomp filter (#3609)
|
||||
* Ensure MMIO/PIO exits complete before pausing (#3658)
|
||||
* Support DWORD writes to MSI-X control register (#3714)
|
||||
* VFIO ioctl reordering to fix MSI on AMD platforms (#3827)
|
||||
* Fix `virtio-net` control queue (#3829)
|
||||
|
||||
# v22.0
|
||||
|
||||
This release has been tracked through the [v22.0
|
||||
project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/25).
|
||||
|
||||
### GDB Debug Stub Support
|
||||
|
||||
Cloud Hypervisor can now be used as debug target with GDB. This is controlled
|
||||
by the `gdb` compile time feature and details of how to use it can be found in
|
||||
the [gdb
|
||||
documentation](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/main/docs/gdb.md).
|
||||
|
||||
|
||||
### `virtio-iommu` Backed Segments
|
||||
|
||||
In order to facilitate hotplug devices that require being behind an IOMMU (e.g.
|
||||
QAT) there is a new option `--platform iommu_segments=<list_of_segments>` that
|
||||
will place all the specified segments behind the IOMMU.
|
||||
|
||||
### Before Boot Configuration Changes
|
||||
|
||||
It is now possible to change the VM configuration (e.g. add or remove devices,
|
||||
resize) before the VM is booted.
|
||||
|
||||
|
||||
### `virtio-balloon` Free Page Reporting
|
||||
|
||||
If `--balloon free_page_reporting=on` is used then the guest can report pages
|
||||
that is it not using to the VMM. The VMM will then notify the host OS that
|
||||
those pages are no longer in use and can be freed. This can result in improved
|
||||
memory density.
|
||||
|
||||
### Support for Direct Kernel Booting with TDX
|
||||
|
||||
Through the use of `TD-Shim` lightweight firmware it is now possible to
|
||||
directly boot into the kernel with TDX. The [TDX
|
||||
documentation](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/main/docs/intel_tdx.md#tdshim)
|
||||
has been updated for this usage.
|
||||
|
||||
### PMU Support for AArch64
|
||||
|
||||
A PMU is now available on AArch64 for guest performance profiling. This will be
|
||||
exposed automatically if available from the host.
|
||||
|
||||
### Documentation Under CC-BY-4.0 License
|
||||
|
||||
The documentation is now licensed under the "Creative Commons Attribution 4.0
|
||||
International" license which is aligned with the project charter under the
|
||||
Linux Foundation.
|
||||
|
||||
### Deprecation of "Classic" `virtiofsd`
|
||||
|
||||
The use of the Rust based [virtiofsd](https://gitlab.com/virtio-fs/virtiofsd)
|
||||
is now recommended and we are no longer testing against the C based "classic"
|
||||
version.
|
||||
|
||||
### Notable Bug Fixes
|
||||
|
||||
* Can now be used on kernels without `AF_INET` support (#3785)
|
||||
* `virtio-balloon` size is now validated against guest RAM size (#3689)
|
||||
* Ensure that I/O related KVM VM Exits are correctly handled (#3677)
|
||||
* Multiple TAP file descriptors can be used for `virtio-net` device hotplug (#3607)
|
||||
* Minor API improvements and fixes (#3756, #3766, #3647, #3578)
|
||||
* Fix sporadic seccomp violation from glibc memory freeing (#3610, #3609)
|
||||
* Fix Windows 11 on AArch64 due to wider MSI-X register accesses (#3714, #3720)
|
||||
* Ensure `vhost-user` features are correct across migration (#3737)
|
||||
* Improved vCPU topology on AArch64 (#3735, #3733)
|
||||
|
||||
### Contributors
|
||||
|
||||
Many thanks to everyone who has contributed to our release:
|
||||
|
||||
* Akira Moroo <retrage01@gmail.com>
|
||||
* Barret Rhoden <brho@google.com>
|
||||
* Bo Chen <chen.bo@intel.com>
|
||||
* Fabiano Fidêncio <fabiano.fidencio@intel.com>
|
||||
* Feng Ye <yefeng@smartx.com>
|
||||
* Henry Wang <Henry.Wang@arm.com>
|
||||
* Jianyong Wu <jianyong.wu@arm.com>
|
||||
* lizhaoxin1 <Lxiaoyouling@163.com>
|
||||
* Michael Zhao <michael.zhao@arm.com>
|
||||
* Rob Bradford <robert.bradford@intel.com>
|
||||
* Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
* Wei Liu <liuwe@microsoft.com>
|
||||
|
||||
# v21.0
|
||||
|
||||
This release has been tracked through the [v21.0
|
||||
@@ -201,11 +294,16 @@ project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/24).
|
||||
|
||||
### Efficient Local Live Migration (for Live Upgrade)
|
||||
|
||||
In order to support fast live upgrade of the VMM an optimised path has been added in which the memory for the VM is not compared from source to destination. This is activated by passing `--local` to the `ch-remote send-migration` command. This means that the live upgrade can complete in the order of 50ms vs 3s. (#3566)
|
||||
In order to support fast live upgrade of the VMM an optimised path has been
|
||||
added in which the memory for the VM is not compared from source to
|
||||
destination. This is activated by passing `--local` to the `ch-remote
|
||||
send-migration` command. This means that the live upgrade can complete in the
|
||||
order of 50ms vs 3s. (#3566)
|
||||
|
||||
### Recommended Kernel is Now 5.15
|
||||
|
||||
Due to an issue in the `virtio-net` code in 5.14 the recommended Linux kernel is now 5.15. (#3530)
|
||||
Due to an issue in the `virtio-net` code in 5.14 the recommended Linux kernel
|
||||
is now 5.15. (#3530)
|
||||
|
||||
### Notable Bug fixes
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
# When changing this file don't forget to update the tag name in the
|
||||
# .github/workflows/docker-image.yaml file if doing multiple per day
|
||||
|
||||
FROM ubuntu:20.04 as dev
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG RUST_TOOLCHAIN="1.58.0"
|
||||
ARG RUST_TOOLCHAIN="1.59.0"
|
||||
ARG CLH_SRC_DIR="/cloud-hypervisor"
|
||||
ARG CLH_BUILD_DIR="$CLH_SRC_DIR/build"
|
||||
ARG CARGO_REGISTRY_DIR="$CLH_BUILD_DIR/cargo_registry"
|
||||
@@ -17,7 +20,6 @@ RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
||||
build-essential \
|
||||
bc \
|
||||
docker.io \
|
||||
curl \
|
||||
wget \
|
||||
sudo \
|
||||
@@ -29,7 +31,6 @@ RUN apt-get update \
|
||||
bison \
|
||||
libelf-dev \
|
||||
qemu-utils \
|
||||
qemu-system \
|
||||
libglib2.0-dev \
|
||||
libpixman-1-dev \
|
||||
libseccomp-dev \
|
||||
@@ -44,6 +45,11 @@ RUN apt-get update \
|
||||
openvswitch-switch-dpdk \
|
||||
python3-distutils \
|
||||
uuid-dev \
|
||||
iperf3 \
|
||||
zip \
|
||||
git-core \
|
||||
dnsmasq \
|
||||
dmsetup \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -66,7 +72,7 @@ RUN if [ "$TARGETARCH" = "arm64" ]; then \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
||||
libcap2-bin \
|
||||
libguestfs-tools \
|
||||
linux-image-5.8.0-63-generic \
|
||||
linux-image-generic \
|
||||
autotools-dev \
|
||||
autoconf \
|
||||
automake \
|
||||
@@ -86,12 +92,11 @@ ENV OPENSSL_INCLUDE_DIR=/usr/include/
|
||||
|
||||
# Install the rust toolchain
|
||||
RUN export ARCH="$(uname -m)" \
|
||||
&& nohup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN" \
|
||||
&& nohup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain "$RUST_TOOLCHAIN" \
|
||||
&& rustup target add $ARCH-unknown-linux-musl --toolchain "$RUST_TOOLCHAIN" \
|
||||
&& if [ "$TARGETARCH" = "amd64" ]; then rustup toolchain add $RUST_TOOLCHAIN-x86_64-unknown-linux-musl; fi \
|
||||
&& if [ "$TARGETARCH" = "amd64" ]; then rustup toolchain add --profile minimal $RUST_TOOLCHAIN-x86_64-unknown-linux-musl; fi \
|
||||
&& if [ "$TARGETARCH" = "amd64" ]; then rustup component add rustfmt; fi \
|
||||
&& if [ "$TARGETARCH" = "amd64" ]; then rustup component add clippy; fi \
|
||||
&& cargo install cargo-audit \
|
||||
&& rm -rf "$CARGO_HOME/registry" \
|
||||
&& ln -s "$CARGO_REGISTRY_DIR" "$CARGO_HOME/registry" \
|
||||
&& rm -rf "$CARGO_HOME/git" \
|
||||
@@ -102,14 +107,6 @@ RUN echo 'source $CARGO_HOME/env' >> $HOME/.bashrc \
|
||||
&& mkdir $HOME/.cargo \
|
||||
&& ln -s $CARGO_HOME/env $HOME/.cargo/env
|
||||
|
||||
# install virtiofsd
|
||||
RUN git clone --depth 1 https://gitlab.com/virtio-fs/qemu.git -b qemu5.0-virtiofs-dax \
|
||||
&& cd qemu \
|
||||
&& ./configure --prefix=$PWD --target-list=x86_64-softmmu \
|
||||
&& make virtiofsd -j `nproc` \
|
||||
&& cp virtiofsd /usr/local/bin \
|
||||
&& cd .. && rm -rf qemu
|
||||
|
||||
# install SPDK NVMe
|
||||
# only for 'x86_64' platform images as 'docker buildx' can't build 'spdk'
|
||||
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
@@ -119,6 +116,7 @@ RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
&& git submodule update --init \
|
||||
&& apt-get update \
|
||||
&& ./scripts/pkgdep.sh \
|
||||
&& apt-get clean \
|
||||
&& ./configure --with-vfio-user \
|
||||
&& make -j `nproc` \
|
||||
&& mkdir /usr/local/bin/spdk-nvme \
|
||||
@@ -126,3 +124,10 @@ RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
&& cp ./scripts/rpc.py /usr/local/bin/spdk-nvme \
|
||||
&& cp -r ./scripts/rpc /usr/local/bin/spdk-nvme \
|
||||
&& cd .. && rm -rf spdk; fi
|
||||
|
||||
# install ethr tool for performance tests
|
||||
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
wget https://github.com/microsoft/ethr/releases/latest/download/ethr_linux.zip \
|
||||
&& unzip ethr_linux.zip \
|
||||
&& cp ethr /usr/local/bin \
|
||||
&& rm ethr_linux.zip; fi
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
Name: cloud-hypervisor
|
||||
Summary: Cloud Hypervisor is an open source Virtual Machine Monitor (VMM) that runs on top of KVM.
|
||||
Version: 21.1
|
||||
Version: 22.1
|
||||
Release: 0%{?dist}
|
||||
License: ASL 2.0 or BSD-3-clause
|
||||
Group: Applications/System
|
||||
@@ -112,8 +112,11 @@ rm -rf %{buildroot}
|
||||
|
||||
|
||||
%changelog
|
||||
* Thu Mar 10 2022 Rob Bradford <robert.bradford@intel.com> 21.1-0
|
||||
- Update to 21.1
|
||||
* Thu Mar 10 2022 Rob Bradford <robert.bradford@intel.com> 22.1-0
|
||||
- Update to 22.1
|
||||
|
||||
* Thu Mar 03 2022 Rob Bradford <robert.bradford@intel.com> 22.0-0
|
||||
- Update to 22.0
|
||||
|
||||
* Thu Jan 20 2022 Rob Bradford <robert.bradford@intel.com> 21.0-0
|
||||
- Update to 21.0
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
CLI_NAME="Cloud Hypervisor"
|
||||
|
||||
CTR_IMAGE_TAG="cloudhypervisor/dev"
|
||||
CTR_IMAGE_VERSION="latest"
|
||||
CTR_IMAGE_VERSION="20220303-0"
|
||||
CTR_IMAGE="${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}"
|
||||
|
||||
DOCKER_RUNTIME="docker"
|
||||
@@ -48,32 +48,32 @@ CARGO_TARGET_DIR="${CLH_BUILD_DIR}/cargo_target"
|
||||
# Send a decorated message to stdout, followed by a new line
|
||||
#
|
||||
say() {
|
||||
[ -t 1 ] && [ -n "$TERM" ] \
|
||||
&& echo "$(tput setaf 2)[$CLI_NAME]$(tput sgr0) $*" \
|
||||
|| echo "[$CLI_NAME] $*"
|
||||
[ -t 1 ] && [ -n "$TERM" ] &&
|
||||
echo "$(tput setaf 2)[$CLI_NAME]$(tput sgr0) $*" ||
|
||||
echo "[$CLI_NAME] $*"
|
||||
}
|
||||
|
||||
# Send a decorated message to stdout, without a trailing new line
|
||||
#
|
||||
say_noln() {
|
||||
[ -t 1 ] && [ -n "$TERM" ] \
|
||||
&& echo -n "$(tput setaf 2)[$CLI_NAME]$(tput sgr0) $*" \
|
||||
|| echo "[$CLI_NAME] $*"
|
||||
[ -t 1 ] && [ -n "$TERM" ] &&
|
||||
echo -n "$(tput setaf 2)[$CLI_NAME]$(tput sgr0) $*" ||
|
||||
echo "[$CLI_NAME] $*"
|
||||
}
|
||||
|
||||
# Send a text message to stderr
|
||||
#
|
||||
say_err() {
|
||||
[ -t 2 ] && [ -n "$TERM" ] \
|
||||
&& echo "$(tput setaf 1)[$CLI_NAME] $*$(tput sgr0)" 1>&2 \
|
||||
|| echo "[$CLI_NAME] $*" 1>&2
|
||||
[ -t 2 ] && [ -n "$TERM" ] &&
|
||||
echo "$(tput setaf 1)[$CLI_NAME] $*$(tput sgr0)" 1>&2 ||
|
||||
echo "[$CLI_NAME] $*" 1>&2
|
||||
}
|
||||
|
||||
# Send a warning-highlighted text to stdout
|
||||
say_warn() {
|
||||
[ -t 1 ] && [ -n "$TERM" ] \
|
||||
&& echo "$(tput setaf 3)[$CLI_NAME] $*$(tput sgr0)" \
|
||||
|| echo "[$CLI_NAME] $*"
|
||||
[ -t 1 ] && [ -n "$TERM" ] &&
|
||||
echo "$(tput setaf 3)[$CLI_NAME] $*$(tput sgr0)" ||
|
||||
echo "[$CLI_NAME] $*"
|
||||
}
|
||||
|
||||
# Exit with an error message and (optional) code
|
||||
@@ -86,7 +86,7 @@ die() {
|
||||
shift 2
|
||||
}
|
||||
say_err "$@"
|
||||
exit $code
|
||||
exit "$code"
|
||||
}
|
||||
|
||||
# Exit with an error message if the last exit code is not 0
|
||||
@@ -101,26 +101,34 @@ ok_or_die() {
|
||||
#
|
||||
ensure_build_dir() {
|
||||
for dir in "$CLH_BUILD_DIR" \
|
||||
"$CLH_INTEGRATION_WORKLOADS" \
|
||||
"$CLH_CTR_BUILD_DIR" \
|
||||
"$CARGO_TARGET_DIR" \
|
||||
"$CARGO_REGISTRY_DIR" \
|
||||
"$CARGO_GIT_REGISTRY_DIR"; do
|
||||
"$CLH_INTEGRATION_WORKLOADS" \
|
||||
"$CLH_CTR_BUILD_DIR" \
|
||||
"$CARGO_TARGET_DIR" \
|
||||
"$CARGO_REGISTRY_DIR" \
|
||||
"$CARGO_GIT_REGISTRY_DIR"; do
|
||||
mkdir -p "$dir" || die "Error: cannot create dir $dir"
|
||||
[ -x "$dir" ] && [ -w "$dir" ] || \
|
||||
[ -x "$dir" ] && [ -w "$dir" ] ||
|
||||
{
|
||||
say "Wrong permissions for $dir. Attempting to fix them ..."
|
||||
chmod +x+w "$dir"
|
||||
} || \
|
||||
} ||
|
||||
die "Error: wrong permissions for $dir. Should be +x+w"
|
||||
done
|
||||
}
|
||||
|
||||
# Make sure we're using the latest dev container, by just pulling it.
|
||||
ensure_latest_ctr() {
|
||||
$DOCKER_RUNTIME pull "$CTR_IMAGE"
|
||||
if [ "$CTR_IMAGE_VERSION" = "local" ]; then
|
||||
build_container
|
||||
else
|
||||
$DOCKER_RUNTIME pull "$CTR_IMAGE"
|
||||
|
||||
ok_or_die "Error pulling container image. Aborting."
|
||||
if [ $? -ne 0 ]; then
|
||||
build_container
|
||||
fi
|
||||
|
||||
ok_or_die "Error pulling/building container image. Aborting."
|
||||
fi
|
||||
}
|
||||
|
||||
# Fix main directory permissions after a container ran as root.
|
||||
@@ -132,14 +140,14 @@ fix_dir_perms() {
|
||||
# Yes, running Docker to get elevated privileges, just to chown some files
|
||||
# is a dirty hack.
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
"$CTR_IMAGE" \
|
||||
chown -R "$(id -u):$(id -g)" "$CTR_CLH_ROOT_DIR"
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
"$CTR_IMAGE" \
|
||||
chown -R "$(id -u):$(id -g)" "$CTR_CLH_ROOT_DIR"
|
||||
|
||||
return $1
|
||||
return "$1"
|
||||
}
|
||||
# Process exported volumes argument, separate the volumes and make docker compatible
|
||||
# Sample input: --volumes /a:/a#/b:/b
|
||||
@@ -151,8 +159,7 @@ process_volumes_args() {
|
||||
fi
|
||||
exported_volumes=""
|
||||
arr_vols=(${arg_vols//#/ })
|
||||
for var in "${arr_vols[@]}"
|
||||
do
|
||||
for var in "${arr_vols[@]}"; do
|
||||
parts=(${var//:/ })
|
||||
if [[ ! -e "${parts[0]}" ]]; then
|
||||
echo "The volume ${parts[0]} does not exist."
|
||||
@@ -163,8 +170,8 @@ process_volumes_args() {
|
||||
}
|
||||
cmd_help() {
|
||||
echo ""
|
||||
echo "Cloud Hypervisor $(basename $0)"
|
||||
echo "Usage: $(basename $0) <command> [<command args>]"
|
||||
echo "Cloud Hypervisor $(basename "$0")"
|
||||
echo "Usage: $(basename "$0") <command> [<command args>]"
|
||||
echo ""
|
||||
echo "Available commands:"
|
||||
echo ""
|
||||
@@ -176,7 +183,7 @@ cmd_help() {
|
||||
echo " --volumes Hash separated volumes to be exported. Example --volumes /mnt:/mnt#/myvol:/myvol"
|
||||
echo " --hypervisor Underlying hypervisor. Options kvm, mshv"
|
||||
echo ""
|
||||
echo " tests [--unit|--cargo|--all] [--libc musl|gnu] [-- [<cargo test args>]]"
|
||||
echo " tests [--unit|--cargo|--all] [--libc musl|gnu] [-- [<test scripts args>] [-- [<test binary args>]]] "
|
||||
echo " Run the Cloud Hypervisor tests."
|
||||
echo " --unit Run the unit tests."
|
||||
echo " --cargo Run the cargo tests."
|
||||
@@ -186,13 +193,13 @@ cmd_help() {
|
||||
echo " --integration-windows Run the Windows guest integration tests."
|
||||
echo " --integration-live-migration Run the live-migration integration tests."
|
||||
echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu"
|
||||
echo " --metrics Generate performance metrics"
|
||||
echo " --volumes Hash separated volumes to be exported. Example --volumes /mnt:/mnt#/myvol:/myvol"
|
||||
echo " --hypervisor Underlying hypervisor. Options kvm, mshv"
|
||||
echo " --all Run all tests."
|
||||
echo ""
|
||||
echo " build-container [--type]"
|
||||
echo " Build the Cloud Hypervisor container."
|
||||
echo " --dev Build dev container. This is the default."
|
||||
echo ""
|
||||
echo " clean [<cargo args>]]"
|
||||
echo " Remove the Cloud Hypervisor artifacts."
|
||||
@@ -213,37 +220,47 @@ cmd_build() {
|
||||
features_build=""
|
||||
exported_device="/dev/kvm"
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--debug") { build="debug"; } ;;
|
||||
"--release") { build="release"; } ;;
|
||||
"--libc")
|
||||
shift
|
||||
[[ "$1" =~ ^(musl|gnu)$ ]] || \
|
||||
die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"."
|
||||
libc="$1"
|
||||
;;
|
||||
"--volumes")
|
||||
shift
|
||||
arg_vols="$1"
|
||||
;;
|
||||
"--hypervisor")
|
||||
shift
|
||||
hypervisor="$1"
|
||||
;;
|
||||
"--") { shift; break; } ;;
|
||||
*)
|
||||
die "Unknown build argument: $1. Please use --help for help."
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
case "$1" in
|
||||
"-h" | "--help") {
|
||||
cmd_help
|
||||
exit 1
|
||||
} ;;
|
||||
"--debug") { build="debug"; } ;;
|
||||
"--release") { build="release"; } ;;
|
||||
"--libc")
|
||||
shift
|
||||
[[ "$1" =~ ^(musl|gnu)$ ]] ||
|
||||
die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"."
|
||||
libc="$1"
|
||||
;;
|
||||
"--volumes")
|
||||
shift
|
||||
arg_vols="$1"
|
||||
;;
|
||||
"--hypervisor")
|
||||
shift
|
||||
hypervisor="$1"
|
||||
;;
|
||||
"--features")
|
||||
shift
|
||||
features_build="--features $1"
|
||||
;;
|
||||
"--") {
|
||||
shift
|
||||
break
|
||||
} ;;
|
||||
*)
|
||||
die "Unknown build argument: $1. Please use --help for help."
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
ensure_build_dir
|
||||
ensure_latest_ctr
|
||||
|
||||
process_volumes_args
|
||||
if [[ ! ("$hypervisor" = "kvm" || "$hypervisor" = "mshv") ]]; then
|
||||
if [[ ! ("$hypervisor" = "kvm" || "$hypervisor" = "mshv") ]]; then
|
||||
die "Hypervisor value must be kvm or mshv"
|
||||
fi
|
||||
if [[ "$hypervisor" = "mshv" ]]; then
|
||||
@@ -254,25 +271,28 @@ cmd_build() {
|
||||
cargo_args=("$@")
|
||||
[ $build = "release" ] && cargo_args+=("--release")
|
||||
cargo_args+=(--target "$target")
|
||||
[ $(uname -m) = "aarch64" ] && cargo_args+=("--no-default-features")
|
||||
[ $(uname -m) = "aarch64" ] && cargo_args+=(--features $hypervisor)
|
||||
[ "$(uname -m)" = "aarch64" ] && cargo_args+=("--no-default-features")
|
||||
[ "$(uname -m)" = "aarch64" ] && cargo_args+=(--features "$hypervisor")
|
||||
|
||||
rustflags=""
|
||||
if [ $(uname -m) = "aarch64" ] && [ $libc = "musl" ] ; then
|
||||
target_cc=""
|
||||
if [ "$(uname -m)" = "aarch64" ] && [ "$libc" = "musl" ]; then
|
||||
rustflags="-C link-arg=-lgcc -C link_arg=-specs -C link_arg=/usr/lib/aarch64-linux-musl/musl-gcc.specs"
|
||||
target_cc="musl-gcc"
|
||||
fi
|
||||
|
||||
$DOCKER_RUNTIME run \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--volume $exported_device \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--env RUSTFLAGS="$rustflags" \
|
||||
"$CTR_IMAGE" \
|
||||
cargo build --all $features_build \
|
||||
--target-dir "$CTR_CLH_CARGO_TARGET" \
|
||||
"${cargo_args[@]}" && say "Binaries placed under $CLH_CARGO_TARGET/$target/$build"
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--volume $exported_device \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--env RUSTFLAGS="$rustflags" \
|
||||
--env TARGET_CC="$target_cc" \
|
||||
"$CTR_IMAGE" \
|
||||
cargo build --all $features_build \
|
||||
--target-dir "$CTR_CLH_CARGO_TARGET" \
|
||||
"${cargo_args[@]}" && say "Binaries placed under $CLH_CARGO_TARGET/$target/$build"
|
||||
}
|
||||
|
||||
cmd_clean() {
|
||||
@@ -282,15 +302,15 @@ cmd_clean() {
|
||||
ensure_latest_ctr
|
||||
|
||||
$DOCKER_RUNTIME run \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
"$CTR_IMAGE" \
|
||||
cargo clean \
|
||||
--target-dir "$CTR_CLH_CARGO_TARGET" \
|
||||
"${cargo_args[@]}"
|
||||
}
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
"$CTR_IMAGE" \
|
||||
cargo clean \
|
||||
--target-dir "$CTR_CLH_CARGO_TARGET" \
|
||||
"${cargo_args[@]}"
|
||||
}
|
||||
|
||||
cmd_tests() {
|
||||
unit=false
|
||||
@@ -300,43 +320,55 @@ cmd_tests() {
|
||||
integration_vfio=false
|
||||
integration_windows=false
|
||||
integration_live_migration=false
|
||||
metrics=false
|
||||
libc="gnu"
|
||||
arg_vols=""
|
||||
hypervisor="kvm"
|
||||
exported_device="/dev/kvm"
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--unit") { unit=true; } ;;
|
||||
"--cargo") { cargo=true; } ;;
|
||||
"--integration") { integration=true; } ;;
|
||||
"--integration-sgx") { integration_sgx=true; } ;;
|
||||
"--integration-vfio") { integration_vfio=true; } ;;
|
||||
"--integration-windows") { integration_windows=true; } ;;
|
||||
"--integration-live-migration") { integration_live_migration=true; } ;;
|
||||
"--libc")
|
||||
shift
|
||||
[[ "$1" =~ ^(musl|gnu)$ ]] || \
|
||||
die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"."
|
||||
libc="$1"
|
||||
;;
|
||||
"--volumes")
|
||||
shift
|
||||
arg_vols="$1"
|
||||
;;
|
||||
"--hypervisor")
|
||||
shift
|
||||
hypervisor="$1"
|
||||
;;
|
||||
"--all") { cargo=true; unit=true; integration=true; } ;;
|
||||
"--") { shift; break; } ;;
|
||||
*)
|
||||
die "Unknown tests argument: $1. Please use --help for help."
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
case "$1" in
|
||||
"-h" | "--help") {
|
||||
cmd_help
|
||||
exit 1
|
||||
} ;;
|
||||
"--unit") { unit=true; } ;;
|
||||
"--cargo") { cargo=true; } ;;
|
||||
"--integration") { integration=true; } ;;
|
||||
"--integration-sgx") { integration_sgx=true; } ;;
|
||||
"--integration-vfio") { integration_vfio=true; } ;;
|
||||
"--integration-windows") { integration_windows=true; } ;;
|
||||
"--integration-live-migration") { integration_live_migration=true; } ;;
|
||||
"--metrics") { metrics=true; } ;;
|
||||
"--libc")
|
||||
shift
|
||||
[[ "$1" =~ ^(musl|gnu)$ ]] ||
|
||||
die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"."
|
||||
libc="$1"
|
||||
;;
|
||||
"--volumes")
|
||||
shift
|
||||
arg_vols="$1"
|
||||
;;
|
||||
"--hypervisor")
|
||||
shift
|
||||
hypervisor="$1"
|
||||
;;
|
||||
"--all") {
|
||||
cargo=true
|
||||
unit=true
|
||||
integration=true
|
||||
} ;;
|
||||
"--") {
|
||||
shift
|
||||
break
|
||||
} ;;
|
||||
*)
|
||||
die "Unknown tests argument: $1. Please use --help for help."
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
if [[ ! ("$hypervisor" = "kvm" || "$hypervisor" = "mshv") ]]; then
|
||||
if [[ ! ("$hypervisor" = "kvm" || "$hypervisor" = "mshv") ]]; then
|
||||
die "Hypervisor value must be kvm or mshv"
|
||||
fi
|
||||
|
||||
@@ -344,190 +376,214 @@ cmd_tests() {
|
||||
exported_device="/dev/mshv"
|
||||
fi
|
||||
|
||||
set -- "$@" '--hypervisor' $hypervisor
|
||||
set -- '--hypervisor' "$hypervisor" "$@"
|
||||
|
||||
ensure_build_dir
|
||||
ensure_latest_ctr
|
||||
|
||||
process_volumes_args
|
||||
target="$(uname -m)-unknown-linux-${libc}"
|
||||
cflags=""
|
||||
target_cc=""
|
||||
if [[ "$target" == "x86_64-unknown-linux-musl" ]]; then
|
||||
target_cc="musl-gcc"
|
||||
cflags="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
|
||||
|
||||
if [[ "$unit" = true ]]; then
|
||||
say "Running unit tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--device $exported_device \
|
||||
--device /dev/net/tun \
|
||||
--cap-add net_admin \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--env BUILD_TARGET="$target" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_unit_tests.sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
if [[ "$unit" = true ]] ; then
|
||||
say "Running unit tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--device $exported_device \
|
||||
--device /dev/net/tun \
|
||||
--cap-add net_admin \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--env BUILD_TARGET="$target" \
|
||||
--env CFLAGS="$cflags" \
|
||||
--env TARGET_CC="$target_cc" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_unit_tests.sh "$@" || fix_dir_perms $? || exit $?
|
||||
if [ "$cargo" = true ]; then
|
||||
say "Running cargo tests..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_cargo_tests.sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
if [ "$cargo" = true ] ; then
|
||||
say "Running cargo tests..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_cargo_tests.sh "$@" || fix_dir_perms $? || exit $?
|
||||
if [ "$integration" = true ]; then
|
||||
say "Running integration tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net="$CTR_CLH_NET" \
|
||||
--mount type=tmpfs,destination=/tmp \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--env CH_LIBC="${libc}" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_integration_tests_"$(uname -m)".sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
if [ "$integration" = true ] ; then
|
||||
say "Running integration tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net="$CTR_CLH_NET" \
|
||||
--mount type=tmpfs,destination=/tmp \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--env CH_LIBC="${libc}" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_integration_tests_$(uname -m).sh "$@" || fix_dir_perms $? || exit $?
|
||||
if [ "$integration_sgx" = true ]; then
|
||||
say "Running SGX integration tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net="$CTR_CLH_NET" \
|
||||
--mount type=tmpfs,destination=/tmp \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--env CH_LIBC="${libc}" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_integration_tests_sgx.sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
if [ "$integration_sgx" = true ] ; then
|
||||
say "Running SGX integration tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net="$CTR_CLH_NET" \
|
||||
--mount type=tmpfs,destination=/tmp \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--env CH_LIBC="${libc}" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_integration_tests_sgx.sh "$@" || fix_dir_perms $? || exit $?
|
||||
if [ "$integration_vfio" = true ]; then
|
||||
say "Running VFIO integration tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net="$CTR_CLH_NET" \
|
||||
--mount type=tmpfs,destination=/tmp \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--env CH_LIBC="${libc}" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_integration_tests_vfio.sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
if [ "$integration_vfio" = true ] ; then
|
||||
say "Running VFIO integration tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net="$CTR_CLH_NET" \
|
||||
--mount type=tmpfs,destination=/tmp \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--env CH_LIBC="${libc}" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_integration_tests_vfio.sh "$@" || fix_dir_perms $? || exit $?
|
||||
if [ "$integration_windows" = true ]; then
|
||||
say "Running Windows integration tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net="$CTR_CLH_NET" \
|
||||
--mount type=tmpfs,destination=/tmp \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--env CH_LIBC="${libc}" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_integration_tests_windows.sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
if [ "$integration_windows" = true ] ; then
|
||||
say "Running Windows integration tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net="$CTR_CLH_NET" \
|
||||
--mount type=tmpfs,destination=/tmp \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--env CH_LIBC="${libc}" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_integration_tests_windows.sh "$@" || fix_dir_perms $? || exit $?
|
||||
if [ "$integration_live_migration" = true ]; then
|
||||
say "Running 'live migration' integration tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net="$CTR_CLH_NET" \
|
||||
--mount type=tmpfs,destination=/tmp \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--env CH_LIBC="${libc}" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_integration_tests_live_migration.sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
if [ "$integration_live_migration" = true ] ; then
|
||||
say "Running 'live migration' integration tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net="$CTR_CLH_NET" \
|
||||
--mount type=tmpfs,destination=/tmp \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--env CH_LIBC="${libc}" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_integration_tests_live_migration.sh "$@" || fix_dir_perms $? || exit $?
|
||||
if [ "$metrics" = true ]; then
|
||||
say "Generating performance metrics for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net="$CTR_CLH_NET" \
|
||||
--mount type=tmpfs,destination=/tmp \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--env CH_LIBC="${libc}" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_metrics.sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
fix_dir_perms $?
|
||||
}
|
||||
|
||||
cmd_build-container() {
|
||||
container_type="dev"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--dev") { container_type="dev"; } ;;
|
||||
"--") { shift; break; } ;;
|
||||
*)
|
||||
die "Unknown build-container argument: $1. Please use --help for help."
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
build_container() {
|
||||
ensure_build_dir
|
||||
ensure_latest_ctr
|
||||
|
||||
BUILD_DIR=/tmp/cloud-hypervisor/container/
|
||||
|
||||
mkdir -p $BUILD_DIR
|
||||
cp $CLH_DOCKERFILE $BUILD_DIR
|
||||
cp "$CLH_DOCKERFILE" $BUILD_DIR
|
||||
|
||||
[ $(uname -m) = "aarch64" ] && TARGETARCH="arm64"
|
||||
[ $(uname -m) = "x86_64" ] && TARGETARCH="amd64"
|
||||
[ "$(uname -m)" = "aarch64" ] && TARGETARCH="arm64"
|
||||
[ "$(uname -m)" = "x86_64" ] && TARGETARCH="amd64"
|
||||
|
||||
$DOCKER_RUNTIME build \
|
||||
--target $container_type \
|
||||
-t $CTR_IMAGE \
|
||||
-f $BUILD_DIR/Dockerfile \
|
||||
--build-arg TARGETARCH=$TARGETARCH \
|
||||
$BUILD_DIR
|
||||
--target dev \
|
||||
-t $CTR_IMAGE \
|
||||
-f $BUILD_DIR/Dockerfile \
|
||||
--build-arg TARGETARCH=$TARGETARCH \
|
||||
$BUILD_DIR
|
||||
}
|
||||
|
||||
cmd_build-container() {
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
"-h" | "--help") {
|
||||
cmd_help
|
||||
exit 1
|
||||
} ;;
|
||||
"--") {
|
||||
shift
|
||||
break
|
||||
} ;;
|
||||
*)
|
||||
die "Unknown build-container argument: $1. Please use --help for help."
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
build_container
|
||||
}
|
||||
|
||||
cmd_shell() {
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--volumes")
|
||||
shift
|
||||
arg_vols="$1"
|
||||
;;
|
||||
"--") { shift; break; } ;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
case "$1" in
|
||||
"-h" | "--help") {
|
||||
cmd_help
|
||||
exit 1
|
||||
} ;;
|
||||
"--volumes")
|
||||
shift
|
||||
arg_vols="$1"
|
||||
;;
|
||||
"--") {
|
||||
shift
|
||||
break
|
||||
} ;;
|
||||
*) ;;
|
||||
|
||||
esac
|
||||
shift
|
||||
done
|
||||
ensure_build_dir
|
||||
ensure_latest_ctr
|
||||
@@ -535,20 +591,20 @@ cmd_shell() {
|
||||
say_warn "Starting a privileged shell prompt as root ..."
|
||||
say_warn "WARNING: Your $CLH_ROOT_DIR folder will be bind-mounted in the container under $CTR_CLH_ROOT_DIR"
|
||||
$DOCKER_RUNTIME run \
|
||||
-ti \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net="$CTR_CLH_NET" \
|
||||
--tmpfs /tmp:exec \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--entrypoint bash \
|
||||
"$CTR_IMAGE"
|
||||
-ti \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net="$CTR_CLH_NET" \
|
||||
--tmpfs /tmp:exec \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--entrypoint bash \
|
||||
"$CTR_IMAGE"
|
||||
|
||||
fix_dir_perms $?
|
||||
}
|
||||
@@ -557,14 +613,20 @@ cmd_shell() {
|
||||
#
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h|--help) { cmd_help; exit 1; } ;;
|
||||
-y|--unattended) { OPT_UNATTENDED=true; } ;;
|
||||
-*)
|
||||
die "Unknown arg: $1. Please use \`$0 help\` for help."
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
-h | --help) {
|
||||
cmd_help
|
||||
exit 1
|
||||
} ;;
|
||||
--local) {
|
||||
CTR_IMAGE_VERSION="local"
|
||||
CTR_IMAGE="${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}"
|
||||
} ;;
|
||||
-*)
|
||||
die "Unknown arg: $1. Please use \`$0 help\` for help."
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
@@ -572,11 +634,10 @@ done
|
||||
# $1 is now a command name. Check if it is a valid command and, if so,
|
||||
# run it.
|
||||
#
|
||||
declare -f "cmd_$1" > /dev/null
|
||||
declare -f "cmd_$1" >/dev/null
|
||||
ok_or_die "Unknown command: $1. Please use \`$0 help\` for help."
|
||||
|
||||
cmd=cmd_$1
|
||||
shift
|
||||
|
||||
|
||||
$cmd "$@"
|
||||
|
||||
@@ -217,17 +217,17 @@ update_workloads() {
|
||||
guestunmount "$FOCAL_OS_RAW_IMAGE_UPDATE_KERNEL_ROOT_DIR"
|
||||
|
||||
# Build virtiofsd
|
||||
VIRTIOFSD_RS="$WORKLOADS_DIR/virtiofsd-rs"
|
||||
VIRTIOFSD_RS_DIR="virtiofsd_rs_build"
|
||||
if [ ! -f "$VIRTIOFSD_RS" ]; then
|
||||
VIRTIOFSD="$WORKLOADS_DIR/virtiofsd"
|
||||
VIRTIOFSD_DIR="virtiofsd_build"
|
||||
if [ ! -f "$VIRTIOFSD" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
git clone "https://gitlab.com/virtio-fs/virtiofsd-rs.git" $VIRTIOFSD_RS_DIR
|
||||
pushd $VIRTIOFSD_RS_DIR
|
||||
git checkout 21d20035a582fb0389697b1bd7f8331623a77939
|
||||
git clone "https://gitlab.com/virtio-fs/virtiofsd.git" $VIRTIOFSD_DIR
|
||||
pushd $VIRTIOFSD_DIR
|
||||
git checkout v1.1.0
|
||||
time cargo build --release
|
||||
cp target/release/virtiofsd-rs $VIRTIOFSD_RS || exit 1
|
||||
cp target/release/virtiofsd $VIRTIOFSD || exit 1
|
||||
popd
|
||||
rm -rf $VIRTIOFSD_RS_DIR
|
||||
rm -rf $VIRTIOFSD_DIR
|
||||
popd
|
||||
fi
|
||||
|
||||
@@ -285,11 +285,9 @@ if [ $RES -ne 0 ]; then
|
||||
fi
|
||||
|
||||
BUILD_TARGET="aarch64-unknown-linux-${CH_LIBC}"
|
||||
CFLAGS=""
|
||||
TARGET_CC=""
|
||||
if [[ "${BUILD_TARGET}" == "aarch64-unknown-linux-musl" ]]; then
|
||||
TARGET_CC="musl-gcc"
|
||||
CFLAGS="-I /usr/include/aarch64-linux-musl/ -idirafter /usr/include/"
|
||||
export TARGET_CC="musl-gcc"
|
||||
export RUSTFLAGS="-C link-arg=-lgcc -C link_arg=-specs -C link_arg=/usr/lib/aarch64-linux-musl/musl-gcc.specs"
|
||||
fi
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
@@ -311,13 +309,13 @@ echo 6144 | sudo tee /proc/sys/vm/nr_hugepages
|
||||
sudo chmod a+rwX /dev/hugepages
|
||||
|
||||
# Run all direct kernel boot (Device Tree) test cases in mod `parallel`
|
||||
time cargo test $features "parallel::$test_filter"
|
||||
time cargo test $features "parallel::$test_filter" --target $BUILD_TARGET -- ${test_binary_args[*]}
|
||||
RES=$?
|
||||
|
||||
# Run some tests in sequence since the result could be affected by other tests
|
||||
# running in parallel.
|
||||
if [ $RES -eq 0 ]; then
|
||||
time cargo test $features "sequential::$test_filter" -- --test-threads=1
|
||||
time cargo test $features "sequential::$test_filter" --target $BUILD_TARGET -- --test-threads=1 ${test_binary_args[*]}
|
||||
RES=$?
|
||||
else
|
||||
exit $RES
|
||||
@@ -325,7 +323,7 @@ fi
|
||||
|
||||
# Run all ACPI test cases
|
||||
if [ $RES -eq 0 ]; then
|
||||
time cargo test $features "aarch64_acpi::$test_filter"
|
||||
time cargo test $features "aarch64_acpi::$test_filter" --target $BUILD_TARGET -- ${test_binary_args[*]}
|
||||
RES=$?
|
||||
else
|
||||
exit $RES
|
||||
@@ -333,7 +331,7 @@ fi
|
||||
|
||||
# Run all test cases related to live migration
|
||||
if [ $RES -eq 0 ]; then
|
||||
time cargo test $features "live_migration::$test_filter" -- --test-threads=1
|
||||
time cargo test $features "live_migration::$test_filter" --target $BUILD_TARGET -- --test-threads=1 ${test_binary_args[*]}
|
||||
RES=$?
|
||||
else
|
||||
exit $RES
|
||||
|
||||
@@ -87,7 +87,7 @@ echo 6144 | sudo tee /proc/sys/vm/nr_hugepages
|
||||
sudo chmod a+rwX /dev/hugepages
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
time cargo test $features "live_migration::$test_filter" -- --test-threads=1
|
||||
time cargo test $features "live_migration::$test_filter" -- --test-threads=1 ${test_binary_args[*]}
|
||||
RES=$?
|
||||
|
||||
exit $RES
|
||||
|
||||
@@ -27,7 +27,7 @@ strip target/$BUILD_TARGET/release/cloud-hypervisor
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
|
||||
time cargo test $features "sgx::$test_filter"
|
||||
time cargo test $features "sgx::$test_filter" -- ${test_binary_args[*]}
|
||||
RES=$?
|
||||
|
||||
exit $RES
|
||||
|
||||
@@ -21,7 +21,7 @@ strip target/$BUILD_TARGET/release/cloud-hypervisor
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
|
||||
time cargo test $features "vfio::$test_filter" -- --test-threads=1
|
||||
time cargo test $features "vfio::$test_filter" -- --test-threads=1 ${test_binary_args[*]}
|
||||
RES=$?
|
||||
|
||||
exit $RES
|
||||
|
||||
@@ -51,7 +51,7 @@ export RUST_BACKTRACE=1
|
||||
|
||||
# Only run with 1 thread to avoid tests interfering with one another because
|
||||
# Windows has a static IP configured
|
||||
time cargo test $features "windows::$test_filter"
|
||||
time cargo test $features "windows::$test_filter" -- ${test_binary_args[*]}
|
||||
RES=$?
|
||||
|
||||
dmsetup remove_all -f
|
||||
|
||||
@@ -129,17 +129,17 @@ if [ -d "$LINUX_CUSTOM_DIR" ]; then
|
||||
rm -rf $LINUX_CUSTOM_DIR
|
||||
fi
|
||||
|
||||
VIRTIOFSD_RS="$WORKLOADS_DIR/virtiofsd-rs"
|
||||
VIRTIOFSD_RS_DIR="virtiofsd_rs_build"
|
||||
if [ ! -f "$VIRTIOFSD_RS" ]; then
|
||||
VIRTIOFSD="$WORKLOADS_DIR/virtiofsd"
|
||||
VIRTIOFSD_DIR="virtiofsd_build"
|
||||
if [ ! -f "$VIRTIOFSD" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
git clone "https://gitlab.com/virtio-fs/virtiofsd-rs.git" $VIRTIOFSD_RS_DIR
|
||||
pushd $VIRTIOFSD_RS_DIR
|
||||
git checkout 21d20035a582fb0389697b1bd7f8331623a77939
|
||||
git clone "https://gitlab.com/virtio-fs/virtiofsd.git" $VIRTIOFSD_DIR
|
||||
pushd $VIRTIOFSD_DIR
|
||||
git checkout v1.1.0
|
||||
time cargo build --release
|
||||
cp target/release/virtiofsd-rs $VIRTIOFSD_RS || exit 1
|
||||
cp target/release/virtiofsd $VIRTIOFSD || exit 1
|
||||
popd
|
||||
rm -rf $VIRTIOFSD_RS_DIR
|
||||
rm -rf $VIRTIOFSD_DIR
|
||||
popd
|
||||
fi
|
||||
|
||||
@@ -174,12 +174,6 @@ cp $FW $VFIO_DIR
|
||||
cp $VMLINUX_IMAGE $VFIO_DIR || exit 1
|
||||
|
||||
BUILD_TARGET="$(uname -m)-unknown-linux-${CH_LIBC}"
|
||||
CFLAGS=""
|
||||
TARGET_CC=""
|
||||
if [[ "${BUILD_TARGET}" == "x86_64-unknown-linux-musl" ]]; then
|
||||
TARGET_CC="musl-gcc"
|
||||
CFLAGS="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
|
||||
fi
|
||||
|
||||
cargo build --all --release $features --target $BUILD_TARGET
|
||||
strip target/$BUILD_TARGET/release/cloud-hypervisor
|
||||
@@ -201,14 +195,14 @@ echo 6144 | sudo tee /proc/sys/vm/nr_hugepages
|
||||
sudo chmod a+rwX /dev/hugepages
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
time cargo test $features "parallel::$test_filter"
|
||||
time cargo test $features "parallel::$test_filter" -- ${test_binary_args[*]}
|
||||
RES=$?
|
||||
|
||||
# Run some tests in sequence since the result could be affected by other tests
|
||||
# running in parallel.
|
||||
if [ $RES -eq 0 ]; then
|
||||
export RUST_BACKTRACE=1
|
||||
time cargo test $features "sequential::$test_filter" -- --test-threads=1
|
||||
time cargo test $features "sequential::$test_filter" -- --test-threads=1 ${test_binary_args[*]}
|
||||
RES=$?
|
||||
fi
|
||||
|
||||
|
||||
94
scripts/run_metrics.sh
Executable file
94
scripts/run_metrics.sh
Executable file
@@ -0,0 +1,94 @@
|
||||
#!/bin/bash
|
||||
set -x
|
||||
|
||||
source $HOME/.cargo/env
|
||||
source $(dirname "$0")/test-util.sh
|
||||
|
||||
export BUILD_TARGET=${BUILD_TARGET-x86_64-unknown-linux-gnu}
|
||||
|
||||
WORKLOADS_DIR="$HOME/workloads"
|
||||
mkdir -p "$WORKLOADS_DIR"
|
||||
|
||||
process_common_args "$@"
|
||||
|
||||
# For now these values are default for kvm
|
||||
features=""
|
||||
|
||||
if [ "$hypervisor" = "mshv" ] ; then
|
||||
features="--no-default-features --features mshv,common"
|
||||
fi
|
||||
|
||||
cp scripts/sha1sums-x86_64 $WORKLOADS_DIR
|
||||
|
||||
FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.qcow2"
|
||||
FOCAL_OS_IMAGE_URL="https://cloud-hypervisor.azureedge.net/$FOCAL_OS_IMAGE_NAME"
|
||||
FOCAL_OS_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_IMAGE_NAME"
|
||||
if [ ! -f "$FOCAL_OS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
time wget --quiet $FOCAL_OS_IMAGE_URL || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
FOCAL_OS_RAW_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.raw"
|
||||
FOCAL_OS_RAW_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
time qemu-img convert -p -f qcow2 -O raw $FOCAL_OS_IMAGE_NAME $FOCAL_OS_RAW_IMAGE_NAME || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
pushd $WORKLOADS_DIR
|
||||
grep focal sha1sums-x86_64 | sha1sum --check
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "sha1sum validation of images failed, remove invalid images to fix the issue."
|
||||
exit 1
|
||||
fi
|
||||
popd
|
||||
|
||||
# Build custom kernel based on virtio-pmem and virtio-fs upstream patches
|
||||
VMLINUX_IMAGE="$WORKLOADS_DIR/vmlinux"
|
||||
|
||||
LINUX_CUSTOM_DIR="$WORKLOADS_DIR/linux-custom"
|
||||
|
||||
if [ ! -f "$VMLINUX_IMAGE" ]; then
|
||||
SRCDIR=$PWD
|
||||
pushd $WORKLOADS_DIR
|
||||
time git clone --depth 1 "https://github.com/cloud-hypervisor/linux.git" -b "ch-5.15.12" $LINUX_CUSTOM_DIR
|
||||
cp $SRCDIR/resources/linux-config-x86_64 $LINUX_CUSTOM_DIR/.config
|
||||
popd
|
||||
fi
|
||||
|
||||
if [ ! -f "$VMLINUX_IMAGE" ]; then
|
||||
pushd $LINUX_CUSTOM_DIR
|
||||
time make bzImage -j `nproc`
|
||||
cp vmlinux $VMLINUX_IMAGE || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
if [ -d "$LINUX_CUSTOM_DIR" ]; then
|
||||
rm -rf $LINUX_CUSTOM_DIR
|
||||
fi
|
||||
|
||||
BUILD_TARGET="$(uname -m)-unknown-linux-${CH_LIBC}"
|
||||
CFLAGS=""
|
||||
TARGET_CC=""
|
||||
if [[ "${BUILD_TARGET}" == "x86_64-unknown-linux-musl" ]]; then
|
||||
TARGET_CC="musl-gcc"
|
||||
CFLAGS="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
|
||||
fi
|
||||
|
||||
cargo build --all --release $features --target $BUILD_TARGET
|
||||
strip target/$BUILD_TARGET/release/cloud-hypervisor
|
||||
strip target/$BUILD_TARGET/release/vhost_user_net
|
||||
strip target/$BUILD_TARGET/release/ch-remote
|
||||
strip target/$BUILD_TARGET/release/performance-metrics
|
||||
|
||||
# setup hugepages
|
||||
echo 6144 | sudo tee /proc/sys/vm/nr_hugepages
|
||||
sudo chmod a+rwX /dev/hugepages
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
time target/$BUILD_TARGET/release/performance-metrics ${test_binary_args[*]}
|
||||
RES=$?
|
||||
|
||||
exit $RES
|
||||
@@ -12,5 +12,11 @@ if [[ $(uname -m) = "aarch64" || $hypervisor = "mshv" ]]; then
|
||||
cargo_args+=("--no-default-features")
|
||||
cargo_args+=("--features $hypervisor")
|
||||
fi
|
||||
|
||||
if [[ "${BUILD_TARGET}" == "aarch64-unknown-linux-musl" ]]; then
|
||||
export TARGET_CC="musl-gcc"
|
||||
export RUSTFLAGS="-C link-arg=-lgcc -C link_arg=-specs -C link_arg=/usr/lib/aarch64-linux-musl/musl-gcc.specs"
|
||||
fi
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
cargo test --lib --bins --target $BUILD_TARGET --workspace ${cargo_args[@]} || exit 1;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
hypervisor="kvm"
|
||||
test_filter=""
|
||||
test_binary_args=()
|
||||
|
||||
cmd_help() {
|
||||
echo ""
|
||||
@@ -28,12 +29,20 @@ process_common_args() {
|
||||
shift
|
||||
test_filter="$1"
|
||||
;;
|
||||
"--") {
|
||||
shift
|
||||
break
|
||||
} ;;
|
||||
*)
|
||||
;;
|
||||
echo "Unknown test scripts argument: $1. Please use '-- --help' for help."
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
if [[ ! ("$hypervisor" = "kvm" || "$hypervisor" = "mshv") ]]; then
|
||||
die "Hypervisor value must be kvm or mshv"
|
||||
fi
|
||||
|
||||
test_binary_args="$@"
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ extern crate clap;
|
||||
use api_client::simple_api_command;
|
||||
use api_client::simple_api_command_with_fds;
|
||||
use api_client::Error as ApiClientError;
|
||||
use clap::{App, AppSettings, Arg, ArgMatches};
|
||||
use clap::{Arg, ArgMatches, Command};
|
||||
use option_parser::{ByteSized, ByteSizedParseError};
|
||||
use std::fmt;
|
||||
use std::os::unix::net::UnixStream;
|
||||
@@ -426,9 +426,9 @@ fn do_command(matches: &ArgMatches) -> Result<(), Error> {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = App::new("ch-remote")
|
||||
let app = Command::new("ch-remote")
|
||||
.author(crate_authors!())
|
||||
.setting(AppSettings::SubcommandRequired)
|
||||
.subcommand_required(true)
|
||||
.about("Remotely control a cloud-hypervisor VMM.")
|
||||
.arg(
|
||||
Arg::new("api-socket")
|
||||
@@ -439,21 +439,21 @@ fn main() {
|
||||
.required(true),
|
||||
)
|
||||
.subcommand(
|
||||
App::new("add-device").about("Add VFIO device").arg(
|
||||
Command::new("add-device").about("Add VFIO device").arg(
|
||||
Arg::new("device_config")
|
||||
.index(1)
|
||||
.help(vmm::config::DeviceConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
App::new("add-disk").about("Add block device").arg(
|
||||
Command::new("add-disk").about("Add block device").arg(
|
||||
Arg::new("disk_config")
|
||||
.index(1)
|
||||
.help(vmm::config::DiskConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
App::new("add-fs")
|
||||
Command::new("add-fs")
|
||||
.about("Add virtio-fs backed fs device")
|
||||
.arg(
|
||||
Arg::new("fs_config")
|
||||
@@ -462,7 +462,7 @@ fn main() {
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
App::new("add-pmem")
|
||||
Command::new("add-pmem")
|
||||
.about("Add persistent memory device")
|
||||
.arg(
|
||||
Arg::new("pmem_config")
|
||||
@@ -471,14 +471,14 @@ fn main() {
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
App::new("add-net").about("Add network device").arg(
|
||||
Command::new("add-net").about("Add network device").arg(
|
||||
Arg::new("net_config")
|
||||
.index(1)
|
||||
.help(vmm::config::NetConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
App::new("add-user-device")
|
||||
Command::new("add-user-device")
|
||||
.about("Add userspace device")
|
||||
.arg(
|
||||
Arg::new("device_config")
|
||||
@@ -487,24 +487,24 @@ fn main() {
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
App::new("add-vsock").about("Add vsock device").arg(
|
||||
Command::new("add-vsock").about("Add vsock device").arg(
|
||||
Arg::new("vsock_config")
|
||||
.index(1)
|
||||
.help(vmm::config::VsockConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
App::new("remove-device")
|
||||
Command::new("remove-device")
|
||||
.about("Remove VFIO device")
|
||||
.arg(Arg::new("id").index(1).help("<device_id>")),
|
||||
)
|
||||
.subcommand(App::new("info").about("Info on the VM"))
|
||||
.subcommand(App::new("counters").about("Counters from the VM"))
|
||||
.subcommand(App::new("pause").about("Pause the VM"))
|
||||
.subcommand(App::new("reboot").about("Reboot the VM"))
|
||||
.subcommand(App::new("power-button").about("Trigger a power button in the VM"))
|
||||
.subcommand(Command::new("info").about("Info on the VM"))
|
||||
.subcommand(Command::new("counters").about("Counters from the VM"))
|
||||
.subcommand(Command::new("pause").about("Pause the VM"))
|
||||
.subcommand(Command::new("reboot").about("Reboot the VM"))
|
||||
.subcommand(Command::new("power-button").about("Trigger a power button in the VM"))
|
||||
.subcommand(
|
||||
App::new("resize")
|
||||
Command::new("resize")
|
||||
.about("Resize the VM")
|
||||
.arg(
|
||||
Arg::new("cpus")
|
||||
@@ -529,7 +529,7 @@ fn main() {
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
App::new("resize-zone")
|
||||
Command::new("resize-zone")
|
||||
.about("Resize a memory zone")
|
||||
.arg(
|
||||
Arg::new("id")
|
||||
@@ -546,24 +546,28 @@ fn main() {
|
||||
.number_of_values(1),
|
||||
),
|
||||
)
|
||||
.subcommand(App::new("resume").about("Resume the VM"))
|
||||
.subcommand(App::new("shutdown").about("Shutdown the VM"))
|
||||
.subcommand(Command::new("resume").about("Resume the VM"))
|
||||
.subcommand(Command::new("shutdown").about("Shutdown the VM"))
|
||||
.subcommand(
|
||||
App::new("snapshot").about("Create a snapshot from VM").arg(
|
||||
Arg::new("snapshot_config")
|
||||
.index(1)
|
||||
.help("<destination_url>"),
|
||||
),
|
||||
Command::new("snapshot")
|
||||
.about("Create a snapshot from VM")
|
||||
.arg(
|
||||
Arg::new("snapshot_config")
|
||||
.index(1)
|
||||
.help("<destination_url>"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
App::new("restore").about("Restore VM from a snapshot").arg(
|
||||
Arg::new("restore_config")
|
||||
.index(1)
|
||||
.help(vmm::config::RestoreConfig::SYNTAX),
|
||||
),
|
||||
Command::new("restore")
|
||||
.about("Restore VM from a snapshot")
|
||||
.arg(
|
||||
Arg::new("restore_config")
|
||||
.index(1)
|
||||
.help(vmm::config::RestoreConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
App::new("send-migration")
|
||||
Command::new("send-migration")
|
||||
.about("Initiate a VM migration")
|
||||
.arg(
|
||||
Arg::new("send_migration_config")
|
||||
@@ -577,7 +581,7 @@ fn main() {
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
App::new("receive-migration")
|
||||
Command::new("receive-migration")
|
||||
.about("Receive a VM migration")
|
||||
.arg(
|
||||
Arg::new("receive_migration_config")
|
||||
|
||||
54
src/main.rs
54
src/main.rs
@@ -8,7 +8,7 @@ extern crate clap;
|
||||
#[macro_use]
|
||||
extern crate event_monitor;
|
||||
|
||||
use clap::{App, Arg, ArgGroup, ArgMatches};
|
||||
use clap::{Arg, ArgGroup, ArgMatches, Command};
|
||||
use libc::EFD_NONBLOCK;
|
||||
use log::LevelFilter;
|
||||
use option_parser::OptionParser;
|
||||
@@ -32,6 +32,9 @@ use vmm_sys_util::signal::block_signal;
|
||||
enum Error {
|
||||
#[error("Failed to create API EventFd: {0}")]
|
||||
CreateApiEventFd(#[source] std::io::Error),
|
||||
#[cfg(feature = "gdb")]
|
||||
#[error("Failed to create Debug EventFd: {0}")]
|
||||
CreateDebugEventFd(#[source] std::io::Error),
|
||||
#[cfg_attr(
|
||||
feature = "kvm",
|
||||
error("Failed to open hypervisor interface (is /dev/kvm available?): {0}")
|
||||
@@ -65,6 +68,12 @@ enum Error {
|
||||
BareEventMonitor,
|
||||
#[error("Error doing event monitor I/O: {0}")]
|
||||
EventMonitorIo(std::io::Error),
|
||||
#[cfg(feature = "gdb")]
|
||||
#[error("Error parsing --gdb: {0}")]
|
||||
ParsingGdb(option_parser::OptionParserError),
|
||||
#[cfg(feature = "gdb")]
|
||||
#[error("Error parsing --gdb: path required")]
|
||||
BareGdb,
|
||||
#[error("Error creating log file: {0}")]
|
||||
LogFileCreation(std::io::Error),
|
||||
#[error("Error setting up logger: {0}")]
|
||||
@@ -129,8 +138,8 @@ fn create_app<'a>(
|
||||
default_vcpus: &'a str,
|
||||
default_memory: &'a str,
|
||||
default_rng: &'a str,
|
||||
) -> App<'a> {
|
||||
let app = App::new("cloud-hypervisor")
|
||||
) -> Command<'a> {
|
||||
let app = Command::new("cloud-hypervisor")
|
||||
// 'BUILT_VERSION' is set by the build script 'build.rs' at
|
||||
// compile time
|
||||
.version(env!("BUILT_VERSION"))
|
||||
@@ -155,7 +164,7 @@ fn create_app<'a>(
|
||||
Arg::new("platform")
|
||||
.long("platform")
|
||||
.help(
|
||||
"num_pci_segments=<num pci segments>",
|
||||
"num_pci_segments=<num pci segments>,iommu_segments=<list_of_segments>",
|
||||
)
|
||||
.takes_value(true)
|
||||
.group("vm-config"),
|
||||
@@ -376,6 +385,15 @@ fn create_app<'a>(
|
||||
.group("vm-config"),
|
||||
);
|
||||
|
||||
#[cfg(feature = "gdb")]
|
||||
let app = app.arg(
|
||||
Arg::new("gdb")
|
||||
.long("gdb")
|
||||
.help("GDB socket (UNIX domain socket): path=</path/to/a/file>")
|
||||
.takes_value(true)
|
||||
.group("vmm-config"),
|
||||
);
|
||||
|
||||
#[cfg(feature = "tdx")]
|
||||
let app = app.arg(
|
||||
Arg::new("tdx")
|
||||
@@ -513,6 +531,26 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
|
||||
event!("vmm", "starting");
|
||||
|
||||
let hypervisor = hypervisor::new().map_err(Error::CreateHypervisor)?;
|
||||
|
||||
#[cfg(feature = "gdb")]
|
||||
let gdb_socket_path = if let Some(gdb_config) = cmd_arguments.value_of("gdb") {
|
||||
let mut parser = OptionParser::new();
|
||||
parser.add("path");
|
||||
parser.parse(gdb_config).map_err(Error::ParsingGdb)?;
|
||||
|
||||
if parser.is_set("path") {
|
||||
Some(std::path::PathBuf::from(parser.get("path").unwrap()))
|
||||
} else {
|
||||
return Err(Error::BareGdb);
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
#[cfg(feature = "gdb")]
|
||||
let debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?;
|
||||
#[cfg(feature = "gdb")]
|
||||
let vm_debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?;
|
||||
|
||||
let vmm_thread = vmm::start_vmm_thread(
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
&api_socket_path,
|
||||
@@ -520,6 +558,12 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
|
||||
api_evt.try_clone().unwrap(),
|
||||
http_sender,
|
||||
api_request_receiver,
|
||||
#[cfg(feature = "gdb")]
|
||||
gdb_socket_path,
|
||||
#[cfg(feature = "gdb")]
|
||||
debug_evt.try_clone().unwrap(),
|
||||
#[cfg(feature = "gdb")]
|
||||
vm_debug_evt.try_clone().unwrap(),
|
||||
&seccomp_action,
|
||||
hypervisor,
|
||||
)
|
||||
@@ -687,6 +731,8 @@ mod unit_tests {
|
||||
watchdog: false,
|
||||
#[cfg(feature = "tdx")]
|
||||
tdx: None,
|
||||
#[cfg(feature = "gdb")]
|
||||
gdb: false,
|
||||
platform: None,
|
||||
};
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
dirs = "3.0.1"
|
||||
dirs = "4.0.0"
|
||||
epoll = "4.3.1"
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.91"
|
||||
ssh2 = "0.9.1"
|
||||
ssh2 = { version = "0.9.1", features = ["vendored-openssl"]}
|
||||
vmm-sys-util = "0.9.0"
|
||||
wait-timeout = "0.2.0"
|
||||
|
||||
@@ -3,20 +3,38 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
use ssh2::Session;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpListener;
|
||||
use std::net::TcpStream;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::Path;
|
||||
use std::process::{ExitStatus, Output};
|
||||
use std::process::{Child, Command, ExitStatus, Output, Stdio};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
use vmm_sys_util::tempdir::TempDir;
|
||||
|
||||
pub const DEFAULT_TCP_LISTENER_MESSAGE: &str = "booted";
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Parsing(std::num::ParseIntError),
|
||||
SshCommand(SshCommandError),
|
||||
WaitForBoot(WaitForBootError),
|
||||
}
|
||||
|
||||
impl From<SshCommandError> for Error {
|
||||
fn from(e: SshCommandError) -> Self {
|
||||
Self::SshCommand(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GuestNetworkConfig {
|
||||
pub guest_ip: String,
|
||||
@@ -31,6 +49,7 @@ pub struct GuestNetworkConfig {
|
||||
pub tcp_listener_port: u16,
|
||||
}
|
||||
|
||||
pub const DEFAULT_TCP_LISTENER_MESSAGE: &str = "booted";
|
||||
pub const DEFAULT_TCP_LISTENER_PORT: u16 = 8000;
|
||||
pub const DEFAULT_TCP_LISTENER_TIMEOUT: i32 = 80;
|
||||
|
||||
@@ -507,6 +526,100 @@ pub enum SshCommandError {
|
||||
Command(ssh2::Error),
|
||||
ExitStatus(ssh2::Error),
|
||||
NonZeroExitStatus(i32),
|
||||
FileRead(std::io::Error),
|
||||
FileMetadata(std::io::Error),
|
||||
ScpSend(ssh2::Error),
|
||||
WriteAll(std::io::Error),
|
||||
SendEof(ssh2::Error),
|
||||
WaitEof(ssh2::Error),
|
||||
}
|
||||
|
||||
fn scp_to_guest_with_auth(
|
||||
path: &Path,
|
||||
remote_path: &Path,
|
||||
auth: &PasswordAuth,
|
||||
ip: &str,
|
||||
retries: u8,
|
||||
timeout: u8,
|
||||
) -> Result<(), SshCommandError> {
|
||||
let mut counter = 0;
|
||||
loop {
|
||||
match (|| -> Result<(), SshCommandError> {
|
||||
let tcp =
|
||||
TcpStream::connect(format!("{}:22", ip)).map_err(SshCommandError::Connection)?;
|
||||
let mut sess = Session::new().unwrap();
|
||||
sess.set_tcp_stream(tcp);
|
||||
sess.handshake().map_err(SshCommandError::Handshake)?;
|
||||
|
||||
sess.userauth_password(&auth.username, &auth.password)
|
||||
.map_err(SshCommandError::Authentication)?;
|
||||
assert!(sess.authenticated());
|
||||
|
||||
let content = fs::read(path).map_err(SshCommandError::FileRead)?;
|
||||
let mode = fs::metadata(path)
|
||||
.map_err(SshCommandError::FileMetadata)?
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o777;
|
||||
|
||||
let mut channel = sess
|
||||
.scp_send(remote_path, mode as i32, content.len() as u64, None)
|
||||
.map_err(SshCommandError::ScpSend)?;
|
||||
channel
|
||||
.write_all(&content)
|
||||
.map_err(SshCommandError::WriteAll)?;
|
||||
channel.send_eof().map_err(SshCommandError::SendEof)?;
|
||||
channel.wait_eof().map_err(SshCommandError::WaitEof)?;
|
||||
|
||||
// Intentionally ignore these results here as their failure
|
||||
// does not precipitate a repeat
|
||||
let _ = channel.close();
|
||||
let _ = channel.wait_close();
|
||||
|
||||
Ok(())
|
||||
})() {
|
||||
Ok(_) => break,
|
||||
Err(e) => {
|
||||
counter += 1;
|
||||
if counter >= retries {
|
||||
eprintln!(
|
||||
"\n\n==== Start scp command output (FAILED) ====\n\n\
|
||||
path =\"{:?}\"\n\
|
||||
remote_path =\"{:?}\"\n\
|
||||
auth=\"{:#?}\"\n\
|
||||
ip=\"{}\"\n\
|
||||
error=\"{:?}\"\n\
|
||||
\n==== End scp command outout ====\n\n",
|
||||
path, remote_path, auth, ip, e
|
||||
);
|
||||
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
thread::sleep(std::time::Duration::new((timeout * counter).into(), 0));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn scp_to_guest(
|
||||
path: &Path,
|
||||
remote_path: &Path,
|
||||
ip: &str,
|
||||
retries: u8,
|
||||
timeout: u8,
|
||||
) -> Result<(), SshCommandError> {
|
||||
scp_to_guest_with_auth(
|
||||
path,
|
||||
remote_path,
|
||||
&PasswordAuth {
|
||||
username: String::from("cloud"),
|
||||
password: String::from("cloud123"),
|
||||
},
|
||||
ip,
|
||||
retries,
|
||||
timeout,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn ssh_command_ip_with_auth(
|
||||
@@ -605,3 +718,587 @@ pub fn exec_host_command_output(command: &str) -> Output {
|
||||
.output()
|
||||
.unwrap_or_else(|_| panic!("Expected '{}' to run", command))
|
||||
}
|
||||
|
||||
pub const PIPE_SIZE: i32 = 32 << 20;
|
||||
|
||||
lazy_static! {
|
||||
static ref NEXT_VM_ID: Mutex<u8> = Mutex::new(1);
|
||||
}
|
||||
|
||||
pub struct Guest {
|
||||
pub tmp_dir: TempDir,
|
||||
pub disk_config: Box<dyn DiskConfig>,
|
||||
pub network: GuestNetworkConfig,
|
||||
}
|
||||
|
||||
// Safe to implement as we know we have no interior mutability
|
||||
impl std::panic::RefUnwindSafe for Guest {}
|
||||
|
||||
impl Guest {
|
||||
pub fn new_from_ip_range(mut disk_config: Box<dyn DiskConfig>, class: &str, id: u8) -> Self {
|
||||
let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap();
|
||||
|
||||
let network = GuestNetworkConfig {
|
||||
guest_ip: format!("{}.{}.2", class, id),
|
||||
l2_guest_ip1: format!("{}.{}.3", class, id),
|
||||
l2_guest_ip2: format!("{}.{}.4", class, id),
|
||||
l2_guest_ip3: format!("{}.{}.5", class, id),
|
||||
host_ip: format!("{}.{}.1", class, id),
|
||||
guest_mac: format!("12:34:56:78:90:{:02x}", id),
|
||||
l2_guest_mac1: format!("de:ad:be:ef:12:{:02x}", id),
|
||||
l2_guest_mac2: format!("de:ad:be:ef:34:{:02x}", id),
|
||||
l2_guest_mac3: format!("de:ad:be:ef:56:{:02x}", id),
|
||||
tcp_listener_port: DEFAULT_TCP_LISTENER_PORT + id as u16,
|
||||
};
|
||||
|
||||
disk_config.prepare_files(&tmp_dir, &network);
|
||||
|
||||
Guest {
|
||||
tmp_dir,
|
||||
disk_config,
|
||||
network,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(disk_config: Box<dyn DiskConfig>) -> Self {
|
||||
let mut guard = NEXT_VM_ID.lock().unwrap();
|
||||
let id = *guard;
|
||||
*guard = id + 1;
|
||||
|
||||
Self::new_from_ip_range(disk_config, "192.168", id)
|
||||
}
|
||||
|
||||
pub fn default_net_string(&self) -> String {
|
||||
format!(
|
||||
"tap=,mac={},ip={},mask=255.255.255.0",
|
||||
self.network.guest_mac, self.network.host_ip
|
||||
)
|
||||
}
|
||||
|
||||
pub fn default_net_string_w_iommu(&self) -> String {
|
||||
format!(
|
||||
"tap=,mac={},ip={},mask=255.255.255.0,iommu=on",
|
||||
self.network.guest_mac, self.network.host_ip
|
||||
)
|
||||
}
|
||||
|
||||
pub fn ssh_command(&self, command: &str) -> Result<String, SshCommandError> {
|
||||
ssh_command_ip(
|
||||
command,
|
||||
&self.network.guest_ip,
|
||||
DEFAULT_SSH_RETRIES,
|
||||
DEFAULT_SSH_TIMEOUT,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn ssh_command_l1(&self, command: &str) -> Result<String, SshCommandError> {
|
||||
ssh_command_ip(
|
||||
command,
|
||||
&self.network.guest_ip,
|
||||
DEFAULT_SSH_RETRIES,
|
||||
DEFAULT_SSH_TIMEOUT,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn ssh_command_l2_1(&self, command: &str) -> Result<String, SshCommandError> {
|
||||
ssh_command_ip(
|
||||
command,
|
||||
&self.network.l2_guest_ip1,
|
||||
DEFAULT_SSH_RETRIES,
|
||||
DEFAULT_SSH_TIMEOUT,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn ssh_command_l2_2(&self, command: &str) -> Result<String, SshCommandError> {
|
||||
ssh_command_ip(
|
||||
command,
|
||||
&self.network.l2_guest_ip2,
|
||||
DEFAULT_SSH_RETRIES,
|
||||
DEFAULT_SSH_TIMEOUT,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn ssh_command_l2_3(&self, command: &str) -> Result<String, SshCommandError> {
|
||||
ssh_command_ip(
|
||||
command,
|
||||
&self.network.l2_guest_ip3,
|
||||
DEFAULT_SSH_RETRIES,
|
||||
DEFAULT_SSH_TIMEOUT,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn api_create_body(
|
||||
&self,
|
||||
cpu_count: u8,
|
||||
_fw_path: &str,
|
||||
_kernel_path: &str,
|
||||
_kernel_cmd: &str,
|
||||
) -> String {
|
||||
#[cfg(all(target_arch = "x86_64", not(feature = "mshv")))]
|
||||
format! {"{{\"cpus\":{{\"boot_vcpus\":{},\"max_vcpus\":{}}},\"kernel\":{{\"path\":\"{}\"}},\"cmdline\":{{\"args\": \"\"}},\"net\":[{{\"ip\":\"{}\", \"mask\":\"255.255.255.0\", \"mac\":\"{}\"}}], \"disks\":[{{\"path\":\"{}\"}}, {{\"path\":\"{}\"}}]}}",
|
||||
cpu_count,
|
||||
cpu_count,
|
||||
_fw_path,
|
||||
self.network.host_ip,
|
||||
self.network.guest_mac,
|
||||
self.disk_config.disk(DiskType::OperatingSystem).unwrap().as_str(),
|
||||
self.disk_config.disk(DiskType::CloudInit).unwrap().as_str(),
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", feature = "mshv"))]
|
||||
format! {"{{\"cpus\":{{\"boot_vcpus\":{},\"max_vcpus\":{}}},\"kernel\":{{\"path\":\"{}\"}},\"cmdline\":{{\"args\": \"{}\"}},\"net\":[{{\"ip\":\"{}\", \"mask\":\"255.255.255.0\", \"mac\":\"{}\"}}], \"disks\":[{{\"path\":\"{}\"}}, {{\"path\":\"{}\"}}]}}",
|
||||
cpu_count,
|
||||
cpu_count,
|
||||
_kernel_path,
|
||||
_kernel_cmd,
|
||||
self.network.host_ip,
|
||||
self.network.guest_mac,
|
||||
self.disk_config.disk(DiskType::OperatingSystem).unwrap().as_str(),
|
||||
self.disk_config.disk(DiskType::CloudInit).unwrap().as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cpu_count(&self) -> Result<u32, Error> {
|
||||
self.ssh_command("grep -c processor /proc/cpuinfo")?
|
||||
.trim()
|
||||
.parse()
|
||||
.map_err(Error::Parsing)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn get_initial_apicid(&self) -> Result<u32, Error> {
|
||||
self.ssh_command("grep \"initial apicid\" /proc/cpuinfo | grep -o \"[0-9]*\"")?
|
||||
.trim()
|
||||
.parse()
|
||||
.map_err(Error::Parsing)
|
||||
}
|
||||
|
||||
pub fn get_total_memory(&self) -> Result<u32, Error> {
|
||||
self.ssh_command("grep MemTotal /proc/meminfo | grep -o \"[0-9]*\"")?
|
||||
.trim()
|
||||
.parse()
|
||||
.map_err(Error::Parsing)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn get_total_memory_l2(&self) -> Result<u32, Error> {
|
||||
self.ssh_command_l2_1("grep MemTotal /proc/meminfo | grep -o \"[0-9]*\"")?
|
||||
.trim()
|
||||
.parse()
|
||||
.map_err(Error::Parsing)
|
||||
}
|
||||
|
||||
pub fn get_numa_node_memory(&self, node_id: usize) -> Result<u32, Error> {
|
||||
self.ssh_command(
|
||||
format!(
|
||||
"grep MemTotal /sys/devices/system/node/node{}/meminfo \
|
||||
| cut -d \":\" -f 2 | grep -o \"[0-9]*\"",
|
||||
node_id
|
||||
)
|
||||
.as_str(),
|
||||
)?
|
||||
.trim()
|
||||
.parse()
|
||||
.map_err(Error::Parsing)
|
||||
}
|
||||
|
||||
pub fn wait_vm_boot(&self, custom_timeout: Option<i32>) -> Result<(), Error> {
|
||||
self.network
|
||||
.wait_vm_boot(custom_timeout)
|
||||
.map_err(Error::WaitForBoot)
|
||||
}
|
||||
|
||||
pub fn check_numa_node_cpus(&self, node_id: usize, cpus: Vec<usize>) -> Result<(), Error> {
|
||||
for cpu in cpus.iter() {
|
||||
let cmd = format!(
|
||||
"[ -d \"/sys/devices/system/node/node{}/cpu{}\" ]",
|
||||
node_id, cpu
|
||||
);
|
||||
self.ssh_command(cmd.as_str())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_numa_node_distances(
|
||||
&self,
|
||||
node_id: usize,
|
||||
distances: &str,
|
||||
) -> Result<bool, Error> {
|
||||
let cmd = format!("cat /sys/devices/system/node/node{}/distance", node_id);
|
||||
if self.ssh_command(cmd.as_str())?.trim() == distances {
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_numa_common(
|
||||
&self,
|
||||
mem_ref: Option<&[u32]>,
|
||||
node_ref: Option<&[Vec<usize>]>,
|
||||
distance_ref: Option<&[&str]>,
|
||||
) {
|
||||
if let Some(mem_ref) = mem_ref {
|
||||
// Check each NUMA node has been assigned the right amount of
|
||||
// memory.
|
||||
for (i, &m) in mem_ref.iter().enumerate() {
|
||||
assert!(self.get_numa_node_memory(i).unwrap_or_default() > m);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(node_ref) = node_ref {
|
||||
// Check each NUMA node has been assigned the right CPUs set.
|
||||
for (i, n) in node_ref.iter().enumerate() {
|
||||
self.check_numa_node_cpus(i, n.clone()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(distance_ref) = distance_ref {
|
||||
// Check each NUMA node has been assigned the right distances.
|
||||
for (i, &d) in distance_ref.iter().enumerate() {
|
||||
assert!(self.check_numa_node_distances(i, d).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn check_sgx_support(&self) -> Result<(), Error> {
|
||||
self.ssh_command(
|
||||
"cpuid -l 0x7 -s 0 | tr -s [:space:] | grep -q 'SGX: \
|
||||
Software Guard Extensions supported = true'",
|
||||
)?;
|
||||
self.ssh_command(
|
||||
"cpuid -l 0x7 -s 0 | tr -s [:space:] | grep -q 'SGX_LC: \
|
||||
SGX launch config supported = true'",
|
||||
)?;
|
||||
self.ssh_command(
|
||||
"cpuid -l 0x12 -s 0 | tr -s [:space:] | grep -q 'SGX1 \
|
||||
supported = true'",
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_entropy(&self) -> Result<u32, Error> {
|
||||
self.ssh_command("cat /proc/sys/kernel/random/entropy_avail")?
|
||||
.trim()
|
||||
.parse()
|
||||
.map_err(Error::Parsing)
|
||||
}
|
||||
|
||||
pub fn get_pci_bridge_class(&self) -> Result<String, Error> {
|
||||
Ok(self
|
||||
.ssh_command("cat /sys/bus/pci/devices/0000:00:00.0/class")?
|
||||
.trim()
|
||||
.to_string())
|
||||
}
|
||||
|
||||
pub fn get_pci_device_ids(&self) -> Result<String, Error> {
|
||||
Ok(self
|
||||
.ssh_command("cat /sys/bus/pci/devices/*/device")?
|
||||
.trim()
|
||||
.to_string())
|
||||
}
|
||||
|
||||
pub fn get_pci_vendor_ids(&self) -> Result<String, Error> {
|
||||
Ok(self
|
||||
.ssh_command("cat /sys/bus/pci/devices/*/vendor")?
|
||||
.trim()
|
||||
.to_string())
|
||||
}
|
||||
|
||||
pub fn does_device_vendor_pair_match(
|
||||
&self,
|
||||
device_id: &str,
|
||||
vendor_id: &str,
|
||||
) -> Result<bool, Error> {
|
||||
// We are checking if console device's device id and vendor id pair matches
|
||||
let devices = self.get_pci_device_ids()?;
|
||||
let devices: Vec<&str> = devices.split('\n').collect();
|
||||
let vendors = self.get_pci_vendor_ids()?;
|
||||
let vendors: Vec<&str> = vendors.split('\n').collect();
|
||||
|
||||
for (index, d_id) in devices.iter().enumerate() {
|
||||
if *d_id == device_id {
|
||||
if let Some(v_id) = vendors.get(index) {
|
||||
if *v_id == vendor_id {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub fn valid_virtio_fs_cache_size(
|
||||
&self,
|
||||
dax: bool,
|
||||
cache_size: Option<u64>,
|
||||
) -> Result<bool, Error> {
|
||||
// SHM region is called different things depending on kernel
|
||||
let shm_region = self
|
||||
.ssh_command("sudo grep 'virtio[0-9]\\|virtio-pci-shm' /proc/iomem || true")?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
if shm_region.is_empty() {
|
||||
return Ok(!dax);
|
||||
}
|
||||
|
||||
// From this point, the region is not empty, hence it is an error
|
||||
// if DAX is off.
|
||||
if !dax {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let cache = if let Some(cache) = cache_size {
|
||||
cache
|
||||
} else {
|
||||
// 8Gib by default
|
||||
0x0002_0000_0000
|
||||
};
|
||||
|
||||
let args: Vec<&str> = shm_region.split(':').collect();
|
||||
if args.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let args: Vec<&str> = args[0].trim().split('-').collect();
|
||||
if args.len() != 2 {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let start_addr = u64::from_str_radix(args[0], 16).map_err(Error::Parsing)?;
|
||||
let end_addr = u64::from_str_radix(args[1], 16).map_err(Error::Parsing)?;
|
||||
|
||||
Ok(cache == (end_addr - start_addr + 1))
|
||||
}
|
||||
|
||||
pub fn check_vsock(&self, socket: &str) {
|
||||
// Listen from guest on vsock CID=3 PORT=16
|
||||
// SOCKET-LISTEN:<domain>:<protocol>:<local-address>
|
||||
let guest_ip = self.network.guest_ip.clone();
|
||||
let listen_socat = thread::spawn(move || {
|
||||
ssh_command_ip("sudo socat - SOCKET-LISTEN:40:0:x00x00x10x00x00x00x03x00x00x00x00x00x00x00 > vsock_log", &guest_ip, DEFAULT_SSH_RETRIES, DEFAULT_SSH_TIMEOUT).unwrap();
|
||||
});
|
||||
|
||||
// Make sure socat is listening, which might take a few second on slow systems
|
||||
thread::sleep(std::time::Duration::new(10, 0));
|
||||
|
||||
// Write something to vsock from the host
|
||||
assert!(exec_host_command_status(&format!(
|
||||
"echo -e \"CONNECT 16\\nHelloWorld!\" | socat - UNIX-CONNECT:{}",
|
||||
socket
|
||||
))
|
||||
.success());
|
||||
|
||||
// Wait for the thread to terminate.
|
||||
listen_socat.join().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
self.ssh_command("cat vsock_log").unwrap().trim(),
|
||||
"HelloWorld!"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn check_nvidia_gpu(&self) {
|
||||
// Run CUDA sample to validate it can find the device
|
||||
let device_query_result = self
|
||||
.ssh_command("sudo /root/NVIDIA_CUDA-11.3_Samples/bin/x86_64/linux/release/deviceQuery")
|
||||
.unwrap();
|
||||
assert!(device_query_result.contains("Detected 1 CUDA Capable device"));
|
||||
assert!(device_query_result.contains("Device 0: \"NVIDIA Tesla T4\""));
|
||||
assert!(device_query_result.contains("Result = PASS"));
|
||||
|
||||
// Run NVIDIA DCGM Diagnostics to validate the device is functional
|
||||
self.ssh_command("sudo nv-hostengine").unwrap();
|
||||
|
||||
assert!(self
|
||||
.ssh_command("sudo dcgmi discovery -l")
|
||||
.unwrap()
|
||||
.contains("Name: NVIDIA Tesla T4"));
|
||||
assert_eq!(
|
||||
self.ssh_command("sudo dcgmi diag -r 'diagnostic' | grep Pass | wc -l")
|
||||
.unwrap()
|
||||
.trim(),
|
||||
"10"
|
||||
);
|
||||
}
|
||||
|
||||
pub fn reboot_linux(&self, current_reboot_count: u32, custom_timeout: Option<i32>) {
|
||||
let list_boots_cmd = "sudo journalctl --list-boots | wc -l";
|
||||
let boot_count = self
|
||||
.ssh_command(list_boots_cmd)
|
||||
.unwrap()
|
||||
.trim()
|
||||
.parse::<u32>()
|
||||
.unwrap_or_default();
|
||||
|
||||
assert_eq!(boot_count, current_reboot_count + 1);
|
||||
self.ssh_command("sudo reboot").unwrap();
|
||||
|
||||
self.wait_vm_boot(custom_timeout).unwrap();
|
||||
let boot_count = self
|
||||
.ssh_command(list_boots_cmd)
|
||||
.unwrap()
|
||||
.trim()
|
||||
.parse::<u32>()
|
||||
.unwrap_or_default();
|
||||
assert_eq!(boot_count, current_reboot_count + 2);
|
||||
}
|
||||
|
||||
pub fn enable_memory_hotplug(&self) {
|
||||
self.ssh_command("echo online | sudo tee /sys/devices/system/memory/auto_online_blocks")
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn check_devices_common(&self, socket: Option<&String>, console_text: Option<&String>) {
|
||||
// Check block devices are readable
|
||||
self.ssh_command("sudo dd if=/dev/vda of=/dev/null bs=1M iflag=direct count=1024")
|
||||
.unwrap();
|
||||
self.ssh_command("sudo dd if=/dev/vdb of=/dev/null bs=1M iflag=direct count=8")
|
||||
.unwrap();
|
||||
// Check if the rng device is readable
|
||||
self.ssh_command("sudo head -c 1000 /dev/hwrng > /dev/null")
|
||||
.unwrap();
|
||||
// Check vsock
|
||||
if let Some(socket) = socket {
|
||||
self.check_vsock(socket.as_str());
|
||||
}
|
||||
// Check if the console is usable
|
||||
if let Some(console_text) = console_text {
|
||||
let console_cmd = format!("echo {} | sudo tee /dev/hvc0", console_text);
|
||||
self.ssh_command(&console_cmd).unwrap();
|
||||
}
|
||||
// The net device is 'automatically' exercised through the above 'ssh' commands
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GuestCommand<'a> {
|
||||
command: Command,
|
||||
guest: &'a Guest,
|
||||
capture_output: bool,
|
||||
print_cmd: bool,
|
||||
}
|
||||
|
||||
impl<'a> GuestCommand<'a> {
|
||||
pub fn new(guest: &'a Guest) -> Self {
|
||||
Self::new_with_binary_name(guest, "cloud-hypervisor")
|
||||
}
|
||||
|
||||
pub fn new_with_binary_name(guest: &'a Guest, binary_name: &str) -> Self {
|
||||
Self {
|
||||
command: Command::new(clh_command(binary_name)),
|
||||
guest,
|
||||
capture_output: false,
|
||||
print_cmd: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn capture_output(&mut self) -> &mut Self {
|
||||
self.capture_output = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_print_cmd(&mut self, print_cmd: bool) -> &mut Self {
|
||||
self.print_cmd = print_cmd;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn spawn(&mut self) -> io::Result<Child> {
|
||||
if self.print_cmd {
|
||||
println!(
|
||||
"\n\n==== Start cloud-hypervisor command-line ====\n\n\
|
||||
{:?}\n\
|
||||
\n==== End cloud-hypervisor command-line ====\n\n",
|
||||
self.command
|
||||
);
|
||||
}
|
||||
|
||||
if self.capture_output {
|
||||
let child = self
|
||||
.command
|
||||
.arg("-v")
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
let fd = child.stdout.as_ref().unwrap().as_raw_fd();
|
||||
let pipesize = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) };
|
||||
let fd = child.stderr.as_ref().unwrap().as_raw_fd();
|
||||
let pipesize1 = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) };
|
||||
|
||||
if pipesize >= PIPE_SIZE && pipesize1 >= PIPE_SIZE {
|
||||
Ok(child)
|
||||
} else {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"resizing pipe w/ 'fnctl' failed!",
|
||||
))
|
||||
}
|
||||
} else {
|
||||
self.command.arg("-v").spawn()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn args<I, S>(&mut self, args: I) -> &mut Self
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<OsStr>,
|
||||
{
|
||||
self.command.args(args);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn default_disks(&mut self) -> &mut Self {
|
||||
if self.guest.disk_config.disk(DiskType::CloudInit).is_some() {
|
||||
self.args(&[
|
||||
"--disk",
|
||||
format!(
|
||||
"path={}",
|
||||
self.guest
|
||||
.disk_config
|
||||
.disk(DiskType::OperatingSystem)
|
||||
.unwrap()
|
||||
)
|
||||
.as_str(),
|
||||
format!(
|
||||
"path={}",
|
||||
self.guest.disk_config.disk(DiskType::CloudInit).unwrap()
|
||||
)
|
||||
.as_str(),
|
||||
])
|
||||
} else {
|
||||
self.args(&[
|
||||
"--disk",
|
||||
format!(
|
||||
"path={}",
|
||||
self.guest
|
||||
.disk_config
|
||||
.disk(DiskType::OperatingSystem)
|
||||
.unwrap()
|
||||
)
|
||||
.as_str(),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_net(&mut self) -> &mut Self {
|
||||
self.args(&["--net", self.guest.default_net_string().as_str()])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clh_command(cmd: &str) -> String {
|
||||
env::var("BUILD_TARGET").map_or(
|
||||
format!("target/x86_64-unknown-linux-gnu/release/{}", cmd),
|
||||
|target| format!("target/{}/release/{}", target, cmd),
|
||||
)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,8 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.52"
|
||||
libc = "0.2.112"
|
||||
anyhow = "1.0.55"
|
||||
libc = "0.2.119"
|
||||
log = "0.4.14"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
|
||||
@@ -8,7 +8,7 @@ license = "Apache-2.0"
|
||||
[dependencies]
|
||||
byteorder = "1.4.3"
|
||||
crc32c = "0.6.1"
|
||||
libc = "0.2.112"
|
||||
libc = "0.2.119"
|
||||
log = "0.4.14"
|
||||
remain = "0.2.2"
|
||||
thiserror = "1.0"
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "vhost_user_backend"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
epoll = "4.3.1"
|
||||
libc = "0.2.112"
|
||||
log = "0.4.14"
|
||||
virtio-bindings = "0.1.0"
|
||||
virtio-queue = { path = "../virtio-queue" }
|
||||
vm-memory = { version = "0.7.0", features = ["backend-bitmap"] }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = "0.9.0"
|
||||
vhost = { version = "0.3.0", features = ["vhost-user-slave"] }
|
||||
@@ -1,960 +0,0 @@
|
||||
// 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
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::error;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread;
|
||||
use vhost::vhost_user::message::{
|
||||
VhostUserConfigFlags, VhostUserInflight, VhostUserMemoryRegion, VhostUserProtocolFeatures,
|
||||
VhostUserSingleMemoryRegion, VhostUserVirtioFeatures, VhostUserVringAddrFlags,
|
||||
VhostUserVringState,
|
||||
};
|
||||
use vhost::vhost_user::SlaveReqHandler;
|
||||
use vhost::vhost_user::{
|
||||
Error as VhostUserError, Listener, Result as VhostUserResult, SlaveFsCacheReq, SlaveListener,
|
||||
VhostUserSlaveReqHandlerMut,
|
||||
};
|
||||
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
||||
use virtio_queue::Queue;
|
||||
use vm_memory::guest_memory::FileOffset;
|
||||
use vm_memory::GuestAddressSpace;
|
||||
use vm_memory::{bitmap::AtomicBitmap, GuestAddress, GuestMemoryAtomic, MmapRegion};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
||||
pub type GuestRegionMmap = vm_memory::GuestRegionMmap<AtomicBitmap>;
|
||||
|
||||
const MAX_MEM_SLOTS: u64 = 32;
|
||||
|
||||
#[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;
|
||||
|
||||
/// Available virtio features.
|
||||
fn features(&self) -> u64;
|
||||
|
||||
/// Acked virtio features.
|
||||
fn acked_features(&mut self, _features: u64) {}
|
||||
|
||||
/// Virtio protocol features.
|
||||
fn protocol_features(&self) -> VhostUserProtocolFeatures;
|
||||
|
||||
/// Tell the backend if EVENT_IDX has been negotiated.
|
||||
fn set_event_idx(&mut self, enabled: bool);
|
||||
|
||||
/// 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(
|
||||
&self,
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
vrings: &[Arc<RwLock<Vring>>],
|
||||
thread_id: usize,
|
||||
) -> 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(())
|
||||
}
|
||||
|
||||
/// Provide an exit EventFd
|
||||
/// When this EventFd is written to the worker thread will exit. An optional id may
|
||||
/// also be provided, if it not provided then the exit event will be first event id
|
||||
/// after the last queue
|
||||
fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, Option<u16>)> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Set slave fd.
|
||||
/// A default implementation is provided as we cannot expect all backends
|
||||
/// to implement this function.
|
||||
fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {}
|
||||
|
||||
fn queues_per_thread(&self) -> Vec<u64> {
|
||||
vec![0xffff_ffff]
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
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, backend: Arc<RwLock<S>>) -> Result<Self> {
|
||||
let handler = Arc::new(Mutex::new(
|
||||
VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?,
|
||||
));
|
||||
|
||||
Ok(VhostUserDaemon {
|
||||
name,
|
||||
handler,
|
||||
main_thread: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Listen 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_server(&mut self, listener: Listener) -> Result<()> {
|
||||
let mut slave_listener = SlaveListener::new(listener, 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(())
|
||||
}
|
||||
|
||||
/// 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)
|
||||
/// hangs up.
|
||||
pub fn start_client(&mut self, socket_path: &str) -> Result<()> {
|
||||
let mut slave_handler = SlaveReqHandler::connect(socket_path, self.handler.clone())
|
||||
.map_err(Error::CreateSlaveReqHandler)?;
|
||||
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() {
|
||||
match handle.join().map_err(Error::WaitDaemon)? {
|
||||
Ok(()) => Ok(()),
|
||||
Err(Error::HandleRequest(VhostUserError::SocketBroken(_))) => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
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_workers(&self) -> Vec<Arc<VringWorker>> {
|
||||
self.handler.lock().unwrap().get_vring_workers()
|
||||
}
|
||||
}
|
||||
|
||||
struct AddrMapping {
|
||||
vmm_addr: u64,
|
||||
size: u64,
|
||||
gpa_base: u64,
|
||||
}
|
||||
|
||||
pub struct Vring {
|
||||
queue: Queue<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
kick: Option<EventFd>,
|
||||
call: Option<EventFd>,
|
||||
#[allow(dead_code)]
|
||||
err: Option<EventFd>,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl Vring {
|
||||
fn new(mem: GuestMemoryAtomic<GuestMemoryMmap>, max_queue_size: u16) -> Self {
|
||||
Vring {
|
||||
queue: Queue::new(mem, max_queue_size),
|
||||
kick: None,
|
||||
call: None,
|
||||
err: None,
|
||||
enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mut_queue(&mut self) -> &mut Queue<GuestMemoryAtomic<GuestMemoryMmap>> {
|
||||
&mut self.queue
|
||||
}
|
||||
|
||||
pub fn signal_used_queue(&mut self) -> result::Result<(), io::Error> {
|
||||
if let Some(call) = self.call.as_ref() {
|
||||
call.write(1)
|
||||
} else {
|
||||
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>>>,
|
||||
exit_event_id: Option<u16>,
|
||||
thread_id: usize,
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VringEpollHandler<S> {
|
||||
fn handle_event(
|
||||
&self,
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
) -> VringEpollHandlerResult<bool> {
|
||||
if self.exit_event_id == Some(device_event) {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let num_queues = self.vrings.len();
|
||||
if (device_event as usize) < num_queues {
|
||||
// If the vring is not enabled, it should not be processed.
|
||||
// But let's not read it (hence lose it) in case it is later enabled.
|
||||
if !self.vrings[device_event as usize].read().unwrap().enabled {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if let Some(kick) = &self.vrings[device_event as usize].read().unwrap().kick {
|
||||
kick.read()
|
||||
.map_err(VringEpollHandlerError::HandleEventReadKick)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.backend
|
||||
.read()
|
||||
.unwrap()
|
||||
.handle_event(device_event, evset, &self.vrings, self.thread_id)
|
||||
.map_err(VringEpollHandlerError::HandleEventBackendHandling)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Errors related to vring worker.
|
||||
enum VringWorkerError {
|
||||
/// Failed while waiting for events.
|
||||
EpollWait(io::Error),
|
||||
/// Failed to handle the event.
|
||||
HandleEvent(VringEpollHandlerError),
|
||||
}
|
||||
|
||||
/// Result of vring worker operations.
|
||||
type VringWorkerResult<T> = std::result::Result<T, VringWorkerError>;
|
||||
|
||||
pub struct VringWorker {
|
||||
epoll_file: File,
|
||||
}
|
||||
|
||||
impl AsRawFd for VringWorker {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.epoll_file.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
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_file.as_raw_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 handler
|
||||
.handle_event(ev_type, evset)
|
||||
.map_err(VringWorkerError::HandleEvent)?
|
||||
{
|
||||
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_file.as_raw_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_file.as_raw_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,
|
||||
/// Could not register exit event
|
||||
RegisterExitEvent(io::Error),
|
||||
}
|
||||
|
||||
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"),
|
||||
VhostUserHandlerError::RegisterExitEvent(e) => {
|
||||
write!(f, "Failed to register exit event: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>>,
|
||||
workers: Vec<Arc<VringWorker>>,
|
||||
owned: bool,
|
||||
acked_features: u64,
|
||||
num_queues: usize,
|
||||
max_queue_size: usize,
|
||||
queues_per_thread: Vec<u64>,
|
||||
mappings: Vec<AddrMapping>,
|
||||
guest_memory: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
vrings: Vec<Arc<RwLock<Vring>>>,
|
||||
worker_threads: Vec<thread::JoinHandle<VringWorkerResult<()>>>,
|
||||
}
|
||||
|
||||
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 queues_per_thread = backend.read().unwrap().queues_per_thread();
|
||||
let guest_memory = GuestMemoryAtomic::new(GuestMemoryMmap::new());
|
||||
|
||||
let mut vrings: Vec<Arc<RwLock<Vring>>> = Vec::new();
|
||||
for _ in 0..num_queues {
|
||||
let vring = Arc::new(RwLock::new(Vring::new(
|
||||
guest_memory.clone(),
|
||||
max_queue_size as u16,
|
||||
)));
|
||||
vrings.push(vring);
|
||||
}
|
||||
|
||||
let mut workers = Vec::new();
|
||||
let mut worker_threads = Vec::new();
|
||||
for (thread_id, queues_mask) in queues_per_thread.iter().enumerate() {
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?;
|
||||
// Use 'File' to enforce closing on 'epoll_fd'
|
||||
let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
|
||||
|
||||
let vring_worker = Arc::new(VringWorker { epoll_file });
|
||||
let worker = vring_worker.clone();
|
||||
|
||||
let exit_event_id = if let Some((exit_event_fd, exit_event_id)) =
|
||||
backend.read().unwrap().exit_event(thread_id)
|
||||
{
|
||||
let exit_event_id = exit_event_id.unwrap_or(num_queues as u16);
|
||||
worker
|
||||
.register_listener(
|
||||
exit_event_fd.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(exit_event_id),
|
||||
)
|
||||
.map_err(VhostUserHandlerError::RegisterExitEvent)?;
|
||||
Some(exit_event_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut thread_vrings: Vec<Arc<RwLock<Vring>>> = Vec::new();
|
||||
for (index, vring) in vrings.iter().enumerate() {
|
||||
if (queues_mask >> index) & 1u64 == 1u64 {
|
||||
thread_vrings.push(vring.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let vring_handler = VringEpollHandler {
|
||||
backend: backend.clone(),
|
||||
vrings: thread_vrings,
|
||||
exit_event_id,
|
||||
thread_id,
|
||||
};
|
||||
|
||||
let worker_thread = thread::Builder::new()
|
||||
.name("vring_worker".to_string())
|
||||
.spawn(move || vring_worker.run(vring_handler))
|
||||
.map_err(VhostUserHandlerError::SpawnVringWorker)?;
|
||||
|
||||
workers.push(worker);
|
||||
worker_threads.push(worker_thread);
|
||||
}
|
||||
|
||||
Ok(VhostUserHandler {
|
||||
backend,
|
||||
workers,
|
||||
owned: false,
|
||||
acked_features: 0,
|
||||
num_queues,
|
||||
max_queue_size,
|
||||
queues_per_thread,
|
||||
mappings: Vec::new(),
|
||||
guest_memory,
|
||||
vrings,
|
||||
worker_threads,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_vring_workers(&self) -> Vec<Arc<VringWorker>> {
|
||||
self.workers.clone()
|
||||
}
|
||||
|
||||
fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult<u64> {
|
||||
for mapping in self.mappings.iter() {
|
||||
if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size {
|
||||
return Ok(vmm_va - mapping.vmm_addr + mapping.gpa_base);
|
||||
}
|
||||
}
|
||||
|
||||
Err(VhostUserHandlerError::MissingMemoryMapping)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VhostUserSlaveReqHandlerMut 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.acked_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 (features & !self.backend.read().unwrap().features()) != 0 {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
self.acked_features = features;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
self.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.acked_features(self.acked_features);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_protocol_features(&mut self) -> VhostUserResult<VhostUserProtocolFeatures> {
|
||||
Ok(self.backend.read().unwrap().protocol_features())
|
||||
}
|
||||
|
||||
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.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_mem_table(
|
||||
&mut self,
|
||||
ctx: &[VhostUserMemoryRegion],
|
||||
mut files: Vec<File>,
|
||||
) -> 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 region in ctx.iter() {
|
||||
let g_addr = GuestAddress(region.guest_phys_addr);
|
||||
let len = region.memory_size as usize;
|
||||
let f_off = FileOffset::new(files.remove(0), region.mmap_offset);
|
||||
|
||||
regions.push((g_addr, len, Some(f_off)));
|
||||
mappings.push(AddrMapping {
|
||||
vmm_addr: region.user_addr,
|
||||
size: region.memory_size,
|
||||
gpa_base: region.guest_phys_addr,
|
||||
});
|
||||
}
|
||||
|
||||
let mem = GuestMemoryMmap::from_ranges_with_files(regions).map_err(|e| {
|
||||
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||
})?;
|
||||
self.guest_memory.lock().unwrap().replace(mem);
|
||||
self.mappings = 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
|
||||
.state
|
||||
.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.mappings.is_empty() {
|
||||
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
|
||||
.state
|
||||
.desc_table = GuestAddress(desc_table);
|
||||
self.vrings[index as usize]
|
||||
.write()
|
||||
.unwrap()
|
||||
.queue
|
||||
.state
|
||||
.avail_ring = GuestAddress(avail_ring);
|
||||
self.vrings[index as usize]
|
||||
.write()
|
||||
.unwrap()
|
||||
.queue
|
||||
.state
|
||||
.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
|
||||
.set_next_avail(base as u16);
|
||||
|
||||
let event_idx: bool = (self.acked_features & (1 << VIRTIO_RING_F_EVENT_IDX)) != 0;
|
||||
self.vrings[index as usize]
|
||||
.write()
|
||||
.unwrap()
|
||||
.mut_queue()
|
||||
.set_event_idx(event_idx);
|
||||
self.backend.write().unwrap().set_event_idx(event_idx);
|
||||
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
|
||||
.state
|
||||
.ready = false;
|
||||
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
|
||||
for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() {
|
||||
let shifted_queues_mask = queues_mask >> index;
|
||||
if shifted_queues_mask & 1u64 == 1u64 {
|
||||
let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones();
|
||||
self.workers[thread_index]
|
||||
.unregister_listener(
|
||||
fd.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(evt_idx),
|
||||
)
|
||||
.map_err(VhostUserError::ReqHandlerError)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let next_avail = self.vrings[index as usize]
|
||||
.read()
|
||||
.unwrap()
|
||||
.queue
|
||||
.next_avail();
|
||||
|
||||
Ok(VhostUserVringState::new(index, u32::from(next_avail)))
|
||||
}
|
||||
|
||||
fn set_vring_kick(&mut self, index: u8, fd: Option<File>) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
self.vrings[index as usize].write().unwrap().kick =
|
||||
fd.map(|x| unsafe { EventFd::from_raw_fd(x.into_raw_fd()) });
|
||||
|
||||
// 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
|
||||
.state
|
||||
.ready = true;
|
||||
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
|
||||
for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() {
|
||||
let shifted_queues_mask = queues_mask >> index;
|
||||
if shifted_queues_mask & 1u64 == 1u64 {
|
||||
let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones();
|
||||
self.workers[thread_index]
|
||||
.register_listener(
|
||||
fd.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(evt_idx),
|
||||
)
|
||||
.map_err(VhostUserError::ReqHandlerError)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_call(&mut self, index: u8, fd: Option<File>) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
self.vrings[index as usize].write().unwrap().call =
|
||||
fd.map(|x| unsafe { EventFd::from_raw_fd(x.into_raw_fd()) });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_err(&mut self, index: u8, fd: Option<File>) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
self.vrings[index as usize].write().unwrap().err =
|
||||
fd.map(|x| unsafe { EventFd::from_raw_fd(x.into_raw_fd()) });
|
||||
|
||||
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>> {
|
||||
Ok(self.backend.read().unwrap().get_config(offset, size))
|
||||
}
|
||||
|
||||
fn set_config(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
buf: &[u8],
|
||||
_flags: VhostUserConfigFlags,
|
||||
) -> VhostUserResult<()> {
|
||||
self.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_config(offset, buf)
|
||||
.map_err(VhostUserError::ReqHandlerError)
|
||||
}
|
||||
|
||||
fn set_slave_req_fd(&mut self, vu_req: SlaveFsCacheReq) {
|
||||
self.backend.write().unwrap().set_slave_req_fd(vu_req);
|
||||
}
|
||||
|
||||
fn get_max_mem_slots(&mut self) -> VhostUserResult<u64> {
|
||||
Ok(MAX_MEM_SLOTS)
|
||||
}
|
||||
|
||||
fn add_mem_region(
|
||||
&mut self,
|
||||
region: &VhostUserSingleMemoryRegion,
|
||||
fd: File,
|
||||
) -> VhostUserResult<()> {
|
||||
let mmap_region = MmapRegion::from_file(
|
||||
FileOffset::new(fd, region.mmap_offset),
|
||||
region.memory_size as usize,
|
||||
)
|
||||
.map_err(|e| VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)))?;
|
||||
let guest_region = Arc::new(
|
||||
GuestRegionMmap::new(mmap_region, GuestAddress(region.guest_phys_addr)).map_err(
|
||||
|e| VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)),
|
||||
)?,
|
||||
);
|
||||
|
||||
let guest_memory = self
|
||||
.guest_memory
|
||||
.memory()
|
||||
.insert_region(guest_region)
|
||||
.map_err(|e| {
|
||||
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||
})?;
|
||||
self.guest_memory.lock().unwrap().replace(guest_memory);
|
||||
|
||||
self.mappings.push(AddrMapping {
|
||||
vmm_addr: region.user_addr,
|
||||
size: region.memory_size,
|
||||
gpa_base: region.guest_phys_addr,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_mem_region(&mut self, region: &VhostUserSingleMemoryRegion) -> VhostUserResult<()> {
|
||||
let (guest_memory, _) = self
|
||||
.guest_memory
|
||||
.memory()
|
||||
.remove_region(GuestAddress(region.guest_phys_addr), region.memory_size)
|
||||
.map_err(|e| {
|
||||
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||
})?;
|
||||
self.guest_memory.lock().unwrap().replace(guest_memory);
|
||||
|
||||
self.mappings
|
||||
.retain(|mapping| mapping.gpa_base != region.guest_phys_addr);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_inflight_fd(
|
||||
&mut self,
|
||||
_: &VhostUserInflight,
|
||||
) -> VhostUserResult<(VhostUserInflight, File)> {
|
||||
std::unimplemented!()
|
||||
}
|
||||
|
||||
fn set_inflight_fd(&mut self, _: &VhostUserInflight, _: File) -> VhostUserResult<()> {
|
||||
std::unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> Drop for VhostUserHandler<S> {
|
||||
fn drop(&mut self) {
|
||||
for thread in self.worker_threads.drain(..) {
|
||||
if let Err(e) = thread.join() {
|
||||
error!("Error in vring worker: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,15 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
block_util = { path = "../block_util" }
|
||||
clap = { version = "3.0.10", features = ["wrap_help"] }
|
||||
clap = { version = "3.1.5", features = ["wrap_help"] }
|
||||
env_logger = "0.9.0"
|
||||
epoll = "4.3.1"
|
||||
libc = "0.2.112"
|
||||
libc = "0.2.119"
|
||||
log = "0.4.14"
|
||||
option_parser = { path = "../option_parser" }
|
||||
qcow = { path = "../qcow" }
|
||||
vhost_user_backend = { path = "../vhost_user_backend" }
|
||||
vhost = { version = "0.3.0", features = ["vhost-user-slave"] }
|
||||
vhost-user-backend = { git = "https://github.com/rust-vmm/vhost-user-backend", branch = "main" }
|
||||
virtio-bindings = "0.1.0"
|
||||
vm-memory = "0.7.0"
|
||||
vmm-sys-util = "0.9.0"
|
||||
|
||||
@@ -23,18 +23,19 @@ use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::sync::{Arc, Mutex, RwLock, RwLockWriteGuard};
|
||||
use std::time::Instant;
|
||||
use std::vec::Vec;
|
||||
use std::{convert, error, fmt, io};
|
||||
use vhost::vhost_user::message::*;
|
||||
use vhost::vhost_user::Listener;
|
||||
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring};
|
||||
use vhost_user_backend::{VhostUserBackendMut, VhostUserDaemon, VringRwLock, VringState, VringT};
|
||||
use virtio_bindings::bindings::virtio_blk::*;
|
||||
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
||||
use vm_memory::ByteValued;
|
||||
use vm_memory::Bytes;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vm_memory::{bitmap::AtomicBitmap, ByteValued, Bytes, GuestMemoryAtomic};
|
||||
use vmm_sys_util::{epoll::EventSet, eventfd::EventFd};
|
||||
|
||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
||||
|
||||
const SECTOR_SHIFT: u8 = 9;
|
||||
const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
||||
@@ -111,13 +112,16 @@ impl VhostUserBlkThread {
|
||||
})
|
||||
}
|
||||
|
||||
fn process_queue(&mut self, vring: &mut Vring) -> bool {
|
||||
let mut used_any = false;
|
||||
fn process_queue(
|
||||
&mut self,
|
||||
vring: &mut RwLockWriteGuard<VringState<GuestMemoryAtomic<GuestMemoryMmap>>>,
|
||||
) -> bool {
|
||||
let mut used_desc_heads = Vec::new();
|
||||
|
||||
while let Some(mut desc_chain) = vring.mut_queue().iter().unwrap().next() {
|
||||
for mut desc_chain in vring.get_queue_mut().iter().unwrap() {
|
||||
debug!("got an element in the queue");
|
||||
let len;
|
||||
match Request::parse(&mut desc_chain) {
|
||||
match Request::parse(&mut desc_chain, None) {
|
||||
Ok(mut request) => {
|
||||
debug!("element is a valid request");
|
||||
request.set_writeback(self.writeback.load(Ordering::Acquire));
|
||||
@@ -147,29 +151,33 @@ impl VhostUserBlkThread {
|
||||
}
|
||||
}
|
||||
|
||||
used_desc_heads.push((desc_chain.head_index(), len));
|
||||
}
|
||||
|
||||
let mut needs_signalling = false;
|
||||
for (desc_head, len) in used_desc_heads.iter() {
|
||||
if self.event_idx {
|
||||
let queue = vring.mut_queue();
|
||||
if queue.add_used(desc_chain.head_index(), len).is_ok() {
|
||||
let queue = vring.get_queue_mut();
|
||||
if queue.add_used(*desc_head, *len).is_ok() {
|
||||
if queue.needs_notification().unwrap() {
|
||||
debug!("signalling queue");
|
||||
vring.signal_used_queue().unwrap();
|
||||
needs_signalling = true;
|
||||
} else {
|
||||
debug!("omitting signal (event_idx)");
|
||||
}
|
||||
used_any = true;
|
||||
}
|
||||
} else {
|
||||
debug!("signalling queue");
|
||||
vring
|
||||
.mut_queue()
|
||||
.add_used(desc_chain.head_index(), len)
|
||||
.unwrap();
|
||||
vring.signal_used_queue().unwrap();
|
||||
used_any = true;
|
||||
vring.get_queue_mut().add_used(*desc_head, *len).unwrap();
|
||||
needs_signalling = true;
|
||||
}
|
||||
}
|
||||
|
||||
used_any
|
||||
if needs_signalling {
|
||||
vring.signal_used_queue().unwrap();
|
||||
}
|
||||
|
||||
!used_desc_heads.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +280,9 @@ impl VhostUserBlkBackend {
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserBackend for VhostUserBlkBackend {
|
||||
impl VhostUserBackendMut<VringRwLock<GuestMemoryAtomic<GuestMemoryMmap>>, AtomicBitmap>
|
||||
for VhostUserBlkBackend
|
||||
{
|
||||
fn num_queues(&self) -> usize {
|
||||
self.config.num_queues as usize
|
||||
}
|
||||
@@ -316,13 +326,13 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&self,
|
||||
&mut self,
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
vrings: &[Arc<RwLock<Vring>>],
|
||||
evset: EventSet,
|
||||
vrings: &[VringRwLock<GuestMemoryAtomic<GuestMemoryMmap>>],
|
||||
thread_id: usize,
|
||||
) -> VhostUserBackendResult<bool> {
|
||||
if evset != epoll::Events::EPOLLIN {
|
||||
if evset != EventSet::IN {
|
||||
return Err(Error::HandleEventNotEpollIn.into());
|
||||
}
|
||||
|
||||
@@ -331,7 +341,7 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
||||
let mut thread = self.threads[thread_id].lock().unwrap();
|
||||
match device_event {
|
||||
0 => {
|
||||
let mut vring = vrings[0].write().unwrap();
|
||||
let mut vring = vrings[0].get_mut();
|
||||
|
||||
if self.poll_queue {
|
||||
// Actively poll the queue until POLL_QUEUE_US has passed
|
||||
@@ -352,7 +362,7 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
||||
// calling process_queue() until it stops finding new
|
||||
// requests on the queue.
|
||||
loop {
|
||||
vring.mut_queue().enable_notification().unwrap();
|
||||
vring.get_queue_mut().enable_notification().unwrap();
|
||||
if !thread.process_queue(&mut vring) {
|
||||
break;
|
||||
}
|
||||
@@ -386,22 +396,27 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exit_event(&self, thread_index: usize) -> Option<(EventFd, Option<u16>)> {
|
||||
// The exit event is placed after the queue, which is event index 1.
|
||||
Some((
|
||||
fn exit_event(&self, thread_index: usize) -> Option<EventFd> {
|
||||
Some(
|
||||
self.threads[thread_index]
|
||||
.lock()
|
||||
.unwrap()
|
||||
.kill_evt
|
||||
.try_clone()
|
||||
.unwrap(),
|
||||
Some(1),
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
fn queues_per_thread(&self) -> Vec<u64> {
|
||||
self.queues_per_thread.clone()
|
||||
}
|
||||
|
||||
fn update_memory(
|
||||
&mut self,
|
||||
_mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
) -> VhostUserBackendResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct VhostUserBlkBackendConfig {
|
||||
@@ -491,11 +506,16 @@ pub fn start_block_backend(backend_command: &str) {
|
||||
let listener = Listener::new(&backend_config.socket, true).unwrap();
|
||||
|
||||
let name = "vhost-user-blk-backend";
|
||||
let mut blk_daemon = VhostUserDaemon::new(name.to_string(), blk_backend.clone()).unwrap();
|
||||
let mut blk_daemon = VhostUserDaemon::new(
|
||||
name.to_string(),
|
||||
blk_backend.clone(),
|
||||
GuestMemoryAtomic::new(GuestMemoryMmap::new()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
debug!("blk_daemon is created!\n");
|
||||
|
||||
if let Err(e) = blk_daemon.start_server(listener) {
|
||||
if let Err(e) = blk_daemon.start(listener) {
|
||||
error!(
|
||||
"Failed to start daemon for vhost-user-block with error: {:?}\n",
|
||||
e
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
extern crate clap;
|
||||
extern crate vhost_user_block;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use clap::{Arg, Command};
|
||||
use vhost_user_block::start_block_backend;
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let cmd_arguments = App::new("vhost-user-blk backend")
|
||||
let cmd_arguments = Command::new("vhost-user-blk backend")
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.about("Launch a vhost-user-blk backend.")
|
||||
|
||||
@@ -5,15 +5,15 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "3.0.10", features = ["wrap_help"] }
|
||||
clap = { version = "3.1.5", features = ["wrap_help"] }
|
||||
env_logger = "0.9.0"
|
||||
epoll = "4.3.1"
|
||||
libc = "0.2.112"
|
||||
libc = "0.2.119"
|
||||
log = "0.4.14"
|
||||
net_util = { path = "../net_util" }
|
||||
option_parser = { path = "../option_parser" }
|
||||
vhost_user_backend = { path = "../vhost_user_backend" }
|
||||
vhost = { version = "0.3.0", features = ["vhost-user-slave"] }
|
||||
vhost-user-backend = { git = "https://github.com/rust-vmm/vhost-user-backend", branch = "main" }
|
||||
virtio-bindings = "0.1.0"
|
||||
vm-memory = "0.7.0"
|
||||
vmm-sys-util = "0.9.0"
|
||||
|
||||
@@ -16,15 +16,18 @@ use option_parser::{OptionParser, OptionParserError};
|
||||
use std::fmt;
|
||||
use std::io::{self};
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::process;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::vec::Vec;
|
||||
use vhost::vhost_user::message::*;
|
||||
use vhost::vhost_user::Listener;
|
||||
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
|
||||
use vhost_user_backend::{VhostUserBackendMut, VhostUserDaemon, VringRwLock, VringT};
|
||||
use virtio_bindings::bindings::virtio_net::*;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vm_memory::{bitmap::AtomicBitmap, GuestMemoryAtomic};
|
||||
use vmm_sys_util::{epoll::EventSet, eventfd::EventFd};
|
||||
|
||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
|
||||
@@ -88,17 +91,18 @@ impl VhostUserNetThread {
|
||||
tx_tap_listening: false,
|
||||
epoll_fd: None,
|
||||
counters: NetCounters::default(),
|
||||
tap_rx_event_id: 2,
|
||||
tap_tx_event_id: 3,
|
||||
tap_rx_event_id: 3,
|
||||
tap_tx_event_id: 4,
|
||||
rx_desc_avail: false,
|
||||
rx_rate_limiter: None,
|
||||
tx_rate_limiter: None,
|
||||
access_platform: None,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_vring_worker(&mut self, vring_worker: Option<Arc<VringWorker>>) {
|
||||
self.net.epoll_fd = Some(vring_worker.as_ref().unwrap().as_raw_fd());
|
||||
pub fn set_epoll_fd(&mut self, fd: RawFd) {
|
||||
self.net.epoll_fd = Some(fd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +149,9 @@ impl VhostUserNetBackend {
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserBackend for VhostUserNetBackend {
|
||||
impl VhostUserBackendMut<VringRwLock<GuestMemoryAtomic<GuestMemoryMmap>>, AtomicBitmap>
|
||||
for VhostUserNetBackend
|
||||
{
|
||||
fn num_queues(&self) -> usize {
|
||||
self.num_queues
|
||||
}
|
||||
@@ -165,7 +171,6 @@ impl VhostUserBackend for VhostUserNetBackend {
|
||||
| 1 << VIRTIO_NET_F_HOST_TSO6
|
||||
| 1 << VIRTIO_NET_F_HOST_ECN
|
||||
| 1 << VIRTIO_NET_F_HOST_UFO
|
||||
| 1 << VIRTIO_NET_F_MRG_RXBUF
|
||||
| 1 << VIRTIO_NET_F_CTRL_VQ
|
||||
| 1 << VIRTIO_NET_F_MQ
|
||||
| 1 << VIRTIO_NET_F_MAC
|
||||
@@ -183,10 +188,10 @@ impl VhostUserBackend for VhostUserNetBackend {
|
||||
fn set_event_idx(&mut self, _enabled: bool) {}
|
||||
|
||||
fn handle_event(
|
||||
&self,
|
||||
&mut self,
|
||||
device_event: u16,
|
||||
_evset: epoll::Events,
|
||||
vrings: &[Arc<RwLock<Vring>>],
|
||||
_evset: EventSet,
|
||||
vrings: &[VringRwLock<GuestMemoryAtomic<GuestMemoryMmap>>],
|
||||
thread_id: usize,
|
||||
) -> VhostUserBackendResult<bool> {
|
||||
let mut thread = self.threads[thread_id].lock().unwrap();
|
||||
@@ -203,11 +208,11 @@ impl VhostUserBackend for VhostUserNetBackend {
|
||||
thread.net.rx_tap_listening = true;
|
||||
}
|
||||
}
|
||||
1 | 3 => {
|
||||
let mut vring = vrings[1].write().unwrap();
|
||||
1 | 4 => {
|
||||
let mut vring = vrings[1].get_mut();
|
||||
if thread
|
||||
.net
|
||||
.process_tx(vring.mut_queue())
|
||||
.process_tx(vring.get_queue_mut())
|
||||
.map_err(Error::NetQueuePair)?
|
||||
{
|
||||
vring
|
||||
@@ -215,11 +220,11 @@ impl VhostUserBackend for VhostUserNetBackend {
|
||||
.map_err(Error::FailedSignalingUsedQueue)?
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
let mut vring = vrings[0].write().unwrap();
|
||||
3 => {
|
||||
let mut vring = vrings[0].get_mut();
|
||||
if thread
|
||||
.net
|
||||
.process_rx(vring.mut_queue())
|
||||
.process_rx(vring.get_queue_mut())
|
||||
.map_err(Error::NetQueuePair)?
|
||||
{
|
||||
vring
|
||||
@@ -233,23 +238,27 @@ impl VhostUserBackend for VhostUserNetBackend {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn exit_event(&self, thread_index: usize) -> Option<(EventFd, Option<u16>)> {
|
||||
// The exit event is placed after the queues and the tap event, which
|
||||
// is event index 3.
|
||||
Some((
|
||||
fn exit_event(&self, thread_index: usize) -> Option<EventFd> {
|
||||
Some(
|
||||
self.threads[thread_index]
|
||||
.lock()
|
||||
.unwrap()
|
||||
.kill_evt
|
||||
.try_clone()
|
||||
.unwrap(),
|
||||
Some(3),
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
fn queues_per_thread(&self) -> Vec<u64> {
|
||||
self.queues_per_thread.clone()
|
||||
}
|
||||
|
||||
fn update_memory(
|
||||
&mut self,
|
||||
_mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
) -> VhostUserBackendResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VhostUserNetBackendConfig {
|
||||
@@ -343,27 +352,30 @@ pub fn start_net_backend(backend_command: &str) {
|
||||
.unwrap(),
|
||||
));
|
||||
|
||||
let mut net_daemon =
|
||||
VhostUserDaemon::new("vhost-user-net-backend".to_string(), net_backend.clone()).unwrap();
|
||||
let mut net_daemon = VhostUserDaemon::new(
|
||||
"vhost-user-net-backend".to_string(),
|
||||
net_backend.clone(),
|
||||
GuestMemoryAtomic::new(GuestMemoryMmap::new()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut vring_workers = net_daemon.get_vring_workers();
|
||||
|
||||
if vring_workers.len() != net_backend.read().unwrap().threads.len() {
|
||||
let epoll_handlers = net_daemon.get_epoll_handlers();
|
||||
if epoll_handlers.len() != net_backend.read().unwrap().threads.len() {
|
||||
error!("Number of vring workers must be identical to the number of backend threads");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
for thread in net_backend.read().unwrap().threads.iter() {
|
||||
for (index, thread) in net_backend.read().unwrap().threads.iter().enumerate() {
|
||||
thread
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_vring_worker(Some(vring_workers.remove(0)));
|
||||
.set_epoll_fd(epoll_handlers[index].as_raw_fd());
|
||||
}
|
||||
|
||||
if let Err(e) = if backend_config.client {
|
||||
net_daemon.start_client(&backend_config.socket)
|
||||
} else {
|
||||
net_daemon.start_server(Listener::new(&backend_config.socket, true).unwrap())
|
||||
net_daemon.start(Listener::new(&backend_config.socket, true).unwrap())
|
||||
} {
|
||||
error!(
|
||||
"failed to start daemon for vhost-user-net with error: {:?}",
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
#[macro_use(crate_version, crate_authors)]
|
||||
extern crate clap;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use clap::{Arg, Command};
|
||||
use vhost_user_net::start_net_backend;
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let cmd_arguments = App::new("vhost-user-net backend")
|
||||
let cmd_arguments = Command::new("vhost-user-net backend")
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.about("Launch a vhost-user-net backend.")
|
||||
|
||||
@@ -9,28 +9,28 @@ default = []
|
||||
mshv = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.52"
|
||||
anyhow = "1.0.55"
|
||||
arc-swap = "1.5.0"
|
||||
block_util = { path = "../block_util" }
|
||||
byteorder = "1.4.3"
|
||||
epoll = "4.3.1"
|
||||
event_monitor = { path = "../event_monitor" }
|
||||
io-uring = "0.5.2"
|
||||
libc = "0.2.112"
|
||||
libc = "0.2.119"
|
||||
log = "0.4.14"
|
||||
net_gen = { path = "../net_gen" }
|
||||
net_util = { path = "../net_util" }
|
||||
pci = { path = "../pci" }
|
||||
rate_limiter = { path = "../rate_limiter" }
|
||||
seccompiler = "0.2.0"
|
||||
serde = "1.0.133"
|
||||
serde_derive = "1.0.133"
|
||||
serde_json = "1.0.75"
|
||||
serde = "1.0.136"
|
||||
serde_derive = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
versionize = "0.1.6"
|
||||
versionize_derive = "0.1.4"
|
||||
vhost = { version = "0.3.0", features = ["vhost-user-master", "vhost-user-slave", "vhost-kern"] }
|
||||
virtio-bindings = { version = "0.1.0", features = ["virtio-v5_0_0"] }
|
||||
virtio-queue = { path = "../virtio-queue" }
|
||||
virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", branch = "main" }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = { version = "0.7.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
|
||||
|
||||
@@ -12,48 +12,55 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{
|
||||
ActivateError, ActivateResult, EpollHelper, EpollHelperError, EpollHelperHandler, VirtioCommon,
|
||||
VirtioDevice, VirtioDeviceType, EPOLL_HELPER_EVENT_LAST, VIRTIO_F_VERSION_1,
|
||||
use crate::{
|
||||
seccomp_filters::Thread, thread_helper::spawn_virtio_thread, ActivateError, ActivateResult,
|
||||
EpollHelper, EpollHelperError, EpollHelperHandler, GuestMemoryMmap, VirtioCommon, VirtioDevice,
|
||||
VirtioDeviceType, VirtioInterrupt, VirtioInterruptType, EPOLL_HELPER_EVENT_LAST,
|
||||
VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::seccomp_filters::Thread;
|
||||
use crate::thread_helper::spawn_virtio_thread;
|
||||
use crate::GuestMemoryMmap;
|
||||
use crate::{VirtioInterrupt, VirtioInterruptType};
|
||||
use libc::EFD_NONBLOCK;
|
||||
use seccompiler::SeccompAction;
|
||||
use std::io;
|
||||
use std::mem::size_of;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::sync::mpsc;
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
mpsc, Arc, Barrier, Mutex,
|
||||
};
|
||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use virtio_queue::Queue;
|
||||
use vm_memory::GuestMemory;
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemoryAtomic, GuestMemoryError};
|
||||
use vm_migration::VersionMapped;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryAtomic, GuestMemoryError,
|
||||
GuestMemoryRegion,
|
||||
};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||
};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 128;
|
||||
const NUM_QUEUES: usize = 2;
|
||||
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
|
||||
const REPORTING_QUEUE_SIZE: u16 = 32;
|
||||
const MIN_NUM_QUEUES: usize = 2;
|
||||
|
||||
// Get resize event.
|
||||
// Resize event.
|
||||
const RESIZE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 1;
|
||||
// New descriptors are pending on the virtio queue.
|
||||
// Inflate virtio queue event.
|
||||
const INFLATE_QUEUE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 2;
|
||||
// New descriptors are pending on the virtio queue.
|
||||
// Deflate virtio queue event.
|
||||
const DEFLATE_QUEUE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 3;
|
||||
// Reporting virtio queue event.
|
||||
const REPORTING_QUEUE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 4;
|
||||
|
||||
// Size of a PFN in the balloon interface.
|
||||
const VIRTIO_BALLOON_PFN_SHIFT: u64 = 12;
|
||||
|
||||
// Deflate balloon on OOM
|
||||
const VIRTIO_BALLOON_F_DEFLATE_ON_OOM: u64 = 2;
|
||||
// Enable an additional virtqueue to let the guest notify the host about free
|
||||
// pages.
|
||||
const VIRTIO_BALLOON_F_REPORTING: u64 = 5;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -75,8 +82,8 @@ pub enum Error {
|
||||
MpscRecvFail(mpsc::RecvError),
|
||||
// Resize invalid argument
|
||||
ResizeInval(String),
|
||||
// process_queue got wrong ev_type
|
||||
ProcessQueueWrongEvType(u16),
|
||||
// Invalid queue index
|
||||
InvalidQueueIndex(usize),
|
||||
// Fail tp signal
|
||||
FailedSignal(io::Error),
|
||||
/// Descriptor chain is too short
|
||||
@@ -160,39 +167,91 @@ struct BalloonEpollHandler {
|
||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
||||
inflate_queue_evt: EventFd,
|
||||
deflate_queue_evt: EventFd,
|
||||
reporting_queue_evt: Option<EventFd>,
|
||||
kill_evt: EventFd,
|
||||
pause_evt: EventFd,
|
||||
}
|
||||
|
||||
impl BalloonEpollHandler {
|
||||
fn signal(
|
||||
&self,
|
||||
int_type: &VirtioInterruptType,
|
||||
queue: Option<&Queue<GuestMemoryAtomic<GuestMemoryMmap>>>,
|
||||
) -> result::Result<(), Error> {
|
||||
self.interrupt_cb.trigger(int_type, queue).map_err(|e| {
|
||||
fn signal(&self, int_type: VirtioInterruptType) -> result::Result<(), Error> {
|
||||
self.interrupt_cb.trigger(int_type).map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
Error::FailedSignal(e)
|
||||
})
|
||||
}
|
||||
|
||||
fn process_queue(&mut self, ev_type: u16) -> result::Result<(), Error> {
|
||||
let queue_index = match ev_type {
|
||||
INFLATE_QUEUE_EVENT => 0,
|
||||
DEFLATE_QUEUE_EVENT => 1,
|
||||
_ => return Err(Error::ProcessQueueWrongEvType(ev_type)),
|
||||
};
|
||||
fn advise_memory_range(
|
||||
memory: &GuestMemoryMmap,
|
||||
range_base: GuestAddress,
|
||||
range_len: usize,
|
||||
advice: libc::c_int,
|
||||
) -> result::Result<(), Error> {
|
||||
let hva = memory
|
||||
.get_host_address(range_base)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
// Need unsafe to do syscall madvise
|
||||
let res =
|
||||
unsafe { libc::madvise(hva as *mut libc::c_void, range_len as libc::size_t, advice) };
|
||||
if res != 0 {
|
||||
return Err(Error::MadviseFail(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let mut used_desc_heads = [0; QUEUE_SIZE as usize];
|
||||
let mut used_count = 0;
|
||||
fn release_memory_range(
|
||||
memory: &GuestMemoryMmap,
|
||||
range_base: GuestAddress,
|
||||
range_len: usize,
|
||||
) -> result::Result<(), Error> {
|
||||
let region = memory.find_region(range_base).ok_or(Error::GuestMemory(
|
||||
GuestMemoryError::InvalidGuestAddress(range_base),
|
||||
))?;
|
||||
if let Some(f_off) = region.file_offset() {
|
||||
let offset = range_base.0 - region.start_addr().0;
|
||||
let res = unsafe {
|
||||
libc::fallocate64(
|
||||
f_off.file().as_raw_fd(),
|
||||
libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE,
|
||||
(offset as u64 + f_off.start()) as libc::off64_t,
|
||||
range_len as libc::off64_t,
|
||||
)
|
||||
};
|
||||
|
||||
if res != 0 {
|
||||
return Err(Error::FallocateFail(io::Error::last_os_error()));
|
||||
}
|
||||
}
|
||||
|
||||
Self::advise_memory_range(memory, range_base, range_len, libc::MADV_DONTNEED)
|
||||
}
|
||||
|
||||
fn notify_queue(
|
||||
&mut self,
|
||||
queue_index: usize,
|
||||
used_descs: Vec<(u16, u32)>,
|
||||
) -> result::Result<(), Error> {
|
||||
for (desc_index, len) in used_descs.iter() {
|
||||
self.queues[queue_index]
|
||||
.add_used(*desc_index, *len)
|
||||
.map_err(Error::QueueAddUsed)?;
|
||||
}
|
||||
|
||||
if !used_descs.is_empty() {
|
||||
self.signal(VirtioInterruptType::Queue(queue_index as u16))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_queue(&mut self, queue_index: usize) -> result::Result<(), Error> {
|
||||
let mut used_descs = Vec::new();
|
||||
for mut desc_chain in self.queues[queue_index]
|
||||
.iter()
|
||||
.map_err(Error::QueueIterator)?
|
||||
{
|
||||
let desc = desc_chain.next().ok_or(Error::DescriptorChainTooShort)?;
|
||||
|
||||
used_desc_heads[used_count] = desc_chain.head_index();
|
||||
used_count += 1;
|
||||
used_descs.push((desc_chain.head_index(), desc.len()));
|
||||
|
||||
let data_chunk_size = size_of::<u32>();
|
||||
|
||||
@@ -215,63 +274,46 @@ impl BalloonEpollHandler {
|
||||
.map_err(Error::GuestMemory)?;
|
||||
offset += data_chunk_size as u64;
|
||||
|
||||
let gpa = (pfn as u64) << VIRTIO_BALLOON_PFN_SHIFT;
|
||||
if let Ok(hva) = desc_chain.memory().get_host_address(GuestAddress(gpa)) {
|
||||
let advice = match ev_type {
|
||||
INFLATE_QUEUE_EVENT => {
|
||||
let region = desc_chain.memory().find_region(GuestAddress(gpa)).ok_or(
|
||||
Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(
|
||||
GuestAddress(gpa),
|
||||
)),
|
||||
)?;
|
||||
if let Some(f_off) = region.file_offset() {
|
||||
let offset = hva as usize - region.as_ptr() as usize;
|
||||
let res = unsafe {
|
||||
libc::fallocate64(
|
||||
f_off.file().as_raw_fd(),
|
||||
libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE,
|
||||
(offset as u64 + f_off.start()) as libc::off64_t,
|
||||
(1 << VIRTIO_BALLOON_PFN_SHIFT) as libc::off64_t,
|
||||
)
|
||||
};
|
||||
let range_base = GuestAddress((pfn as u64) << VIRTIO_BALLOON_PFN_SHIFT);
|
||||
let range_len = 1 << VIRTIO_BALLOON_PFN_SHIFT;
|
||||
|
||||
if res != 0 {
|
||||
return Err(Error::FallocateFail(io::Error::last_os_error()));
|
||||
}
|
||||
}
|
||||
libc::MADV_DONTNEED
|
||||
}
|
||||
DEFLATE_QUEUE_EVENT => libc::MADV_WILLNEED,
|
||||
_ => return Err(Error::ProcessQueueWrongEvType(ev_type)),
|
||||
};
|
||||
// Need unsafe to do syscall madvise
|
||||
let res = unsafe {
|
||||
libc::madvise(
|
||||
hva as *mut libc::c_void,
|
||||
(1 << VIRTIO_BALLOON_PFN_SHIFT) as libc::size_t,
|
||||
advice,
|
||||
)
|
||||
};
|
||||
if res != 0 {
|
||||
return Err(Error::MadviseFail(io::Error::last_os_error()));
|
||||
match queue_index {
|
||||
0 => {
|
||||
Self::release_memory_range(desc_chain.memory(), range_base, range_len)?;
|
||||
}
|
||||
} else {
|
||||
error!("Address 0x{:x} is not available", gpa);
|
||||
return Err(Error::InvalidRequest);
|
||||
1 => {
|
||||
Self::advise_memory_range(
|
||||
desc_chain.memory(),
|
||||
range_base,
|
||||
range_len,
|
||||
libc::MADV_WILLNEED,
|
||||
)?;
|
||||
}
|
||||
_ => return Err(Error::InvalidQueueIndex(queue_index)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for &desc_index in &used_desc_heads[..used_count] {
|
||||
self.queues[queue_index]
|
||||
.add_used(desc_index, 0)
|
||||
.map_err(Error::QueueAddUsed)?;
|
||||
}
|
||||
if used_count > 0 {
|
||||
self.signal(&VirtioInterruptType::Queue, Some(&self.queues[queue_index]))?;
|
||||
self.notify_queue(queue_index, used_descs)
|
||||
}
|
||||
|
||||
fn process_reporting_queue(&mut self, queue_index: usize) -> result::Result<(), Error> {
|
||||
let mut used_descs = Vec::new();
|
||||
|
||||
for mut desc_chain in self.queues[queue_index]
|
||||
.iter()
|
||||
.map_err(Error::QueueIterator)?
|
||||
{
|
||||
let mut descs_len = 0;
|
||||
while let Some(desc) = desc_chain.next() {
|
||||
descs_len += desc.len();
|
||||
Self::release_memory_range(desc_chain.memory(), desc.addr(), desc.len() as usize)?;
|
||||
}
|
||||
|
||||
used_descs.push((desc_chain.head_index(), descs_len));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
self.notify_queue(queue_index, used_descs)
|
||||
}
|
||||
|
||||
fn run(
|
||||
@@ -283,6 +325,9 @@ impl BalloonEpollHandler {
|
||||
helper.add_event(self.resize_receiver.evt.as_raw_fd(), RESIZE_EVENT)?;
|
||||
helper.add_event(self.inflate_queue_evt.as_raw_fd(), INFLATE_QUEUE_EVENT)?;
|
||||
helper.add_event(self.deflate_queue_evt.as_raw_fd(), DEFLATE_QUEUE_EVENT)?;
|
||||
if let Some(reporting_queue_evt) = self.reporting_queue_evt.as_ref() {
|
||||
helper.add_event(reporting_queue_evt.as_raw_fd(), REPORTING_QUEUE_EVENT)?;
|
||||
}
|
||||
helper.run(paused, paused_sync, self)?;
|
||||
|
||||
Ok(())
|
||||
@@ -303,7 +348,7 @@ impl EpollHelperHandler for BalloonEpollHandler {
|
||||
let mut config = self.config.lock().unwrap();
|
||||
config.num_pages =
|
||||
(self.resize_receiver.get_size() >> VIRTIO_BALLOON_PFN_SHIFT) as u32;
|
||||
if let Err(e) = self.signal(&VirtioInterruptType::Config, None) {
|
||||
if let Err(e) = self.signal(VirtioInterruptType::Config) {
|
||||
signal_error = true;
|
||||
Err(e)
|
||||
} else {
|
||||
@@ -326,7 +371,7 @@ impl EpollHelperHandler for BalloonEpollHandler {
|
||||
if let Err(e) = self.inflate_queue_evt.read() {
|
||||
error!("Failed to get inflate queue event: {:?}", e);
|
||||
return true;
|
||||
} else if let Err(e) = self.process_queue(ev_type) {
|
||||
} else if let Err(e) = self.process_queue(0) {
|
||||
error!("Failed to signal used inflate queue: {:?}", e);
|
||||
return true;
|
||||
}
|
||||
@@ -335,11 +380,25 @@ impl EpollHelperHandler for BalloonEpollHandler {
|
||||
if let Err(e) = self.deflate_queue_evt.read() {
|
||||
error!("Failed to get deflate queue event: {:?}", e);
|
||||
return true;
|
||||
} else if let Err(e) = self.process_queue(ev_type) {
|
||||
} else if let Err(e) = self.process_queue(1) {
|
||||
error!("Failed to signal used deflate queue: {:?}", e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
REPORTING_QUEUE_EVENT => {
|
||||
if let Some(reporting_queue_evt) = self.reporting_queue_evt.as_ref() {
|
||||
if let Err(e) = reporting_queue_evt.read() {
|
||||
error!("Failed to get reporting queue event: {:?}", e);
|
||||
return true;
|
||||
} else if let Err(e) = self.process_reporting_queue(2) {
|
||||
error!("Failed to signal used inflate queue: {:?}", e);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
error!("Invalid reporting queue event as no eventfd registered");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("Unknown event for virtio-balloon");
|
||||
return true;
|
||||
@@ -375,13 +434,19 @@ impl Balloon {
|
||||
id: String,
|
||||
size: u64,
|
||||
deflate_on_oom: bool,
|
||||
free_page_reporting: bool,
|
||||
seccomp_action: SeccompAction,
|
||||
exit_evt: EventFd,
|
||||
) -> io::Result<Self> {
|
||||
let mut queue_sizes = vec![QUEUE_SIZE; MIN_NUM_QUEUES];
|
||||
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
|
||||
if deflate_on_oom {
|
||||
avail_features |= 1u64 << VIRTIO_BALLOON_F_DEFLATE_ON_OOM;
|
||||
}
|
||||
if free_page_reporting {
|
||||
avail_features |= 1u64 << VIRTIO_BALLOON_F_REPORTING;
|
||||
queue_sizes.push(REPORTING_QUEUE_SIZE);
|
||||
}
|
||||
|
||||
let config = VirtioBalloonConfig {
|
||||
num_pages: (size >> VIRTIO_BALLOON_PFN_SHIFT) as u32,
|
||||
@@ -393,8 +458,8 @@ impl Balloon {
|
||||
device_type: VirtioDeviceType::Balloon as u32,
|
||||
avail_features,
|
||||
paused_sync: Some(Arc::new(Barrier::new(2))),
|
||||
queue_sizes: QUEUE_SIZES.to_vec(),
|
||||
min_queues: NUM_QUEUES as u16,
|
||||
queue_sizes,
|
||||
min_queues: MIN_NUM_QUEUES as u16,
|
||||
..Default::default()
|
||||
},
|
||||
id,
|
||||
@@ -483,6 +548,15 @@ impl VirtioDevice for Balloon {
|
||||
self.common.activate(&queues, &queue_evts, &interrupt_cb)?;
|
||||
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
||||
|
||||
let inflate_queue_evt = queue_evts.remove(0);
|
||||
let deflate_queue_evt = queue_evts.remove(0);
|
||||
let reporting_queue_evt =
|
||||
if self.common.feature_acked(VIRTIO_BALLOON_F_REPORTING) && !queue_evts.is_empty() {
|
||||
Some(queue_evts.remove(0))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut handler = BalloonEpollHandler {
|
||||
config: self.config.clone(),
|
||||
resize_receiver: self.resize.get_receiver().map_err(|e| {
|
||||
@@ -491,8 +565,9 @@ impl VirtioDevice for Balloon {
|
||||
})?,
|
||||
queues,
|
||||
interrupt_cb,
|
||||
inflate_queue_evt: queue_evts.remove(0),
|
||||
deflate_queue_evt: queue_evts.remove(0),
|
||||
inflate_queue_evt,
|
||||
deflate_queue_evt,
|
||||
reporting_queue_evt,
|
||||
kill_evt,
|
||||
pause_evt,
|
||||
};
|
||||
|
||||
@@ -39,6 +39,7 @@ use virtio_queue::Queue;
|
||||
use vm_memory::{ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic};
|
||||
use vm_migration::VersionMapped;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
use vm_virtio::AccessPlatform;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const SECTOR_SHIFT: u8 = 9;
|
||||
@@ -82,6 +83,7 @@ pub struct BlockCounters {
|
||||
}
|
||||
|
||||
struct BlockEpollHandler {
|
||||
queue_index: u16,
|
||||
queue: Queue<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
disk_image: Box<dyn AsyncIo>,
|
||||
@@ -95,6 +97,7 @@ struct BlockEpollHandler {
|
||||
queue_evt: EventFd,
|
||||
request_list: HashMap<u16, Request>,
|
||||
rate_limiter: Option<RateLimiter>,
|
||||
access_platform: Option<Arc<dyn AccessPlatform>>,
|
||||
}
|
||||
|
||||
impl BlockEpollHandler {
|
||||
@@ -106,7 +109,8 @@ impl BlockEpollHandler {
|
||||
|
||||
let mut avail_iter = queue.iter().map_err(Error::QueueIterator)?;
|
||||
for mut desc_chain in &mut avail_iter {
|
||||
let mut request = Request::parse(&mut desc_chain).map_err(Error::RequestParsing)?;
|
||||
let mut request = Request::parse(&mut desc_chain, self.access_platform.as_ref())
|
||||
.map_err(Error::RequestParsing)?;
|
||||
|
||||
if let Some(rate_limiter) = &mut self.rate_limiter {
|
||||
// If limiter.consume() fails it means there is no more TokenType::Ops
|
||||
@@ -258,7 +262,7 @@ impl BlockEpollHandler {
|
||||
|
||||
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_cb
|
||||
.trigger(&VirtioInterruptType::Queue, Some(&self.queue))
|
||||
.trigger(VirtioInterruptType::Queue(self.queue_index))
|
||||
.map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
@@ -605,6 +609,7 @@ impl VirtioDevice for Block {
|
||||
.map_err(ActivateError::CreateRateLimiter)?;
|
||||
|
||||
let mut handler = BlockEpollHandler {
|
||||
queue_index: i as u16,
|
||||
queue,
|
||||
mem: mem.clone(),
|
||||
disk_image: self
|
||||
@@ -624,6 +629,7 @@ impl VirtioDevice for Block {
|
||||
queue_evt,
|
||||
request_list: HashMap::with_capacity(queue_size.into()),
|
||||
rate_limiter,
|
||||
access_platform: self.common.access_platform.clone(),
|
||||
};
|
||||
|
||||
let paused = self.common.paused.clone();
|
||||
@@ -677,6 +683,10 @@ impl VirtioDevice for Block {
|
||||
|
||||
Some(counters)
|
||||
}
|
||||
|
||||
fn set_access_platform(&mut self, access_platform: Arc<dyn AccessPlatform>) {
|
||||
self.common.set_access_platform(access_platform)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Block {
|
||||
|
||||
@@ -28,6 +28,7 @@ use virtio_queue::Queue;
|
||||
use vm_memory::{ByteValued, Bytes, GuestMemoryAtomic};
|
||||
use vm_migration::VersionMapped;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
use vm_virtio::{AccessPlatform, Translatable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
@@ -85,6 +86,7 @@ struct ConsoleEpollHandler {
|
||||
resize_pipe: Option<File>,
|
||||
kill_evt: EventFd,
|
||||
pause_evt: EventFd,
|
||||
access_platform: Option<Arc<dyn AccessPlatform>>,
|
||||
}
|
||||
|
||||
pub enum Endpoint {
|
||||
@@ -145,10 +147,12 @@ impl ConsoleEpollHandler {
|
||||
let desc = desc_chain.next().unwrap();
|
||||
let len = cmp::min(desc.len() as u32, in_buffer.len() as u32);
|
||||
let source_slice = in_buffer.drain(..len as usize).collect::<Vec<u8>>();
|
||||
if let Err(e) = desc_chain
|
||||
.memory()
|
||||
.write_slice(&source_slice[..], desc.addr())
|
||||
{
|
||||
|
||||
if let Err(e) = desc_chain.memory().write_slice(
|
||||
&source_slice[..],
|
||||
desc.addr()
|
||||
.translate(self.access_platform.as_ref(), desc.len() as usize),
|
||||
) {
|
||||
error!("Failed to write slice: {:?}", e);
|
||||
avail_iter.go_to_previous_position();
|
||||
break;
|
||||
@@ -184,9 +188,12 @@ impl ConsoleEpollHandler {
|
||||
for mut desc_chain in trans_queue.iter().unwrap() {
|
||||
let desc = desc_chain.next().unwrap();
|
||||
if let Some(ref mut out) = self.endpoint.out_file() {
|
||||
let _ = desc_chain
|
||||
.memory()
|
||||
.write_to(desc.addr(), out, desc.len() as usize);
|
||||
let _ = desc_chain.memory().write_to(
|
||||
desc.addr()
|
||||
.translate(self.access_platform.as_ref(), desc.len() as usize),
|
||||
out,
|
||||
desc.len() as usize,
|
||||
);
|
||||
let _ = out.flush();
|
||||
}
|
||||
used_desc_heads[used_count] = (desc_chain.head_index(), desc.len());
|
||||
@@ -199,9 +206,9 @@ impl ConsoleEpollHandler {
|
||||
used_count > 0
|
||||
}
|
||||
|
||||
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
|
||||
fn signal_used_queue(&self, queue_index: u16) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_cb
|
||||
.trigger(&VirtioInterruptType::Queue, Some(&self.queues[0]))
|
||||
.trigger(VirtioInterruptType::Queue(queue_index))
|
||||
.map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
@@ -239,7 +246,7 @@ impl EpollHelperHandler for ConsoleEpollHandler {
|
||||
error!("Failed to get queue event: {:?}", e);
|
||||
return true;
|
||||
} else if self.process_input_queue() {
|
||||
if let Err(e) = self.signal_used_queue() {
|
||||
if let Err(e) = self.signal_used_queue(0) {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
return true;
|
||||
}
|
||||
@@ -249,8 +256,11 @@ impl EpollHelperHandler for ConsoleEpollHandler {
|
||||
if let Err(e) = self.output_queue_evt.read() {
|
||||
error!("Failed to get queue event: {:?}", e);
|
||||
return true;
|
||||
} else {
|
||||
self.process_output_queue();
|
||||
} else if self.process_output_queue() {
|
||||
if let Err(e) = self.signal_used_queue(1) {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
INPUT_EVENT => {
|
||||
@@ -258,7 +268,7 @@ impl EpollHelperHandler for ConsoleEpollHandler {
|
||||
error!("Failed to get input event: {:?}", e);
|
||||
return true;
|
||||
} else if self.process_input_queue() {
|
||||
if let Err(e) = self.signal_used_queue() {
|
||||
if let Err(e) = self.signal_used_queue(0) {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
return true;
|
||||
}
|
||||
@@ -268,10 +278,7 @@ impl EpollHelperHandler for ConsoleEpollHandler {
|
||||
if let Err(e) = self.config_evt.read() {
|
||||
error!("Failed to get config event: {:?}", e);
|
||||
return true;
|
||||
} else if let Err(e) = self
|
||||
.interrupt_cb
|
||||
.trigger(&VirtioInterruptType::Config, None)
|
||||
{
|
||||
} else if let Err(e) = self.interrupt_cb.trigger(VirtioInterruptType::Config) {
|
||||
error!("Failed to signal console driver: {:?}", e);
|
||||
return true;
|
||||
}
|
||||
@@ -293,7 +300,7 @@ impl EpollHelperHandler for ConsoleEpollHandler {
|
||||
}
|
||||
|
||||
if self.process_input_queue() {
|
||||
if let Err(e) = self.signal_used_queue() {
|
||||
if let Err(e) = self.signal_used_queue(0) {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
return true;
|
||||
}
|
||||
@@ -492,7 +499,7 @@ impl VirtioDevice for Console {
|
||||
.store(self.common.acked_features, Ordering::Relaxed);
|
||||
|
||||
if self.common.feature_acked(VIRTIO_CONSOLE_F_SIZE) {
|
||||
if let Err(e) = interrupt_cb.trigger(&VirtioInterruptType::Config, None) {
|
||||
if let Err(e) = interrupt_cb.trigger(VirtioInterruptType::Config) {
|
||||
error!("Failed to signal console driver: {:?}", e);
|
||||
}
|
||||
}
|
||||
@@ -513,6 +520,7 @@ impl VirtioDevice for Console {
|
||||
resizer: Arc::clone(&self.resizer),
|
||||
kill_evt,
|
||||
pause_evt,
|
||||
access_platform: self.common.access_platform.clone(),
|
||||
};
|
||||
|
||||
let paused = self.common.paused.clone();
|
||||
@@ -543,6 +551,10 @@ impl VirtioDevice for Console {
|
||||
event!("virtio-device", "reset", "id", &self.id);
|
||||
result
|
||||
}
|
||||
|
||||
fn set_access_platform(&mut self, access_platform: Arc<dyn AccessPlatform>) {
|
||||
self.common.set_access_platform(access_platform)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Console {
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use crate::{ActivateError, ActivateResult, Error};
|
||||
use crate::{GuestMemoryMmap, GuestRegionMmap};
|
||||
use crate::{
|
||||
ActivateError, ActivateResult, Error, GuestMemoryMmap, GuestRegionMmap,
|
||||
VIRTIO_F_RING_INDIRECT_DESC,
|
||||
};
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
@@ -20,25 +22,18 @@ use std::thread;
|
||||
use virtio_queue::Queue;
|
||||
use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestUsize};
|
||||
use vm_migration::{MigratableError, Pausable};
|
||||
use vm_virtio::AccessPlatform;
|
||||
use vm_virtio::VirtioDeviceType;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub enum VirtioInterruptType {
|
||||
Config,
|
||||
Queue,
|
||||
Queue(u16),
|
||||
}
|
||||
|
||||
pub trait VirtioInterrupt: Send + Sync {
|
||||
fn trigger(
|
||||
&self,
|
||||
int_type: &VirtioInterruptType,
|
||||
queue: Option<&Queue<GuestMemoryAtomic<GuestMemoryMmap>>>,
|
||||
) -> std::result::Result<(), std::io::Error>;
|
||||
fn notifier(
|
||||
&self,
|
||||
_int_type: &VirtioInterruptType,
|
||||
_queue: Option<&Queue<GuestMemoryAtomic<GuestMemoryMmap>>>,
|
||||
) -> Option<EventFd> {
|
||||
fn trigger(&self, int_type: VirtioInterruptType) -> std::result::Result<(), std::io::Error>;
|
||||
fn notifier(&self, _int_type: VirtioInterruptType) -> Option<EventFd> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -135,10 +130,6 @@ pub trait VirtioDevice: Send {
|
||||
std::unimplemented!()
|
||||
}
|
||||
|
||||
fn iommu_translate(&self, addr: u64) -> u64 {
|
||||
addr
|
||||
}
|
||||
|
||||
/// Some devices may need to do some explicit shutdown work. This method
|
||||
/// may be implemented to do this. The VMM should call shutdown() on
|
||||
/// every device as part of shutting down the VM. Acting on the device
|
||||
@@ -203,6 +194,10 @@ pub trait VirtioDevice: Send {
|
||||
offset_config.write_all(data).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the access platform trait to let the device perform address
|
||||
/// translations if needed.
|
||||
fn set_access_platform(&mut self, _access_platform: Arc<dyn AccessPlatform>) {}
|
||||
}
|
||||
|
||||
/// Trait providing address translation the same way a physical DMA remapping
|
||||
@@ -230,6 +225,7 @@ pub struct VirtioCommon {
|
||||
pub queue_sizes: Vec<u16>,
|
||||
pub device_type: u32,
|
||||
pub min_queues: u16,
|
||||
pub access_platform: Option<Arc<dyn AccessPlatform>>,
|
||||
}
|
||||
|
||||
impl VirtioCommon {
|
||||
@@ -332,6 +328,13 @@ impl VirtioCommon {
|
||||
self.pause_evt.as_ref().unwrap().try_clone().unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_access_platform(&mut self, access_platform: Arc<dyn AccessPlatform>) {
|
||||
self.access_platform = Some(access_platform);
|
||||
// Indirect descriptors feature is not supported when the device
|
||||
// requires the addresses held by the descriptors to be translated.
|
||||
self.avail_features &= !(1 << VIRTIO_F_RING_INDIRECT_DESC);
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for VirtioCommon {
|
||||
|
||||
@@ -23,7 +23,7 @@ use std::sync::atomic::AtomicBool;
|
||||
use std::sync::{Arc, Barrier, RwLock};
|
||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use virtio_queue::{AccessPlatform, DescriptorChain, Queue};
|
||||
use virtio_queue::{DescriptorChain, Queue};
|
||||
use vm_device::dma_mapping::ExternalDmaMapping;
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemoryAtomic, GuestMemoryError,
|
||||
@@ -31,6 +31,7 @@ use vm_memory::{
|
||||
};
|
||||
use vm_migration::VersionMapped;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
use vm_virtio::AccessPlatform;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// Queues sizes
|
||||
@@ -609,12 +610,9 @@ impl IommuEpollHandler {
|
||||
false
|
||||
}
|
||||
|
||||
fn signal_used_queue(
|
||||
&self,
|
||||
queue: &Queue<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
) -> result::Result<(), DeviceError> {
|
||||
fn signal_used_queue(&self, queue_index: u16) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_cb
|
||||
.trigger(&VirtioInterruptType::Queue, Some(queue))
|
||||
.trigger(VirtioInterruptType::Queue(queue_index))
|
||||
.map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
@@ -644,7 +642,7 @@ impl EpollHelperHandler for IommuEpollHandler {
|
||||
error!("Failed to get queue event: {:?}", e);
|
||||
return true;
|
||||
} else if self.request_queue() {
|
||||
if let Err(e) = self.signal_used_queue(&self.queues[0]) {
|
||||
if let Err(e) = self.signal_used_queue(0) {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
return true;
|
||||
}
|
||||
@@ -655,7 +653,7 @@ impl EpollHelperHandler for IommuEpollHandler {
|
||||
error!("Failed to get queue event: {:?}", e);
|
||||
return true;
|
||||
} else if self.event_queue() {
|
||||
if let Err(e) = self.signal_used_queue(&self.queues[1]) {
|
||||
if let Err(e) = self.signal_used_queue(1) {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -644,13 +644,11 @@ impl MemEpollHandler {
|
||||
(resp_type, resp_state)
|
||||
}
|
||||
|
||||
fn signal(&self, int_type: &VirtioInterruptType) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_cb
|
||||
.trigger(int_type, Some(&self.queue))
|
||||
.map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
fn signal(&self, int_type: VirtioInterruptType) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_cb.trigger(int_type).map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
}
|
||||
|
||||
fn process_queue(&mut self) -> bool {
|
||||
@@ -734,7 +732,7 @@ impl EpollHelperHandler for MemEpollHandler {
|
||||
let mut r = config.resize(size);
|
||||
r = match r {
|
||||
Err(e) => Err(e),
|
||||
_ => match self.signal(&VirtioInterruptType::Config) {
|
||||
_ => match self.signal(VirtioInterruptType::Config) {
|
||||
Err(e) => {
|
||||
signal_error = true;
|
||||
Err(Error::ResizeTriggerFail(e))
|
||||
@@ -756,7 +754,7 @@ impl EpollHelperHandler for MemEpollHandler {
|
||||
error!("Failed to get queue event: {:?}", e);
|
||||
return true;
|
||||
} else if self.process_queue() {
|
||||
if let Err(e) = self.signal(&VirtioInterruptType::Queue) {
|
||||
if let Err(e) = self.signal(VirtioInterruptType::Queue(0)) {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ use virtio_queue::Queue;
|
||||
use vm_memory::{ByteValued, GuestMemoryAtomic};
|
||||
use vm_migration::VersionMapped;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
use vm_virtio::AccessPlatform;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// Control queue
|
||||
@@ -51,6 +52,7 @@ pub struct NetCtrlEpollHandler {
|
||||
pub ctrl_q: CtrlQueue,
|
||||
pub queue_evt: EventFd,
|
||||
pub queue: Queue<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
pub access_platform: Option<Arc<dyn AccessPlatform>>,
|
||||
}
|
||||
|
||||
impl NetCtrlEpollHandler {
|
||||
@@ -76,7 +78,10 @@ impl EpollHelperHandler for NetCtrlEpollHandler {
|
||||
error!("failed to get ctl queue event: {:?}", e);
|
||||
return true;
|
||||
}
|
||||
if let Err(e) = self.ctrl_q.process(&mut self.queue) {
|
||||
if let Err(e) = self
|
||||
.ctrl_q
|
||||
.process(&mut self.queue, self.access_platform.as_ref())
|
||||
{
|
||||
error!("failed to process ctrl queue: {:?}", e);
|
||||
return true;
|
||||
}
|
||||
@@ -124,6 +129,7 @@ struct NetEpollHandler {
|
||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
||||
kill_evt: EventFd,
|
||||
pause_evt: EventFd,
|
||||
queue_index_base: u16,
|
||||
queue_pair: Vec<Queue<GuestMemoryAtomic<GuestMemoryMmap>>>,
|
||||
queue_evt_pair: Vec<EventFd>,
|
||||
// Always generate interrupts until the driver has signalled to the device.
|
||||
@@ -134,12 +140,9 @@ struct NetEpollHandler {
|
||||
}
|
||||
|
||||
impl NetEpollHandler {
|
||||
fn signal_used_queue(
|
||||
&self,
|
||||
queue: &Queue<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
) -> result::Result<(), DeviceError> {
|
||||
fn signal_used_queue(&self, queue_index: u16) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_cb
|
||||
.trigger(&VirtioInterruptType::Queue, Some(queue))
|
||||
.trigger(VirtioInterruptType::Queue(queue_index))
|
||||
.map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
@@ -182,7 +185,7 @@ impl NetEpollHandler {
|
||||
.map_err(DeviceError::NetQueuePair)?
|
||||
|| !self.driver_awake
|
||||
{
|
||||
self.signal_used_queue(&self.queue_pair[1])?;
|
||||
self.signal_used_queue(self.queue_index_base + 1)?;
|
||||
debug!("Signalling TX queue");
|
||||
} else {
|
||||
debug!("Not signalling TX queue");
|
||||
@@ -211,7 +214,7 @@ impl NetEpollHandler {
|
||||
.map_err(DeviceError::NetQueuePair)?
|
||||
|| !self.driver_awake
|
||||
{
|
||||
self.signal_used_queue(&self.queue_pair[0])?;
|
||||
self.signal_used_queue(self.queue_index_base)?;
|
||||
debug!("Signalling RX queue");
|
||||
} else {
|
||||
debug!("Not signalling RX queue");
|
||||
@@ -577,6 +580,7 @@ impl VirtioDevice for Net {
|
||||
ctrl_q: CtrlQueue::new(self.taps.clone()),
|
||||
queue: cvq_queue,
|
||||
queue_evt: cvq_queue_evt,
|
||||
access_platform: self.common.access_platform.clone(),
|
||||
};
|
||||
|
||||
let paused = self.common.paused.clone();
|
||||
@@ -651,7 +655,9 @@ impl VirtioDevice for Net {
|
||||
rx_desc_avail: false,
|
||||
rx_rate_limiter,
|
||||
tx_rate_limiter,
|
||||
access_platform: self.common.access_platform.clone(),
|
||||
},
|
||||
queue_index_base: (i * 2) as u16,
|
||||
queue_pair,
|
||||
queue_evt_pair,
|
||||
interrupt_cb: interrupt_cb.clone(),
|
||||
@@ -711,6 +717,10 @@ impl VirtioDevice for Net {
|
||||
|
||||
Some(counters)
|
||||
}
|
||||
|
||||
fn set_access_platform(&mut self, access_platform: Arc<dyn AccessPlatform>) {
|
||||
self.common.set_access_platform(access_platform)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Net {
|
||||
|
||||
@@ -34,6 +34,7 @@ use vm_memory::{
|
||||
};
|
||||
use vm_migration::VersionMapped;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
use vm_virtio::{AccessPlatform, Translatable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
@@ -118,6 +119,7 @@ struct Request {
|
||||
impl Request {
|
||||
fn parse(
|
||||
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap>>,
|
||||
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
||||
) -> result::Result<Request, Error> {
|
||||
let desc = desc_chain.next().ok_or(Error::DescriptorChainTooShort)?;
|
||||
// The descriptor contains the request type which MUST be readable.
|
||||
@@ -131,7 +133,7 @@ impl Request {
|
||||
|
||||
let request: VirtioPmemReq = desc_chain
|
||||
.memory()
|
||||
.read_obj(desc.addr())
|
||||
.read_obj(desc.addr().translate(access_platform, desc.len() as usize))
|
||||
.map_err(Error::GuestMemory)?;
|
||||
|
||||
let request_type = match request.type_ {
|
||||
@@ -152,7 +154,9 @@ impl Request {
|
||||
|
||||
Ok(Request {
|
||||
type_: request_type,
|
||||
status_addr: status_desc.addr(),
|
||||
status_addr: status_desc
|
||||
.addr()
|
||||
.translate(access_platform, status_desc.len() as usize),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -164,6 +168,7 @@ struct PmemEpollHandler {
|
||||
queue_evt: EventFd,
|
||||
kill_evt: EventFd,
|
||||
pause_evt: EventFd,
|
||||
access_platform: Option<Arc<dyn AccessPlatform>>,
|
||||
}
|
||||
|
||||
impl PmemEpollHandler {
|
||||
@@ -171,7 +176,7 @@ impl PmemEpollHandler {
|
||||
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
|
||||
let mut used_count = 0;
|
||||
for mut desc_chain in self.queue.iter().unwrap() {
|
||||
let len = match Request::parse(&mut desc_chain) {
|
||||
let len = match Request::parse(&mut desc_chain, self.access_platform.as_ref()) {
|
||||
Ok(ref req) if (req.type_ == RequestType::Flush) => {
|
||||
let status_code = match self.disk.sync_all() {
|
||||
Ok(()) => VIRTIO_PMEM_RESP_TYPE_OK,
|
||||
@@ -213,7 +218,7 @@ impl PmemEpollHandler {
|
||||
|
||||
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_cb
|
||||
.trigger(&VirtioInterruptType::Queue, Some(&self.queue))
|
||||
.trigger(VirtioInterruptType::Queue(0))
|
||||
.map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
@@ -388,6 +393,7 @@ impl VirtioDevice for Pmem {
|
||||
queue_evt: queue_evts.remove(0),
|
||||
kill_evt,
|
||||
pause_evt,
|
||||
access_platform: self.common.access_platform.clone(),
|
||||
};
|
||||
|
||||
let paused = self.common.paused.clone();
|
||||
@@ -424,6 +430,10 @@ impl VirtioDevice for Pmem {
|
||||
fn userspace_mappings(&self) -> Vec<UserspaceMapping> {
|
||||
vec![self.mapping.clone()]
|
||||
}
|
||||
|
||||
fn set_access_platform(&mut self, access_platform: Arc<dyn AccessPlatform>) {
|
||||
self.common.set_access_platform(access_platform)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Pmem {
|
||||
|
||||
@@ -25,6 +25,7 @@ use virtio_queue::Queue;
|
||||
use vm_memory::{Bytes, GuestMemoryAtomic};
|
||||
use vm_migration::VersionMapped;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
use vm_virtio::{AccessPlatform, Translatable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
@@ -40,6 +41,7 @@ struct RngEpollHandler {
|
||||
queue_evt: EventFd,
|
||||
kill_evt: EventFd,
|
||||
pause_evt: EventFd,
|
||||
access_platform: Option<Arc<dyn AccessPlatform>>,
|
||||
}
|
||||
|
||||
impl RngEpollHandler {
|
||||
@@ -57,7 +59,12 @@ impl RngEpollHandler {
|
||||
// Fill the read with data from the random device on the host.
|
||||
if desc_chain
|
||||
.memory()
|
||||
.read_from(desc.addr(), &mut self.random_file, desc.len() as usize)
|
||||
.read_from(
|
||||
desc.addr()
|
||||
.translate(self.access_platform.as_ref(), desc.len() as usize),
|
||||
&mut self.random_file,
|
||||
desc.len() as usize,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
len = desc.len();
|
||||
@@ -76,7 +83,7 @@ impl RngEpollHandler {
|
||||
|
||||
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_cb
|
||||
.trigger(&VirtioInterruptType::Queue, Some(&self.queues[0]))
|
||||
.trigger(VirtioInterruptType::Queue(0))
|
||||
.map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
@@ -230,6 +237,7 @@ impl VirtioDevice for Rng {
|
||||
queue_evt: queue_evts.remove(0),
|
||||
kill_evt,
|
||||
pause_evt,
|
||||
access_platform: self.common.access_platform.clone(),
|
||||
};
|
||||
|
||||
let paused = self.common.paused.clone();
|
||||
@@ -261,6 +269,10 @@ impl VirtioDevice for Rng {
|
||||
event!("virtio-device", "reset", "id", &self.id);
|
||||
result
|
||||
}
|
||||
|
||||
fn set_access_platform(&mut self, access_platform: Arc<dyn AccessPlatform>) {
|
||||
self.common.set_access_platform(access_platform)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Rng {
|
||||
|
||||
@@ -15,6 +15,7 @@ use versionize_derive::Versionize;
|
||||
use virtio_queue::Queue;
|
||||
use vm_memory::{GuestAddress, GuestMemoryAtomic};
|
||||
use vm_migration::{MigratableError, Pausable, Snapshot, Snapshottable, VersionMapped};
|
||||
use vm_virtio::AccessPlatform;
|
||||
|
||||
#[derive(Clone, Versionize)]
|
||||
pub struct VirtioPciCommonConfigState {
|
||||
@@ -24,6 +25,7 @@ pub struct VirtioPciCommonConfigState {
|
||||
pub driver_feature_select: u32,
|
||||
pub queue_select: u16,
|
||||
pub msix_config: u16,
|
||||
pub msix_queues: Vec<u16>,
|
||||
}
|
||||
|
||||
impl VersionMapped for VirtioPciCommonConfigState {}
|
||||
@@ -51,12 +53,14 @@ impl VersionMapped for VirtioPciCommonConfigState {}
|
||||
/// le64 queue_avail; // 0x28 // read-write
|
||||
/// le64 queue_used; // 0x30 // read-write
|
||||
pub struct VirtioPciCommonConfig {
|
||||
pub access_platform: Option<Arc<dyn AccessPlatform>>,
|
||||
pub driver_status: u8,
|
||||
pub config_generation: u8,
|
||||
pub device_feature_select: u32,
|
||||
pub driver_feature_select: u32,
|
||||
pub queue_select: u16,
|
||||
pub msix_config: Arc<AtomicU16>,
|
||||
pub msix_queues: Arc<Mutex<Vec<u16>>>,
|
||||
}
|
||||
|
||||
impl VirtioPciCommonConfig {
|
||||
@@ -68,6 +72,7 @@ impl VirtioPciCommonConfig {
|
||||
driver_feature_select: self.driver_feature_select,
|
||||
queue_select: self.queue_select,
|
||||
msix_config: self.msix_config.load(Ordering::Acquire),
|
||||
msix_queues: self.msix_queues.lock().unwrap().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,13 +83,14 @@ impl VirtioPciCommonConfig {
|
||||
self.driver_feature_select = state.driver_feature_select;
|
||||
self.queue_select = state.queue_select;
|
||||
self.msix_config.store(state.msix_config, Ordering::Release);
|
||||
*(self.msix_queues.lock().unwrap()) = state.msix_queues.clone();
|
||||
}
|
||||
|
||||
pub fn read(
|
||||
&mut self,
|
||||
offset: u64,
|
||||
data: &mut [u8],
|
||||
queues: &mut Vec<Queue<GuestMemoryAtomic<GuestMemoryMmap>>>,
|
||||
queues: &mut [Queue<GuestMemoryAtomic<GuestMemoryMmap>>],
|
||||
device: Arc<Mutex<dyn VirtioDevice>>,
|
||||
) {
|
||||
assert!(data.len() <= 8);
|
||||
@@ -114,7 +120,7 @@ impl VirtioPciCommonConfig {
|
||||
&mut self,
|
||||
offset: u64,
|
||||
data: &[u8],
|
||||
queues: &mut Vec<Queue<GuestMemoryAtomic<GuestMemoryMmap>>>,
|
||||
queues: &mut [Queue<GuestMemoryAtomic<GuestMemoryMmap>>],
|
||||
device: Arc<Mutex<dyn VirtioDevice>>,
|
||||
) {
|
||||
assert!(data.len() <= 8);
|
||||
@@ -164,7 +170,7 @@ impl VirtioPciCommonConfig {
|
||||
0x12 => queues.len() as u16, // num_queues
|
||||
0x16 => self.queue_select,
|
||||
0x18 => self.with_queue(queues, |q| q.state.size).unwrap_or(0),
|
||||
0x1a => self.with_queue(queues, |q| q.state.vector).unwrap_or(0),
|
||||
0x1a => self.msix_queues.lock().unwrap()[self.queue_select as usize],
|
||||
0x1c => {
|
||||
if self.with_queue(queues, |q| q.state.ready).unwrap_or(false) {
|
||||
1
|
||||
@@ -184,15 +190,40 @@ impl VirtioPciCommonConfig {
|
||||
&mut self,
|
||||
offset: u64,
|
||||
value: u16,
|
||||
queues: &mut Vec<Queue<GuestMemoryAtomic<GuestMemoryMmap>>>,
|
||||
queues: &mut [Queue<GuestMemoryAtomic<GuestMemoryMmap>>],
|
||||
) {
|
||||
debug!("write_common_config_word: offset 0x{:x}", offset);
|
||||
match offset {
|
||||
0x10 => self.msix_config.store(value, Ordering::Release),
|
||||
0x16 => self.queue_select = value,
|
||||
0x18 => self.with_queue_mut(queues, |q| q.state.size = value),
|
||||
0x1a => self.with_queue_mut(queues, |q| q.state.vector = value),
|
||||
0x1c => self.with_queue_mut(queues, |q| q.enable(value == 1)),
|
||||
0x1a => self.msix_queues.lock().unwrap()[self.queue_select as usize] = value,
|
||||
0x1c => self.with_queue_mut(queues, |q| {
|
||||
let ready = value == 1;
|
||||
q.set_ready(ready);
|
||||
// Translate address of descriptor table and vrings.
|
||||
if let Some(access_platform) = &self.access_platform {
|
||||
if ready {
|
||||
let desc_table =
|
||||
access_platform.translate(q.state.desc_table.0, 0).unwrap();
|
||||
let avail_ring =
|
||||
access_platform.translate(q.state.avail_ring.0, 0).unwrap();
|
||||
let used_ring = access_platform.translate(q.state.used_ring.0, 0).unwrap();
|
||||
q.set_desc_table_address(
|
||||
Some((desc_table & 0xffff_ffff) as u32),
|
||||
Some((desc_table >> 32) as u32),
|
||||
);
|
||||
q.set_avail_ring_address(
|
||||
Some((avail_ring & 0xffff_ffff) as u32),
|
||||
Some((avail_ring >> 32) as u32),
|
||||
);
|
||||
q.set_used_ring_address(
|
||||
Some((used_ring & 0xffff_ffff) as u32),
|
||||
Some((used_ring >> 32) as u32),
|
||||
);
|
||||
}
|
||||
}
|
||||
}),
|
||||
_ => {
|
||||
warn!("invalid virtio register word write: 0x{:x}", offset);
|
||||
}
|
||||
@@ -225,7 +256,7 @@ impl VirtioPciCommonConfig {
|
||||
&mut self,
|
||||
offset: u64,
|
||||
value: u32,
|
||||
queues: &mut Vec<Queue<GuestMemoryAtomic<GuestMemoryMmap>>>,
|
||||
queues: &mut [Queue<GuestMemoryAtomic<GuestMemoryMmap>>],
|
||||
device: Arc<Mutex<dyn VirtioDevice>>,
|
||||
) {
|
||||
debug!("write_common_config_dword: offset 0x{:x}", offset);
|
||||
@@ -273,7 +304,7 @@ impl VirtioPciCommonConfig {
|
||||
&mut self,
|
||||
offset: u64,
|
||||
value: u64,
|
||||
queues: &mut Vec<Queue<GuestMemoryAtomic<GuestMemoryMmap>>>,
|
||||
queues: &mut [Queue<GuestMemoryAtomic<GuestMemoryMmap>>],
|
||||
) {
|
||||
debug!("write_common_config_qword: offset 0x{:x}", offset);
|
||||
match offset {
|
||||
@@ -299,7 +330,7 @@ impl VirtioPciCommonConfig {
|
||||
|
||||
fn with_queue_mut<F: FnOnce(&mut Queue<GuestMemoryAtomic<GuestMemoryMmap>>)>(
|
||||
&self,
|
||||
queues: &mut Vec<Queue<GuestMemoryAtomic<GuestMemoryMmap>>>,
|
||||
queues: &mut [Queue<GuestMemoryAtomic<GuestMemoryMmap>>],
|
||||
f: F,
|
||||
) {
|
||||
if let Some(queue) = queues.get_mut(self.queue_select as usize) {
|
||||
@@ -370,12 +401,14 @@ mod tests {
|
||||
#[test]
|
||||
fn write_base_regs() {
|
||||
let mut regs = VirtioPciCommonConfig {
|
||||
access_platform: None,
|
||||
driver_status: 0xaa,
|
||||
config_generation: 0x55,
|
||||
device_feature_select: 0x0,
|
||||
driver_feature_select: 0x0,
|
||||
queue_select: 0xff,
|
||||
msix_config: Arc::new(AtomicU16::new(0)),
|
||||
msix_queues: Arc::new(Mutex::new(vec![0; 3])),
|
||||
};
|
||||
|
||||
let dev = Arc::new(Mutex::new(DummyDevice(0)));
|
||||
|
||||
@@ -29,8 +29,7 @@ use std::sync::atomic::{AtomicBool, AtomicU16, AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use virtio_queue::AccessPlatform;
|
||||
use virtio_queue::{defs::VIRTQ_MSI_NO_VECTOR, Error as QueueError, Queue};
|
||||
use virtio_queue::{Error as QueueError, Queue};
|
||||
use vm_allocator::{AddressAllocator, SystemAllocator};
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
|
||||
@@ -40,8 +39,12 @@ use vm_memory::{Address, ByteValued, GuestAddress, GuestMemoryAtomic, GuestUsize
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||
};
|
||||
use vm_virtio::AccessPlatform;
|
||||
use vmm_sys_util::{errno::Result, eventfd::EventFd};
|
||||
|
||||
/// Vector value used to disable MSI for a queue.
|
||||
const VIRTQ_MSI_NO_VECTOR: u16 = 0xffff;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
/// Failed to retrieve queue ring's index.
|
||||
@@ -267,7 +270,6 @@ struct QueueState {
|
||||
max_size: u16,
|
||||
size: u16,
|
||||
ready: bool,
|
||||
vector: u16,
|
||||
desc_table: u64,
|
||||
avail_ring: u64,
|
||||
used_ring: u64,
|
||||
@@ -353,22 +355,25 @@ impl VirtioPciDevice {
|
||||
use_64bit_bar: bool,
|
||||
) -> Result<Self> {
|
||||
let device_clone = device.clone();
|
||||
let locked_device = device_clone.lock().unwrap();
|
||||
let mut locked_device = device_clone.lock().unwrap();
|
||||
let mut queue_evts = Vec::new();
|
||||
for _ in locked_device.queue_max_sizes().iter() {
|
||||
queue_evts.push(EventFd::new(EFD_NONBLOCK)?)
|
||||
}
|
||||
let num_queues = locked_device.queue_max_sizes().len();
|
||||
|
||||
if let Some(access_platform) = &access_platform {
|
||||
locked_device.set_access_platform(access_platform.clone());
|
||||
}
|
||||
|
||||
let queues = locked_device
|
||||
.queue_max_sizes()
|
||||
.iter()
|
||||
.map(|&s| {
|
||||
let mut queue =
|
||||
Queue::<GuestMemoryAtomic<GuestMemoryMmap>, virtio_queue::QueueState>::new(
|
||||
memory.clone(),
|
||||
s,
|
||||
);
|
||||
queue.state.access_platform = access_platform.clone();
|
||||
queue
|
||||
Queue::<GuestMemoryAtomic<GuestMemoryMmap>, virtio_queue::QueueState>::new(
|
||||
memory.clone(),
|
||||
s,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -423,12 +428,14 @@ impl VirtioPciDevice {
|
||||
id,
|
||||
configuration,
|
||||
common_config: VirtioPciCommonConfig {
|
||||
access_platform,
|
||||
driver_status: 0,
|
||||
config_generation: 0,
|
||||
device_feature_select: 0,
|
||||
driver_feature_select: 0,
|
||||
queue_select: 0,
|
||||
msix_config: Arc::new(AtomicU16::new(VIRTQ_MSI_NO_VECTOR)),
|
||||
msix_queues: Arc::new(Mutex::new(vec![VIRTQ_MSI_NO_VECTOR; num_queues])),
|
||||
},
|
||||
msix_config,
|
||||
msix_num,
|
||||
@@ -453,6 +460,7 @@ impl VirtioPciDevice {
|
||||
virtio_pci_device.virtio_interrupt = Some(Arc::new(VirtioInterruptMsix::new(
|
||||
msix_config.clone(),
|
||||
virtio_pci_device.common_config.msix_config.clone(),
|
||||
virtio_pci_device.common_config.msix_queues.clone(),
|
||||
virtio_pci_device.interrupt_source_group.clone(),
|
||||
)));
|
||||
}
|
||||
@@ -471,7 +479,6 @@ impl VirtioPciDevice {
|
||||
max_size: q.max_size(),
|
||||
size: q.state.size,
|
||||
ready: q.state.ready,
|
||||
vector: q.state.vector,
|
||||
desc_table: q.state.desc_table.0,
|
||||
avail_ring: q.state.avail_ring.0,
|
||||
used_ring: q.state.used_ring.0,
|
||||
@@ -490,7 +497,6 @@ impl VirtioPciDevice {
|
||||
for (i, queue) in self.queues.iter_mut().enumerate() {
|
||||
queue.state.size = state.queues[i].size;
|
||||
queue.state.ready = state.queues[i].ready;
|
||||
queue.state.vector = state.queues[i].vector;
|
||||
queue.state.desc_table = GuestAddress(state.queues[i].desc_table);
|
||||
queue.state.avail_ring = GuestAddress(state.queues[i].avail_ring);
|
||||
queue.state.used_ring = GuestAddress(state.queues[i].used_ring);
|
||||
@@ -716,6 +722,7 @@ impl VirtioTransport for VirtioPciDevice {
|
||||
pub struct VirtioInterruptMsix {
|
||||
msix_config: Arc<Mutex<MsixConfig>>,
|
||||
config_vector: Arc<AtomicU16>,
|
||||
queues_vectors: Arc<Mutex<Vec<u16>>>,
|
||||
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
||||
}
|
||||
|
||||
@@ -723,30 +730,24 @@ impl VirtioInterruptMsix {
|
||||
pub fn new(
|
||||
msix_config: Arc<Mutex<MsixConfig>>,
|
||||
config_vector: Arc<AtomicU16>,
|
||||
queues_vectors: Arc<Mutex<Vec<u16>>>,
|
||||
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
||||
) -> Self {
|
||||
VirtioInterruptMsix {
|
||||
msix_config,
|
||||
config_vector,
|
||||
queues_vectors,
|
||||
interrupt_source_group,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioInterrupt for VirtioInterruptMsix {
|
||||
fn trigger(
|
||||
&self,
|
||||
int_type: &VirtioInterruptType,
|
||||
queue: Option<&Queue<GuestMemoryAtomic<GuestMemoryMmap>>>,
|
||||
) -> std::result::Result<(), std::io::Error> {
|
||||
fn trigger(&self, int_type: VirtioInterruptType) -> std::result::Result<(), std::io::Error> {
|
||||
let vector = match int_type {
|
||||
VirtioInterruptType::Config => self.config_vector.load(Ordering::Acquire),
|
||||
VirtioInterruptType::Queue => {
|
||||
if let Some(q) = queue {
|
||||
q.state.vector
|
||||
} else {
|
||||
0
|
||||
}
|
||||
VirtioInterruptType::Queue(queue_index) => {
|
||||
self.queues_vectors.lock().unwrap()[queue_index as usize]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -770,19 +771,11 @@ impl VirtioInterrupt for VirtioInterruptMsix {
|
||||
.trigger(vector as InterruptIndex)
|
||||
}
|
||||
|
||||
fn notifier(
|
||||
&self,
|
||||
int_type: &VirtioInterruptType,
|
||||
queue: Option<&Queue<GuestMemoryAtomic<GuestMemoryMmap>>>,
|
||||
) -> Option<EventFd> {
|
||||
fn notifier(&self, int_type: VirtioInterruptType) -> Option<EventFd> {
|
||||
let vector = match int_type {
|
||||
VirtioInterruptType::Config => self.config_vector.load(Ordering::Acquire),
|
||||
VirtioInterruptType::Queue => {
|
||||
if let Some(q) = queue {
|
||||
q.state.vector
|
||||
} else {
|
||||
0
|
||||
}
|
||||
VirtioInterruptType::Queue(queue_index) => {
|
||||
self.queues_vectors.lock().unwrap()[queue_index as usize]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -965,24 +958,24 @@ impl PciDevice for VirtioPciDevice {
|
||||
&mut self.queues,
|
||||
self.device.clone(),
|
||||
),
|
||||
o if ISR_CONFIG_BAR_OFFSET <= o && o < ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE => {
|
||||
o if (ISR_CONFIG_BAR_OFFSET..ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE).contains(&o) => {
|
||||
if let Some(v) = data.get_mut(0) {
|
||||
// Reading this register resets it to 0.
|
||||
*v = self.interrupt_status.swap(0, Ordering::AcqRel) as u8;
|
||||
}
|
||||
}
|
||||
o if DEVICE_CONFIG_BAR_OFFSET <= o
|
||||
&& o < DEVICE_CONFIG_BAR_OFFSET + DEVICE_CONFIG_SIZE =>
|
||||
o if (DEVICE_CONFIG_BAR_OFFSET..DEVICE_CONFIG_BAR_OFFSET + DEVICE_CONFIG_SIZE)
|
||||
.contains(&o) =>
|
||||
{
|
||||
let device = self.device.lock().unwrap();
|
||||
device.read_config(o - DEVICE_CONFIG_BAR_OFFSET, data);
|
||||
}
|
||||
o if NOTIFICATION_BAR_OFFSET <= o
|
||||
&& o < NOTIFICATION_BAR_OFFSET + NOTIFICATION_SIZE =>
|
||||
o if (NOTIFICATION_BAR_OFFSET..NOTIFICATION_BAR_OFFSET + NOTIFICATION_SIZE)
|
||||
.contains(&o) =>
|
||||
{
|
||||
// Handled with ioeventfds.
|
||||
}
|
||||
o if MSIX_TABLE_BAR_OFFSET <= o && o < MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE => {
|
||||
o if (MSIX_TABLE_BAR_OFFSET..MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE).contains(&o) => {
|
||||
if let Some(msix_config) = &self.msix_config {
|
||||
msix_config
|
||||
.lock()
|
||||
@@ -990,7 +983,7 @@ impl PciDevice for VirtioPciDevice {
|
||||
.read_table(o - MSIX_TABLE_BAR_OFFSET, data);
|
||||
}
|
||||
}
|
||||
o if MSIX_PBA_BAR_OFFSET <= o && o < MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE => {
|
||||
o if (MSIX_PBA_BAR_OFFSET..MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE).contains(&o) => {
|
||||
if let Some(msix_config) = &self.msix_config {
|
||||
msix_config
|
||||
.lock()
|
||||
@@ -1010,24 +1003,25 @@ impl PciDevice for VirtioPciDevice {
|
||||
&mut self.queues,
|
||||
self.device.clone(),
|
||||
),
|
||||
o if ISR_CONFIG_BAR_OFFSET <= o && o < ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE => {
|
||||
o if (ISR_CONFIG_BAR_OFFSET..ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE).contains(&o) => {
|
||||
if let Some(v) = data.get(0) {
|
||||
self.interrupt_status
|
||||
.fetch_and(!(*v as usize), Ordering::AcqRel);
|
||||
}
|
||||
}
|
||||
o if DEVICE_CONFIG_BAR_OFFSET <= o
|
||||
&& o < DEVICE_CONFIG_BAR_OFFSET + DEVICE_CONFIG_SIZE =>
|
||||
o if (DEVICE_CONFIG_BAR_OFFSET..DEVICE_CONFIG_BAR_OFFSET + DEVICE_CONFIG_SIZE)
|
||||
.contains(&o) =>
|
||||
{
|
||||
let mut device = self.device.lock().unwrap();
|
||||
device.write_config(o - DEVICE_CONFIG_BAR_OFFSET, data);
|
||||
}
|
||||
o if NOTIFICATION_BAR_OFFSET <= o
|
||||
&& o < NOTIFICATION_BAR_OFFSET + NOTIFICATION_SIZE =>
|
||||
o if (NOTIFICATION_BAR_OFFSET..NOTIFICATION_BAR_OFFSET + NOTIFICATION_SIZE)
|
||||
.contains(&o) =>
|
||||
{
|
||||
// Handled with ioeventfds.
|
||||
error!("Unexpected write to notification BAR: offset = 0x{:x}", o);
|
||||
}
|
||||
o if MSIX_TABLE_BAR_OFFSET <= o && o < MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE => {
|
||||
o if (MSIX_TABLE_BAR_OFFSET..MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE).contains(&o) => {
|
||||
if let Some(msix_config) = &self.msix_config {
|
||||
msix_config
|
||||
.lock()
|
||||
@@ -1035,7 +1029,7 @@ impl PciDevice for VirtioPciDevice {
|
||||
.write_table(o - MSIX_TABLE_BAR_OFFSET, data);
|
||||
}
|
||||
}
|
||||
o if MSIX_PBA_BAR_OFFSET <= o && o < MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE => {
|
||||
o if (MSIX_PBA_BAR_OFFSET..MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE).contains(&o) => {
|
||||
if let Some(msix_config) = &self.msix_config {
|
||||
msix_config
|
||||
.lock()
|
||||
|
||||
@@ -167,7 +167,11 @@ impl Blk {
|
||||
device_type: VirtioDeviceType::Block as u32,
|
||||
queue_sizes: vec![vu_cfg.queue_size; num_queues],
|
||||
avail_features: acked_features,
|
||||
acked_features: 0,
|
||||
// If part of the available features that have been acked, the
|
||||
// PROTOCOL_FEATURES bit must be already set through the VIRTIO
|
||||
// acked features as we know the guest would never ack it, thus
|
||||
// the feature would be lost.
|
||||
acked_features: acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(),
|
||||
paused_sync: Some(Arc::new(Barrier::new(2))),
|
||||
min_queues: DEFAULT_QUEUE_NUMBER as u16,
|
||||
..Default::default()
|
||||
@@ -288,12 +292,6 @@ impl VirtioDevice for Blk {
|
||||
|
||||
let slave_req_handler: Option<MasterReqHandler<SlaveReqHandler>> = None;
|
||||
|
||||
// The backend acknowledged features must contain the protocol feature
|
||||
// bit in case it was initially set but lost through the features
|
||||
// negotiation with the guest.
|
||||
let backend_acked_features = self.common.acked_features
|
||||
| (self.common.avail_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits());
|
||||
|
||||
// Run a dedicated thread for handling potential reconnections with
|
||||
// the backend.
|
||||
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
||||
@@ -303,7 +301,7 @@ impl VirtioDevice for Blk {
|
||||
queues,
|
||||
queue_evts,
|
||||
interrupt_cb,
|
||||
backend_acked_features,
|
||||
self.common.acked_features,
|
||||
slave_req_handler,
|
||||
kill_evt,
|
||||
pause_evt,
|
||||
@@ -417,6 +415,10 @@ impl Migratable for Blk {
|
||||
self.vu_common.dirty_log(&self.guest_memory)
|
||||
}
|
||||
|
||||
fn start_migration(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
self.vu_common.start_migration()
|
||||
}
|
||||
|
||||
fn complete_migration(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
self.vu_common
|
||||
.complete_migration(self.common.kill_evt.take())
|
||||
|
||||
@@ -413,7 +413,11 @@ impl Fs {
|
||||
common: VirtioCommon {
|
||||
device_type: VirtioDeviceType::Fs as u32,
|
||||
avail_features: acked_features,
|
||||
acked_features: 0,
|
||||
// If part of the available features that have been acked, the
|
||||
// PROTOCOL_FEATURES bit must be already set through the VIRTIO
|
||||
// acked features as we know the guest would never ack it, thus
|
||||
// the feature would be lost.
|
||||
acked_features: acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(),
|
||||
queue_sizes: vec![queue_size; num_queues],
|
||||
paused_sync: Some(Arc::new(Barrier::new(2))),
|
||||
min_queues: DEFAULT_QUEUE_NUMBER as u16,
|
||||
@@ -538,12 +542,6 @@ impl VirtioDevice for Fs {
|
||||
None
|
||||
};
|
||||
|
||||
// The backend acknowledged features must contain the protocol feature
|
||||
// bit in case it was initially set but lost through the features
|
||||
// negotiation with the guest.
|
||||
let backend_acked_features = self.common.acked_features
|
||||
| (self.common.avail_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits());
|
||||
|
||||
// Run a dedicated thread for handling potential reconnections with
|
||||
// the backend.
|
||||
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
||||
@@ -553,7 +551,7 @@ impl VirtioDevice for Fs {
|
||||
queues,
|
||||
queue_evts,
|
||||
interrupt_cb,
|
||||
backend_acked_features,
|
||||
self.common.acked_features,
|
||||
slave_req_handler,
|
||||
kill_evt,
|
||||
pause_evt,
|
||||
@@ -698,6 +696,10 @@ impl Migratable for Fs {
|
||||
self.vu_common.dirty_log(&self.guest_memory)
|
||||
}
|
||||
|
||||
fn start_migration(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
self.vu_common.start_migration()
|
||||
}
|
||||
|
||||
fn complete_migration(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
self.vu_common
|
||||
.complete_migration(self.common.kill_evt.take())
|
||||
|
||||
@@ -451,7 +451,6 @@ impl VhostUserCommon {
|
||||
&mut self,
|
||||
guest_memory: &Option<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
) -> std::result::Result<(), MigratableError> {
|
||||
self.migration_started = true;
|
||||
if let Some(vu) = &self.vu {
|
||||
if let Some(guest_memory) = guest_memory {
|
||||
let last_ram_addr = guest_memory.memory().last_addr().raw_value();
|
||||
@@ -475,7 +474,6 @@ impl VhostUserCommon {
|
||||
}
|
||||
|
||||
pub fn stop_dirty_log(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
self.migration_started = false;
|
||||
if let Some(vu) = &self.vu {
|
||||
vu.lock().unwrap().stop_dirty_log().map_err(|e| {
|
||||
MigratableError::StopDirtyLog(anyhow!(
|
||||
@@ -509,10 +507,17 @@ impl VhostUserCommon {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_migration(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
self.migration_started = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn complete_migration(
|
||||
&mut self,
|
||||
kill_evt: Option<EventFd>,
|
||||
) -> std::result::Result<(), MigratableError> {
|
||||
self.migration_started = false;
|
||||
|
||||
// Make sure the device thread is killed in order to prevent from
|
||||
// reconnections to the socket.
|
||||
if let Some(kill_evt) = kill_evt {
|
||||
|
||||
@@ -89,7 +89,7 @@ impl EpollHelperHandler for NetCtrlEpollHandler {
|
||||
error!("failed to get ctl queue event: {:?}", e);
|
||||
return true;
|
||||
}
|
||||
if let Err(e) = self.ctrl_q.process(&mut self.queue) {
|
||||
if let Err(e) = self.ctrl_q.process(&mut self.queue, None) {
|
||||
error!("failed to process ctrl queue: {:?}", e);
|
||||
return true;
|
||||
}
|
||||
@@ -222,7 +222,11 @@ impl Net {
|
||||
device_type: VirtioDeviceType::Net as u32,
|
||||
queue_sizes: vec![vu_cfg.queue_size; num_queues],
|
||||
avail_features: acked_features,
|
||||
acked_features: 0,
|
||||
// If part of the available features that have been acked, the
|
||||
// PROTOCOL_FEATURES bit must be already set through the VIRTIO
|
||||
// acked features as we know the guest would never ack it, thus
|
||||
// the feature would be lost.
|
||||
acked_features: acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(),
|
||||
paused_sync: Some(Arc::new(Barrier::new(2))),
|
||||
min_queues: DEFAULT_QUEUE_NUMBER as u16,
|
||||
..Default::default()
|
||||
@@ -261,9 +265,13 @@ impl Net {
|
||||
self.vu_common.acked_protocol_features = state.acked_protocol_features;
|
||||
self.vu_common.vu_num_queues = state.vu_num_queues;
|
||||
|
||||
// The backend acknowledged features must not contain VIRTIO_NET_F_MAC
|
||||
// since we don't expect the backend to handle it.
|
||||
let backend_acked_features = self.common.acked_features & !(1 << VIRTIO_NET_F_MAC);
|
||||
|
||||
if let Err(e) = self
|
||||
.vu_common
|
||||
.restore_backend_connection(self.common.acked_features)
|
||||
.restore_backend_connection(backend_acked_features)
|
||||
{
|
||||
error!(
|
||||
"Failed restoring connection with vhost-user backend: {:?}",
|
||||
@@ -355,12 +363,9 @@ impl VirtioDevice for Net {
|
||||
|
||||
let slave_req_handler: Option<MasterReqHandler<SlaveReqHandler>> = None;
|
||||
|
||||
// The backend acknowledged features must contain the protocol feature
|
||||
// bit in case it was initially set but lost through the features
|
||||
// negotiation with the guest. Additionally, it must not contain
|
||||
// VIRTIO_NET_F_MAC since we don't expect the backend to handle it.
|
||||
let backend_acked_features = self.common.acked_features & !(1 << VIRTIO_NET_F_MAC)
|
||||
| (self.common.avail_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits());
|
||||
// The backend acknowledged features must not contain VIRTIO_NET_F_MAC
|
||||
// since we don't expect the backend to handle it.
|
||||
let backend_acked_features = self.common.acked_features & !(1 << VIRTIO_NET_F_MAC);
|
||||
|
||||
// Run a dedicated thread for handling potential reconnections with
|
||||
// the backend.
|
||||
@@ -488,6 +493,10 @@ impl Migratable for Net {
|
||||
self.vu_common.dirty_log(&self.guest_memory)
|
||||
}
|
||||
|
||||
fn start_migration(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
self.vu_common.start_migration()
|
||||
}
|
||||
|
||||
fn complete_migration(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
self.vu_common
|
||||
.complete_migration(self.common.kill_evt.take())
|
||||
|
||||
@@ -253,7 +253,7 @@ impl VhostUserHandle {
|
||||
.map_err(Error::VhostUserSetVringBase)?;
|
||||
|
||||
if let Some(eventfd) =
|
||||
virtio_interrupt.notifier(&VirtioInterruptType::Queue, Some(&queue))
|
||||
virtio_interrupt.notifier(VirtioInterruptType::Queue(queue_index as u16))
|
||||
{
|
||||
self.vu
|
||||
.set_vring_call(queue_index, &eventfd)
|
||||
|
||||
@@ -823,6 +823,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let conn = match conn_state {
|
||||
|
||||
@@ -52,6 +52,7 @@ use vm_memory::GuestMemoryAtomic;
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||
};
|
||||
use vm_virtio::AccessPlatform;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
@@ -93,6 +94,7 @@ pub struct VsockEpollHandler<B: VsockBackend> {
|
||||
pub pause_evt: EventFd,
|
||||
pub interrupt_cb: Arc<dyn VirtioInterrupt>,
|
||||
pub backend: Arc<RwLock<B>>,
|
||||
pub access_platform: Option<Arc<dyn AccessPlatform>>,
|
||||
}
|
||||
|
||||
impl<B> VsockEpollHandler<B>
|
||||
@@ -102,14 +104,11 @@ where
|
||||
/// Signal the guest driver that we've used some virtio buffers that it had previously made
|
||||
/// available.
|
||||
///
|
||||
fn signal_used_queue(
|
||||
&self,
|
||||
queue: &Queue<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
) -> result::Result<(), DeviceError> {
|
||||
fn signal_used_queue(&self, queue_index: u16) -> result::Result<(), DeviceError> {
|
||||
debug!("vsock: raising IRQ");
|
||||
|
||||
self.interrupt_cb
|
||||
.trigger(&VirtioInterruptType::Queue, Some(queue))
|
||||
.trigger(VirtioInterruptType::Queue(queue_index))
|
||||
.map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
@@ -127,7 +126,10 @@ where
|
||||
|
||||
let mut avail_iter = self.queues[0].iter().map_err(DeviceError::QueueIterator)?;
|
||||
for mut desc_chain in &mut avail_iter {
|
||||
let used_len = match VsockPacket::from_rx_virtq_head(&mut desc_chain) {
|
||||
let used_len = match VsockPacket::from_rx_virtq_head(
|
||||
&mut desc_chain,
|
||||
self.access_platform.as_ref(),
|
||||
) {
|
||||
Ok(mut pkt) => {
|
||||
if self.backend.write().unwrap().recv_pkt(&mut pkt).is_ok() {
|
||||
pkt.hdr().len() as u32 + pkt.len()
|
||||
@@ -155,7 +157,7 @@ where
|
||||
}
|
||||
|
||||
if used_count > 0 {
|
||||
self.signal_used_queue(&self.queues[0])
|
||||
self.signal_used_queue(0)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -172,7 +174,10 @@ where
|
||||
|
||||
let mut avail_iter = self.queues[1].iter().map_err(DeviceError::QueueIterator)?;
|
||||
for mut desc_chain in &mut avail_iter {
|
||||
let pkt = match VsockPacket::from_tx_virtq_head(&mut desc_chain) {
|
||||
let pkt = match VsockPacket::from_tx_virtq_head(
|
||||
&mut desc_chain,
|
||||
self.access_platform.as_ref(),
|
||||
) {
|
||||
Ok(pkt) => pkt,
|
||||
Err(e) => {
|
||||
error!("vsock: error reading TX packet: {:?}", e);
|
||||
@@ -198,7 +203,7 @@ where
|
||||
}
|
||||
|
||||
if used_count > 0 {
|
||||
self.signal_used_queue(&self.queues[1])
|
||||
self.signal_used_queue(1)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -441,6 +446,7 @@ where
|
||||
pause_evt,
|
||||
interrupt_cb,
|
||||
backend: self.backend.clone(),
|
||||
access_platform: self.common.access_platform.clone(),
|
||||
};
|
||||
|
||||
let paused = self.common.paused.clone();
|
||||
@@ -475,6 +481,10 @@ where
|
||||
fn shutdown(&mut self) {
|
||||
std::fs::remove_file(&self.path).ok();
|
||||
}
|
||||
|
||||
fn set_access_platform(&mut self, access_platform: Arc<dyn AccessPlatform>) {
|
||||
self.common.set_access_platform(access_platform)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Pausable for Vsock<B>
|
||||
@@ -617,8 +627,8 @@ mod tests {
|
||||
let ctx = test_ctx.create_epoll_handler_context();
|
||||
let memory = GuestMemoryAtomic::new(test_ctx.mem.clone());
|
||||
|
||||
let queue = Queue::new(memory, 256);
|
||||
assert!(ctx.handler.signal_used_queue(&queue).is_ok());
|
||||
let _queue: Queue<GuestMemoryAtomic<GuestMemoryMmap>> = Queue::new(memory, 256);
|
||||
assert!(ctx.handler.signal_used_queue(0).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ mod tests {
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use virtio_queue::{defs::VIRTQ_DESC_F_NEXT, defs::VIRTQ_DESC_F_WRITE, Queue};
|
||||
use virtio_queue::{defs::VIRTQ_DESC_F_NEXT, defs::VIRTQ_DESC_F_WRITE};
|
||||
use vm_memory::{GuestAddress, GuestMemoryAtomic};
|
||||
use vm_virtio::queue::testing::VirtQueue as GuestQ;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
@@ -180,8 +180,7 @@ mod tests {
|
||||
impl VirtioInterrupt for NoopVirtioInterrupt {
|
||||
fn trigger(
|
||||
&self,
|
||||
_int_type: &VirtioInterruptType,
|
||||
_queue: Option<&Queue<GuestMemoryAtomic<GuestMemoryMmap>>>,
|
||||
_int_type: VirtioInterruptType,
|
||||
) -> std::result::Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -329,6 +328,7 @@ mod tests {
|
||||
pause_evt: EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||
interrupt_cb,
|
||||
backend: Arc::new(RwLock::new(TestBackend::new())),
|
||||
access_platform: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@
|
||||
/// to temporary buffers, before passing it on to the vsock backend.
|
||||
///
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::defs;
|
||||
use super::{Result, VsockError};
|
||||
use crate::{get_host_address_range, GuestMemoryMmap};
|
||||
use virtio_queue::DescriptorChain;
|
||||
use vm_memory::GuestMemoryLoadGuard;
|
||||
use vm_virtio::{AccessPlatform, Translatable};
|
||||
|
||||
// The vsock packet header is defined by the C struct:
|
||||
//
|
||||
@@ -107,6 +109,7 @@ impl VsockPacket {
|
||||
///
|
||||
pub fn from_tx_virtq_head(
|
||||
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap>>,
|
||||
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
||||
) -> Result<Self> {
|
||||
let head = desc_chain.next().ok_or(VsockError::HdrDescMissing)?;
|
||||
|
||||
@@ -122,8 +125,12 @@ impl VsockPacket {
|
||||
}
|
||||
|
||||
let mut pkt = Self {
|
||||
hdr: get_host_address_range(desc_chain.memory(), head.addr(), VSOCK_PKT_HDR_SIZE)
|
||||
.ok_or(VsockError::GuestMemory)? as *mut u8,
|
||||
hdr: get_host_address_range(
|
||||
desc_chain.memory(),
|
||||
head.addr().translate(access_platform, head.len() as usize),
|
||||
VSOCK_PKT_HDR_SIZE,
|
||||
)
|
||||
.ok_or(VsockError::GuestMemory)? as *mut u8,
|
||||
buf: None,
|
||||
buf_size: 0,
|
||||
};
|
||||
@@ -155,8 +162,14 @@ impl VsockPacket {
|
||||
|
||||
pkt.buf_size = buf_desc.len() as usize;
|
||||
pkt.buf = Some(
|
||||
get_host_address_range(desc_chain.memory(), buf_desc.addr(), pkt.buf_size)
|
||||
.ok_or(VsockError::GuestMemory)? as *mut u8,
|
||||
get_host_address_range(
|
||||
desc_chain.memory(),
|
||||
buf_desc
|
||||
.addr()
|
||||
.translate(access_platform, buf_desc.len() as usize),
|
||||
pkt.buf_size,
|
||||
)
|
||||
.ok_or(VsockError::GuestMemory)? as *mut u8,
|
||||
);
|
||||
|
||||
Ok(pkt)
|
||||
@@ -169,6 +182,7 @@ impl VsockPacket {
|
||||
///
|
||||
pub fn from_rx_virtq_head(
|
||||
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap>>,
|
||||
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
||||
) -> Result<Self> {
|
||||
let head = desc_chain.next().ok_or(VsockError::HdrDescMissing)?;
|
||||
|
||||
@@ -191,11 +205,21 @@ impl VsockPacket {
|
||||
let buf_size = buf_desc.len() as usize;
|
||||
|
||||
Ok(Self {
|
||||
hdr: get_host_address_range(desc_chain.memory(), head.addr(), VSOCK_PKT_HDR_SIZE)
|
||||
.ok_or(VsockError::GuestMemory)? as *mut u8,
|
||||
hdr: get_host_address_range(
|
||||
desc_chain.memory(),
|
||||
head.addr().translate(access_platform, head.len() as usize),
|
||||
VSOCK_PKT_HDR_SIZE,
|
||||
)
|
||||
.ok_or(VsockError::GuestMemory)? as *mut u8,
|
||||
buf: Some(
|
||||
get_host_address_range(desc_chain.memory(), buf_desc.addr(), buf_size)
|
||||
.ok_or(VsockError::GuestMemory)? as *mut u8,
|
||||
get_host_address_range(
|
||||
desc_chain.memory(),
|
||||
buf_desc
|
||||
.addr()
|
||||
.translate(access_platform, buf_desc.len() as usize),
|
||||
buf_size,
|
||||
)
|
||||
.ok_or(VsockError::GuestMemory)? as *mut u8,
|
||||
),
|
||||
buf_size,
|
||||
})
|
||||
@@ -380,6 +404,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap(),
|
||||
None,
|
||||
) {
|
||||
Err($err) => (),
|
||||
Ok(_) => panic!("Packet assembly should've failed!"),
|
||||
@@ -410,6 +435,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(pkt.hdr().len(), VSOCK_PKT_HDR_SIZE);
|
||||
@@ -447,6 +473,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(pkt.buf().is_none());
|
||||
@@ -504,6 +531,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(pkt.hdr().len(), VSOCK_PKT_HDR_SIZE);
|
||||
@@ -560,6 +588,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -650,6 +679,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -846,6 +846,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let uds_path = format!("test_vsock_{}.sock", name);
|
||||
|
||||
@@ -96,7 +96,7 @@ impl WatchdogEpollHandler {
|
||||
|
||||
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_cb
|
||||
.trigger(&VirtioInterruptType::Queue, Some(&self.queues[0]))
|
||||
.trigger(VirtioInterruptType::Queue(0))
|
||||
.map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "virtio-queue"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
description = "virtio queue implementation"
|
||||
repository = "https://github.com/rust-vmm/vm-virtio"
|
||||
keywords = ["virtio"]
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0 OR BSD-3-Clause"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vm-memory = "0.7.0"
|
||||
vmm-sys-util = ">=0.8.0"
|
||||
log = ">=0.4.6"
|
||||
|
||||
[dev-dependencies]
|
||||
vm-memory = { version = "0.7.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
memoffset = "~0"
|
||||
@@ -1,490 +0,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.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// Copyright (C) 2020-2021 Alibaba Cloud. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::mem::size_of;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory};
|
||||
|
||||
use crate::defs::VIRTQ_DESCRIPTOR_SIZE;
|
||||
use crate::{AccessPlatform, Descriptor, Error};
|
||||
|
||||
/// A virtio descriptor chain.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DescriptorChain<M> {
|
||||
mem: M,
|
||||
desc_table: GuestAddress,
|
||||
queue_size: u16,
|
||||
head_index: u16,
|
||||
next_index: u16,
|
||||
ttl: u16,
|
||||
is_indirect: bool,
|
||||
access_platform: Option<Arc<dyn AccessPlatform>>,
|
||||
}
|
||||
|
||||
impl<M> DescriptorChain<M>
|
||||
where
|
||||
M: Deref,
|
||||
M::Target: GuestMemory,
|
||||
{
|
||||
fn with_ttl(
|
||||
mem: M,
|
||||
desc_table: GuestAddress,
|
||||
queue_size: u16,
|
||||
ttl: u16,
|
||||
head_index: u16,
|
||||
access_platform: Option<Arc<dyn AccessPlatform>>,
|
||||
) -> Self {
|
||||
DescriptorChain {
|
||||
mem,
|
||||
desc_table,
|
||||
queue_size,
|
||||
head_index,
|
||||
next_index: head_index,
|
||||
ttl,
|
||||
is_indirect: false,
|
||||
access_platform,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `DescriptorChain` instance.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `mem` - the `GuestMemory` object that can be used to access the buffers pointed to by the
|
||||
/// descriptor chain.
|
||||
/// * `desc_table` - the address of the descriptor table.
|
||||
/// * `queue_size` - the size of the queue, which is also the maximum size of a descriptor
|
||||
/// chain.
|
||||
/// * `head_index` - the descriptor index of the chain head.
|
||||
pub(crate) fn new(
|
||||
mem: M,
|
||||
desc_table: GuestAddress,
|
||||
queue_size: u16,
|
||||
head_index: u16,
|
||||
access_platform: Option<Arc<dyn AccessPlatform>>,
|
||||
) -> Self {
|
||||
Self::with_ttl(
|
||||
mem,
|
||||
desc_table,
|
||||
queue_size,
|
||||
queue_size,
|
||||
head_index,
|
||||
access_platform,
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the descriptor index of the chain head.
|
||||
pub fn head_index(&self) -> u16 {
|
||||
self.head_index
|
||||
}
|
||||
|
||||
/// Return a `GuestMemory` object that can be used to access the buffers pointed to by the
|
||||
/// descriptor chain.
|
||||
pub fn memory(&self) -> &M::Target {
|
||||
self.mem.deref()
|
||||
}
|
||||
|
||||
/// Return an iterator that only yields the readable descriptors in the chain.
|
||||
pub fn readable(self) -> DescriptorChainRwIter<M> {
|
||||
DescriptorChainRwIter {
|
||||
chain: self,
|
||||
writable: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an iterator that only yields the writable descriptors in the chain.
|
||||
pub fn writable(self) -> DescriptorChainRwIter<M> {
|
||||
DescriptorChainRwIter {
|
||||
chain: self,
|
||||
writable: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Alters the internal state of the `DescriptorChain` to switch iterating over an
|
||||
// indirect descriptor table defined by `desc`.
|
||||
fn switch_to_indirect_table(&mut self, desc: Descriptor) -> Result<(), Error> {
|
||||
// Check the VIRTQ_DESC_F_INDIRECT flag (i.e., is_indirect) is not set inside
|
||||
// an indirect descriptor.
|
||||
// (see VIRTIO Spec, Section 2.6.5.3.1 Driver Requirements: Indirect Descriptors)
|
||||
if self.is_indirect {
|
||||
return Err(Error::InvalidIndirectDescriptor);
|
||||
}
|
||||
|
||||
// Check the target indirect descriptor table is correctly aligned.
|
||||
if desc.addr().raw_value() & (VIRTQ_DESCRIPTOR_SIZE as u64 - 1) != 0
|
||||
|| desc.len() & (VIRTQ_DESCRIPTOR_SIZE as u32 - 1) != 0
|
||||
{
|
||||
return Err(Error::InvalidIndirectDescriptorTable);
|
||||
}
|
||||
|
||||
// It is safe to do a plain division since we checked above that desc.len() is a multiple of
|
||||
// VIRTQ_DESCRIPTOR_SIZE, and VIRTQ_DESCRIPTOR_SIZE is != 0.
|
||||
let table_len = (desc.len() as usize) / VIRTQ_DESCRIPTOR_SIZE;
|
||||
if table_len > usize::from(u16::MAX) {
|
||||
return Err(Error::InvalidIndirectDescriptorTable);
|
||||
}
|
||||
|
||||
self.desc_table = desc.addr();
|
||||
// try_from cannot fail as we've checked table_len above
|
||||
self.queue_size = u16::try_from(table_len).expect("invalid table_len");
|
||||
self.next_index = 0;
|
||||
self.ttl = self.queue_size;
|
||||
self.is_indirect = true;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Iterator for DescriptorChain<M>
|
||||
where
|
||||
M: Deref,
|
||||
M::Target: GuestMemory,
|
||||
{
|
||||
type Item = Descriptor;
|
||||
|
||||
/// Return the next descriptor in this descriptor chain, if there is one.
|
||||
///
|
||||
/// Note that this is distinct from the next descriptor chain returned by
|
||||
/// [`AvailIter`](struct.AvailIter.html), which is the head of the next
|
||||
/// _available_ descriptor chain.
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.ttl == 0 || self.next_index >= self.queue_size {
|
||||
return None;
|
||||
}
|
||||
|
||||
let desc_addr = self
|
||||
.desc_table
|
||||
// The multiplication can not overflow an u64 since we are multiplying an u16 with a
|
||||
// small number.
|
||||
.checked_add(self.next_index as u64 * size_of::<Descriptor>() as u64)?;
|
||||
|
||||
// The guest device driver should not touch the descriptor once submitted, so it's safe
|
||||
// to use read_obj() here.
|
||||
let mut desc = self.mem.read_obj::<Descriptor>(desc_addr).ok()?;
|
||||
// When needed, it's very important to translate the decriptor address
|
||||
// before returning the Descriptor to the consumer.
|
||||
if let Some(access_platform) = &self.access_platform {
|
||||
desc.set_addr(
|
||||
access_platform
|
||||
.translate(desc.addr().0, u64::from(desc.len()))
|
||||
.ok()?,
|
||||
);
|
||||
}
|
||||
|
||||
if desc.refers_to_indirect_table() {
|
||||
self.switch_to_indirect_table(desc).ok()?;
|
||||
return self.next();
|
||||
}
|
||||
|
||||
if desc.has_next() {
|
||||
self.next_index = desc.next();
|
||||
// It's ok to decrement `self.ttl` here because we check at the start of the method
|
||||
// that it's greater than 0.
|
||||
self.ttl -= 1;
|
||||
} else {
|
||||
self.ttl = 0;
|
||||
}
|
||||
|
||||
Some(desc)
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator for readable or writable descriptors.
|
||||
#[derive(Clone)]
|
||||
pub struct DescriptorChainRwIter<M> {
|
||||
chain: DescriptorChain<M>,
|
||||
writable: bool,
|
||||
}
|
||||
|
||||
impl<M> Iterator for DescriptorChainRwIter<M>
|
||||
where
|
||||
M: Deref,
|
||||
M::Target: GuestMemory,
|
||||
{
|
||||
type Item = Descriptor;
|
||||
|
||||
/// Return the next readable/writeable descriptor (depending on the `writable` value) in this
|
||||
/// descriptor chain, if there is one.
|
||||
///
|
||||
/// Note that this is distinct from the next descriptor chain returned by
|
||||
/// [`AvailIter`](struct.AvailIter.html), which is the head of the next
|
||||
/// _available_ descriptor chain.
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
match self.chain.next() {
|
||||
Some(v) => {
|
||||
if v.is_write_only() == self.writable {
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
None => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can't derive Debug, because rustc doesn't generate the `M::T: Debug` constraint
|
||||
impl<M> Debug for DescriptorChainRwIter<M>
|
||||
where
|
||||
M: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("DescriptorChainRwIter")
|
||||
.field("chain", &self.chain)
|
||||
.field("writable", &self.writable)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::defs::{VIRTQ_DESC_F_INDIRECT, VIRTQ_DESC_F_NEXT};
|
||||
use crate::mock::{DescriptorTable, MockSplitQueue};
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
|
||||
#[test]
|
||||
fn test_checked_new_descriptor_chain() {
|
||||
let m = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let vq = MockSplitQueue::new(m, 16);
|
||||
|
||||
assert!(vq.end().0 < 0x1000);
|
||||
|
||||
// index >= queue_size
|
||||
assert!(
|
||||
DescriptorChain::<&GuestMemoryMmap>::new(m, vq.start(), 16, 16, None)
|
||||
.next()
|
||||
.is_none()
|
||||
);
|
||||
|
||||
// desc_table address is way off
|
||||
assert!(DescriptorChain::<&GuestMemoryMmap>::new(
|
||||
m,
|
||||
GuestAddress(0x00ff_ffff_ffff),
|
||||
16,
|
||||
0,
|
||||
None
|
||||
)
|
||||
.next()
|
||||
.is_none());
|
||||
|
||||
{
|
||||
// the first desc has a normal len, and the next_descriptor flag is set
|
||||
// but the the index of the next descriptor is too large
|
||||
let desc = Descriptor::new(0x1000, 0x1000, VIRTQ_DESC_F_NEXT, 16);
|
||||
vq.desc_table().store(0, desc);
|
||||
|
||||
let mut c = DescriptorChain::<&GuestMemoryMmap>::new(m, vq.start(), 16, 0, None);
|
||||
c.next().unwrap();
|
||||
assert!(c.next().is_none());
|
||||
}
|
||||
|
||||
// finally, let's test an ok chain
|
||||
{
|
||||
let desc = Descriptor::new(0x1000, 0x1000, VIRTQ_DESC_F_NEXT, 1);
|
||||
vq.desc_table().store(0, desc);
|
||||
|
||||
let desc = Descriptor::new(0x2000, 0x1000, 0, 0);
|
||||
vq.desc_table().store(1, desc);
|
||||
|
||||
let mut c = DescriptorChain::<&GuestMemoryMmap>::new(m, vq.start(), 16, 0, None);
|
||||
|
||||
assert_eq!(
|
||||
c.memory() as *const GuestMemoryMmap,
|
||||
m as *const GuestMemoryMmap
|
||||
);
|
||||
|
||||
assert_eq!(c.desc_table, vq.start());
|
||||
assert_eq!(c.queue_size, 16);
|
||||
assert_eq!(c.ttl, c.queue_size);
|
||||
|
||||
let desc = c.next().unwrap();
|
||||
assert_eq!(desc.addr(), GuestAddress(0x1000));
|
||||
assert_eq!(desc.len(), 0x1000);
|
||||
assert_eq!(desc.flags(), VIRTQ_DESC_F_NEXT);
|
||||
assert_eq!(desc.next(), 1);
|
||||
assert_eq!(c.ttl, c.queue_size - 1);
|
||||
|
||||
assert!(c.next().is_some());
|
||||
// The descriptor above was the last from the chain, so `ttl` should be 0 now.
|
||||
assert_eq!(c.ttl, 0);
|
||||
assert!(c.next().is_none());
|
||||
assert_eq!(c.ttl, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ttl_wrap_around() {
|
||||
const QUEUE_SIZE: u16 = 16;
|
||||
|
||||
let m = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x100000)]).unwrap();
|
||||
let vq = MockSplitQueue::new(m, QUEUE_SIZE);
|
||||
|
||||
// Populate the entire descriptor table with entries. Only the last one should not have the
|
||||
// VIRTQ_DESC_F_NEXT set.
|
||||
for i in 0..QUEUE_SIZE - 1 {
|
||||
let desc = Descriptor::new(0x1000 * (i + 1) as u64, 0x1000, VIRTQ_DESC_F_NEXT, i + 1);
|
||||
vq.desc_table().store(i, desc);
|
||||
}
|
||||
let desc = Descriptor::new((0x1000 * 16) as u64, 0x1000, 0, 0);
|
||||
vq.desc_table().store(QUEUE_SIZE - 1, desc);
|
||||
|
||||
let mut c = DescriptorChain::<&GuestMemoryMmap>::new(m, vq.start(), QUEUE_SIZE, 0, None);
|
||||
assert_eq!(c.ttl, c.queue_size);
|
||||
|
||||
// Validate that `ttl` wraps around even when the entire descriptor table is populated.
|
||||
for i in 0..QUEUE_SIZE {
|
||||
let _desc = c.next().unwrap();
|
||||
assert_eq!(c.ttl, c.queue_size - i - 1);
|
||||
}
|
||||
assert!(c.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_from_indirect_descriptor() {
|
||||
// This is testing that chaining an indirect table works as expected. It is also a negative
|
||||
// test for the following requirement from the spec:
|
||||
// `A driver MUST NOT set both VIRTQ_DESC_F_INDIRECT and VIRTQ_DESC_F_NEXT in flags.`. In
|
||||
// case the driver is setting both of these flags, we check that the device doesn't panic.
|
||||
let m = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let vq = MockSplitQueue::new(m, 16);
|
||||
let dtable = vq.desc_table();
|
||||
|
||||
// Create a chain with one normal descriptor and one pointing to an indirect table.
|
||||
let desc = Descriptor::new(0x6000, 0x1000, VIRTQ_DESC_F_NEXT, 1);
|
||||
dtable.store(0, desc);
|
||||
// The spec forbids setting both VIRTQ_DESC_F_INDIRECT and VIRTQ_DESC_F_NEXT in flags. We do
|
||||
// not currently enforce this rule, we just ignore the VIRTQ_DESC_F_NEXT flag.
|
||||
let desc = Descriptor::new(0x7000, 0x1000, VIRTQ_DESC_F_INDIRECT | VIRTQ_DESC_F_NEXT, 2);
|
||||
dtable.store(1, desc);
|
||||
let desc = Descriptor::new(0x8000, 0x1000, 0, 0);
|
||||
dtable.store(2, desc);
|
||||
|
||||
let mut c: DescriptorChain<&GuestMemoryMmap> =
|
||||
DescriptorChain::new(m, vq.start(), 16, 0, None);
|
||||
|
||||
// create an indirect table with 4 chained descriptors
|
||||
let idtable = DescriptorTable::new(m, GuestAddress(0x7000), 4);
|
||||
for i in 0..4u16 {
|
||||
let desc = if i < 3 {
|
||||
Descriptor::new(0x1000 * i as u64, 0x1000, VIRTQ_DESC_F_NEXT, i + 1)
|
||||
} else {
|
||||
Descriptor::new(0x1000 * i as u64, 0x1000, 0, 0)
|
||||
};
|
||||
idtable.store(i, desc);
|
||||
}
|
||||
|
||||
assert_eq!(c.head_index(), 0);
|
||||
// Consume the first descriptor.
|
||||
c.next().unwrap();
|
||||
|
||||
// The chain logic hasn't parsed the indirect descriptor yet.
|
||||
assert!(!c.is_indirect);
|
||||
|
||||
// Try to iterate through the indirect descriptor chain.
|
||||
for i in 0..4 {
|
||||
let desc = c.next().unwrap();
|
||||
assert!(c.is_indirect);
|
||||
if i < 3 {
|
||||
assert_eq!(desc.flags(), VIRTQ_DESC_F_NEXT);
|
||||
assert_eq!(desc.next(), i + 1);
|
||||
}
|
||||
}
|
||||
// Even though we added a new descriptor after the one that is pointing to the indirect
|
||||
// table, this descriptor won't be available when parsing the chain.
|
||||
assert!(c.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_indirect_descriptor_err() {
|
||||
// We are testing here different misconfigurations of the indirect table. For these error
|
||||
// case scenarios, the iterator over the descriptor chain won't return a new descriptor.
|
||||
{
|
||||
let m = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let vq = MockSplitQueue::new(m, 16);
|
||||
|
||||
// Create a chain with a descriptor pointing to an invalid indirect table: addr not a
|
||||
// multiple of descriptor size.
|
||||
let desc = Descriptor::new(0x1001, 0x1000, VIRTQ_DESC_F_INDIRECT, 0);
|
||||
vq.desc_table().store(0, desc);
|
||||
|
||||
let mut c: DescriptorChain<&GuestMemoryMmap> =
|
||||
DescriptorChain::new(m, vq.start(), 16, 0, None);
|
||||
|
||||
assert!(c.next().is_none());
|
||||
}
|
||||
|
||||
{
|
||||
let m = &GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let vq = MockSplitQueue::new(m, 16);
|
||||
|
||||
// Create a chain with a descriptor pointing to an invalid indirect table: len not a
|
||||
// multiple of descriptor size.
|
||||
let desc = Descriptor::new(0x1000, 0x1001, VIRTQ_DESC_F_INDIRECT, 0);
|
||||
vq.desc_table().store(0, desc);
|
||||
|
||||
let mut c: DescriptorChain<&GuestMemoryMmap> =
|
||||
DescriptorChain::new(m, vq.start(), 16, 0, None);
|
||||
|
||||
assert!(c.next().is_none());
|
||||
}
|
||||
|
||||
{
|
||||
let m = &GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let vq = MockSplitQueue::new(m, 16);
|
||||
|
||||
// Create a chain with a descriptor pointing to an invalid indirect table: table len >
|
||||
// u16::MAX.
|
||||
let desc = Descriptor::new(
|
||||
0x1000,
|
||||
(u16::MAX as u32 + 1) * VIRTQ_DESCRIPTOR_SIZE as u32,
|
||||
VIRTQ_DESC_F_INDIRECT,
|
||||
0,
|
||||
);
|
||||
vq.desc_table().store(0, desc);
|
||||
|
||||
let mut c: DescriptorChain<&GuestMemoryMmap> =
|
||||
DescriptorChain::new(m, vq.start(), 16, 0, None);
|
||||
|
||||
assert!(c.next().is_none());
|
||||
}
|
||||
|
||||
{
|
||||
let m = &GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let vq = MockSplitQueue::new(m, 16);
|
||||
|
||||
// Create a chain with a descriptor pointing to an indirect table.
|
||||
let desc = Descriptor::new(0x1000, 0x1000, VIRTQ_DESC_F_INDIRECT, 0);
|
||||
vq.desc_table().store(0, desc);
|
||||
// It's ok for an indirect descriptor to have flags = 0.
|
||||
let desc = Descriptor::new(0x3000, 0x1000, 0, 0);
|
||||
m.write_obj(desc, GuestAddress(0x1000)).unwrap();
|
||||
|
||||
let mut c: DescriptorChain<&GuestMemoryMmap> =
|
||||
DescriptorChain::new(m, vq.start(), 16, 0, None);
|
||||
assert!(c.next().is_some());
|
||||
|
||||
// But it's not allowed to have an indirect descriptor that points to another indirect
|
||||
// table.
|
||||
let desc = Descriptor::new(0x3000, 0x1000, VIRTQ_DESC_F_INDIRECT, 0);
|
||||
m.write_obj(desc, GuestAddress(0x1000)).unwrap();
|
||||
|
||||
let mut c: DescriptorChain<&GuestMemoryMmap> =
|
||||
DescriptorChain::new(m, vq.start(), 16, 0, None);
|
||||
|
||||
assert!(c.next().is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
|
||||
//! Virtio queue related constant definitions
|
||||
|
||||
/// Marks a buffer as continuing via the next field.
|
||||
pub const VIRTQ_DESC_F_NEXT: u16 = 0x1;
|
||||
|
||||
/// Marks a buffer as device write-only.
|
||||
pub const VIRTQ_DESC_F_WRITE: u16 = 0x2;
|
||||
|
||||
/// Marks a buffer as containing a list of buffer descriptors.
|
||||
pub const VIRTQ_DESC_F_INDIRECT: u16 = 0x4;
|
||||
|
||||
/// Flag to disable guest notification for used descriptors.
|
||||
pub const VIRTQ_USED_F_NO_NOTIFY: u16 = 0x1;
|
||||
|
||||
/// Size of one element in the used ring, id (le32) + len (le32).
|
||||
pub(crate) const VIRTQ_USED_ELEMENT_SIZE: u64 = 8;
|
||||
|
||||
/// Size of used ring header: flags (u16) + idx (u16)
|
||||
pub(crate) const VIRTQ_USED_RING_HEADER_SIZE: u64 = 4;
|
||||
|
||||
/// Size of the used ring metadata: header + avail_event (le16).
|
||||
///
|
||||
/// The total size of the used ring is:
|
||||
/// VIRTQ_USED_RING_META_SIZE + VIRTQ_USED_ELEMENT_SIZE * queue_size.
|
||||
pub(crate) const VIRTQ_USED_RING_META_SIZE: u64 = VIRTQ_USED_RING_HEADER_SIZE + 2;
|
||||
|
||||
/// Size of one element in the available ring (le16).
|
||||
pub(crate) const VIRTQ_AVAIL_ELEMENT_SIZE: u64 = 2;
|
||||
|
||||
/// Size of available ring header: flags(u16) + idx(u16)
|
||||
pub(crate) const VIRTQ_AVAIL_RING_HEADER_SIZE: u64 = 4;
|
||||
|
||||
/// Size of the available ring metadata: header + used_event (le16).
|
||||
///
|
||||
/// The total size of the available ring is:
|
||||
/// VIRTQ_AVAIL_RING_META_SIZE + VIRTQ_AVAIL_ELEMENT_SIZE * queue_size.
|
||||
pub(crate) const VIRTQ_AVAIL_RING_META_SIZE: u64 = VIRTQ_AVAIL_RING_HEADER_SIZE + 2;
|
||||
|
||||
/// Size of virtio descriptor.
|
||||
///
|
||||
/// The Virtio Spec 1.0 defines the alignment of VirtIO descriptor is 16 bytes,
|
||||
/// which fulfills the explicit constraint of GuestMemory::read_obj().
|
||||
pub(crate) const VIRTQ_DESCRIPTOR_SIZE: usize = 16;
|
||||
|
||||
/// Default guest physical address for descriptor table.
|
||||
pub(crate) const DEFAULT_DESC_TABLE_ADDR: u64 = 0x0;
|
||||
|
||||
/// Default guest physical address for available ring.
|
||||
pub(crate) const DEFAULT_AVAIL_RING_ADDR: u64 = 0x0;
|
||||
|
||||
/// Default guest physical address for used ring.
|
||||
pub(crate) const DEFAULT_USED_RING_ADDR: u64 = 0x0;
|
||||
|
||||
/// Vector value used to disable MSI for a queue.
|
||||
pub const VIRTQ_MSI_NO_VECTOR: u16 = 0xffff;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user