mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
166 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3eb9e61a32 | ||
|
|
d42b50845b | ||
|
|
06f391e022 | ||
|
|
60d2469e7a | ||
|
|
c4f8e4b000 | ||
|
|
c32959261e | ||
|
|
ef3fad8388 | ||
|
|
2b42f6c835 | ||
|
|
7acb5c6437 | ||
|
|
63f356b75b | ||
|
|
fd0ef6cfb5 | ||
|
|
286a23fbd4 | ||
|
|
3a93487b82 | ||
|
|
23c46b162e | ||
|
|
f4af668d76 | ||
|
|
7fe5d276a3 | ||
|
|
e664fa386b | ||
|
|
9919dec1d9 | ||
|
|
9ce6c3b75c | ||
|
|
47f59409db | ||
|
|
b51f112629 | ||
|
|
d158fa8de2 | ||
|
|
c64de38140 | ||
|
|
9594ecec0d | ||
|
|
6c0c47a530 | ||
|
|
6d38612f6c | ||
|
|
a44d96c9cc | ||
|
|
93b7dcac12 | ||
|
|
dd3844a892 | ||
|
|
0c3ef986fa | ||
|
|
53c3b00fb0 | ||
|
|
03b824be0b | ||
|
|
558975aa51 | ||
|
|
d78b9bd287 | ||
|
|
365d39ca2c | ||
|
|
d79594aa3f | ||
|
|
27b5f8d7e3 | ||
|
|
2cc312b65c | ||
|
|
d416271457 | ||
|
|
9e6a023f7f | ||
|
|
90ae4676c8 | ||
|
|
89bc413add | ||
|
|
c352b59857 | ||
|
|
ab89b48143 | ||
|
|
ffaab46934 | ||
|
|
280d4fb245 | ||
|
|
15f0451c34 | ||
|
|
3fa6e17cc4 | ||
|
|
e2c81f9ed8 | ||
|
|
ead8453120 | ||
|
|
c4ebe83576 | ||
|
|
1908f488c3 | ||
|
|
593a958fe5 | ||
|
|
ba71390d6a | ||
|
|
f6892f2ede | ||
|
|
251434862f | ||
|
|
1ac4c42a84 | ||
|
|
7dc92d1dac | ||
|
|
7a4edecd29 | ||
|
|
d419e30df1 | ||
|
|
3faffcb087 | ||
|
|
b3a1f5f1be | ||
|
|
2bc5119730 | ||
|
|
5100bc94cc | ||
|
|
6bf9fe487b | ||
|
|
d1a23d9085 | ||
|
|
05f785fa88 | ||
|
|
a3d957ba4d | ||
|
|
5bd63efa45 | ||
|
|
66b001665f | ||
|
|
c6aea5af5d | ||
|
|
c8b655490e | ||
|
|
72741c557a | ||
|
|
b2608ca285 | ||
|
|
74fe032827 | ||
|
|
40a2e3b462 | ||
|
|
abc6672f21 | ||
|
|
053c9e4d73 | ||
|
|
9ea19852b5 | ||
|
|
9871ff454b | ||
|
|
e817b4f36c | ||
|
|
6f63cd00eb | ||
|
|
a6ad85dc40 | ||
|
|
0f4f30dbde | ||
|
|
fe5401223b | ||
|
|
12b7a498d9 | ||
|
|
1fc97e91a4 | ||
|
|
fc5d6c96be | ||
|
|
546778ebfb | ||
|
|
f0360aff83 | ||
|
|
ae96aeda65 | ||
|
|
b5b97f7b05 | ||
|
|
9fc2613b41 | ||
|
|
44b5a19209 | ||
|
|
df6b52924f | ||
|
|
1ab1341775 | ||
|
|
a29f7ffcc7 | ||
|
|
eda39d0469 | ||
|
|
de0c24810e | ||
|
|
fadeb98c67 | ||
|
|
1fc0b94fcd | ||
|
|
ef4c896bbf | ||
|
|
72bb255ff6 | ||
|
|
a406d90059 | ||
|
|
0dfffee6ba | ||
|
|
0fec326582 | ||
|
|
6a591ca81d | ||
|
|
a525f8365f | ||
|
|
f271378811 | ||
|
|
78b9edf848 | ||
|
|
d0aebfb922 | ||
|
|
f913dd6150 | ||
|
|
c622278030 | ||
|
|
d1d0421103 | ||
|
|
fb70baece0 | ||
|
|
0159ab93e1 | ||
|
|
609d704168 | ||
|
|
3ac9b6c404 | ||
|
|
b34703d29f | ||
|
|
cf6763dfdb | ||
|
|
11a69450ba | ||
|
|
c62e409827 | ||
|
|
041724a7cf | ||
|
|
8baa244ec1 | ||
|
|
b399287430 | ||
|
|
c0827e01b1 | ||
|
|
46e736973e | ||
|
|
096d99664d | ||
|
|
f7c42dc7d2 | ||
|
|
37f947d614 | ||
|
|
47d2421e39 | ||
|
|
04419065a3 | ||
|
|
ca60adda70 | ||
|
|
dfe2dadb3e | ||
|
|
523029449e | ||
|
|
7ac764518c | ||
|
|
aa98589bb4 | ||
|
|
ca5f1d6de8 | ||
|
|
270631922d | ||
|
|
465ef3f7ad | ||
|
|
50e0423304 | ||
|
|
906a1eb18d | ||
|
|
7b77f1ef90 | ||
|
|
0005d11e32 | ||
|
|
ec84abc5c7 | ||
|
|
2ea0d8eb91 | ||
|
|
6a25aaf6b9 | ||
|
|
1b6b45bf2f | ||
|
|
ecda97f759 | ||
|
|
5566992422 | ||
|
|
cc637f12f6 | ||
|
|
592babaadd | ||
|
|
138a8156c8 | ||
|
|
7a008e66a4 | ||
|
|
164d481683 | ||
|
|
52f60188ca | ||
|
|
8cb29af712 | ||
|
|
60768a84c5 | ||
|
|
3266eb7cce | ||
|
|
c9a28312a9 | ||
|
|
a278704ae0 | ||
|
|
093a581ee1 | ||
|
|
69394c9c35 | ||
|
|
0f5e5d9e6d | ||
|
|
4606f0f28a | ||
|
|
cb88ceeae8 |
1
.github/workflows/build.yaml
vendored
1
.github/workflows/build.yaml
vendored
@@ -7,6 +7,7 @@ jobs:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
rust:
|
||||
- stable
|
||||
|
||||
31
.github/workflows/quality.yaml
vendored
31
.github/workflows/quality.yaml
vendored
@@ -18,19 +18,28 @@ jobs:
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Formatting (rustfmt)
|
||||
run: cargo fmt -- --check
|
||||
|
||||
- name: Clippy (all features)
|
||||
run: cargo clippy --all-targets --all-features -- -D warnings
|
||||
|
||||
- name: Clippy (all features,kvm)
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "common,kvm" -- -D warnings
|
||||
|
||||
- name: Clippy (all features,mshv)
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "common,mshv" -- -D warnings
|
||||
|
||||
- name: Clippy (acpi,kvm)
|
||||
run: cargo clippy --all-targets --no-default-features --features "acpi,kvm" -- -D warnings
|
||||
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "acpi,kvm" -- -D warnings
|
||||
|
||||
- name: Clippy (kvm)
|
||||
run: cargo clippy --all-targets --no-default-features --features "kvm" -- -D warnings
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "kvm" -- -D warnings
|
||||
|
||||
- name: Clippy (acpi,mshv)
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "acpi,mshv" -- -D warnings
|
||||
|
||||
- name: Clippy (mshv)
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "mshv" -- -D warnings
|
||||
|
||||
512
Cargo.lock
generated
512
Cargo.lock
generated
@@ -9,9 +9,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.13.0"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072"
|
||||
checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
@@ -24,9 +24,9 @@ checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.14"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d"
|
||||
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -42,9 +42,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.33"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c"
|
||||
checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4"
|
||||
|
||||
[[package]]
|
||||
name = "api_client"
|
||||
@@ -52,9 +52,9 @@ version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "0.4.7"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034"
|
||||
checksum = "0688b520bcc7498f6ca8fa006e8031d353e3fd4f51bd4a50fb03cc4230b28bd2"
|
||||
|
||||
[[package]]
|
||||
name = "arch"
|
||||
@@ -111,9 +111,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.53"
|
||||
version = "0.3.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e"
|
||||
checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -125,9 +125,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.12.3"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
@@ -143,9 +143,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "blake2b_simd"
|
||||
version = "0.5.10"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"
|
||||
checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
@@ -176,9 +176,9 @@ checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.61"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
|
||||
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@@ -210,7 +210,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cloud-hypervisor"
|
||||
version = "0.11.0"
|
||||
version = "0.12.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"api_client",
|
||||
@@ -230,8 +230,7 @@ dependencies = [
|
||||
"tempdir",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"vhost_user_block",
|
||||
"vhost_user_net",
|
||||
"vm-memory",
|
||||
"vmm",
|
||||
"vmm-sys-util",
|
||||
"wait-timeout",
|
||||
@@ -264,12 +263,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.7.2"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
|
||||
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if 0.1.10",
|
||||
"cfg-if 1.0.0",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
@@ -314,6 +313,19 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"humantime",
|
||||
"log 0.4.11",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epoll"
|
||||
version = "4.3.1"
|
||||
@@ -346,108 +358,22 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95314d38584ffbfda215621d723e0a3906f032e03ae5551e650058dac83d4797"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0448174b01148032eed37ac4aed28963aaaa8cfa93569a08e5b479bbc6c2c151"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18eaa56102984bed2c88ea39026cff3ce3b4c7f508ca970cedf2450ea10d4e46"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5f8e0c9258abaea85e78ebdda17ef9666d390e987f006be6080dfe354b708cb"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1798854a4727ff944a7b12aa999f58ce7aa81db80d2dfaaf2ba06f065ddd2b"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e36fccf3fc58563b4a14d265027c627c3b665d7fed489427e88e7cc929559efe"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e3ca3f17d6e8804ae5d3df7a7d35b2b3a6fe89dac84b31872720fc3060a0b11"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d502af37186c4fef99453df03e374683f8a1eec9dcc1e66b3b82dc8278ce3c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abcb44342f62e6f3e8ac427b8aa815f724fd705dfad060b18ac7866c15bb8e34"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project",
|
||||
"pin-utils",
|
||||
"proc-macro-hack",
|
||||
"proc-macro-nested",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.15"
|
||||
@@ -461,9 +387,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.22.0"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
|
||||
checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
@@ -480,17 +406,27 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a"
|
||||
|
||||
[[package]]
|
||||
name = "hypervisor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"env_logger",
|
||||
"epoll",
|
||||
"iced-x86",
|
||||
"kvm-bindings",
|
||||
"kvm-ioctls",
|
||||
"libc",
|
||||
"linux-loader",
|
||||
"log 0.4.11",
|
||||
"mshv-bindings",
|
||||
"mshv-ioctls",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
@@ -499,6 +435,17 @@ dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced-x86"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "248ce8ff0784d2b15f3c3d8b01f529be0e18aa693a2ba7415df76857967c8fc3"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"rustc_version",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.0"
|
||||
@@ -522,9 +469,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ipnetwork"
|
||||
version = "0.16.0"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8eca9f51da27bc908ef3dd85c21e1bbba794edaf94d7841e37356275b82d31e"
|
||||
checksum = "02c3eaab3ac0ede60ffa41add21970a7df7d91772c03383aac6c2c3d53cc716b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -573,15 +520,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.80"
|
||||
version = "0.2.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
|
||||
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
|
||||
|
||||
[[package]]
|
||||
name = "libssh2-sys"
|
||||
version = "0.2.19"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca46220853ba1c512fc82826d0834d87b06bcd3c2a42241b7de72f3d2fe17056"
|
||||
checksum = "df40b13fe7ea1be9b9dffa365a51273816c345fc1811478b57ed7d964fbfc4ce"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -606,7 +553,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "linux-loader"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/rust-vmm/linux-loader#3cf96c53ed6bfed9c7ca7dcfdbbcd04c86b6dd09"
|
||||
source = "git+https://github.com/rust-vmm/linux-loader#acf9c21591813c87dc77f667618d433f62c821af"
|
||||
dependencies = [
|
||||
"vm-memory",
|
||||
]
|
||||
@@ -653,7 +600,7 @@ checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
||||
[[package]]
|
||||
name = "micro_http"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/firecracker-microvm/micro-http#40309b72ee084b0a26d60208f0872bd7c986de45"
|
||||
source = "git+https://github.com/firecracker-microvm/micro-http?branch=master#59ab64440a95f7b16253fef67b5201c2ec4adaf7"
|
||||
dependencies = [
|
||||
"epoll",
|
||||
]
|
||||
@@ -668,6 +615,28 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mshv-bindings"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/cloud-hypervisor/mshv?branch=master#2ded6daf97976aa27c1aca47964672c201a7d17d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"vmm-sys-util",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mshv-ioctls"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/cloud-hypervisor/mshv?branch=master#2ded6daf97976aa27c1aca47964672c201a7d17d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mshv-bindings",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "net_gen"
|
||||
version = "0.1.0"
|
||||
@@ -694,33 +663,17 @@ dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.21.1"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
|
||||
checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.58"
|
||||
version = "0.9.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de"
|
||||
checksum = "de52d8eabd217311538a39bba130d7dea1f1e118010fee7a033d966845e7d5fe"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cc",
|
||||
@@ -784,32 +737,6 @@ version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee41d838744f60d959d7074e3afb6b35c7456d0f61cad38a24e35e6553f73841"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81a4ffa594b66bff340084d4081df649a7dc049ac8d7fc458d8e628bfbbb2f86"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.19"
|
||||
@@ -818,9 +745,9 @@ checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
|
||||
|
||||
[[package]]
|
||||
name = "pnet"
|
||||
version = "0.26.0"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c62df42dcd72f6f2a658bcf38509f1027df1440ac85f1af4badbe034418302dc"
|
||||
checksum = "b657d5b9a98a2c81b82549922b8b15984e49f8120cd130b11a09f81b9b55d633"
|
||||
dependencies = [
|
||||
"ipnetwork",
|
||||
"pnet_base",
|
||||
@@ -832,28 +759,28 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pnet_base"
|
||||
version = "0.26.0"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7cd5f7e15220afa66b0a9a62841ea10089f39dcaa1c29752c0b22dfc03111b5"
|
||||
checksum = "4e4688aa497ef62129f302a5800ebde67825f8ff129f43690ca84099f6620bed"
|
||||
|
||||
[[package]]
|
||||
name = "pnet_datalink"
|
||||
version = "0.26.0"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7318ae1d6e0b7fa1e49933233c9473f2b72d3d18b97e70e2716c6415dde5f915"
|
||||
checksum = "59001c9c4d9d23bf2f61afaaf134a766fd6932ba2557c606b9112157053b9ac7"
|
||||
dependencies = [
|
||||
"ipnetwork",
|
||||
"libc",
|
||||
"pnet_base",
|
||||
"pnet_sys",
|
||||
"winapi 0.2.8",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_macros"
|
||||
version = "0.26.0"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbbd5c52c6e04aa720400f9c71cd0e8bcb38cd13421d5caabd9035e9efa47de9"
|
||||
checksum = "d894a90dbdbe976e624453fc31b1912f658083778329442dda1cca94f76a3e76"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"syntex",
|
||||
@@ -862,18 +789,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pnet_macros_support"
|
||||
version = "0.26.0"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf9c5c0c36766d0a4da9ab268c0700771b8ec367b9463fd678109fa28463c5b"
|
||||
checksum = "4b99269a458570bc06a9132254349f6543d9abc92e88b68d8de934aac9481f6c"
|
||||
dependencies = [
|
||||
"pnet_base",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_packet"
|
||||
version = "0.26.0"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89e26a864d71d0ac51a549cf40283c44ed1b8f98168545638a4730ef9f560283"
|
||||
checksum = "33f8238f4eb897a55ca06510cd71afb5b5ca7b4ff2d7188f1ca855fc1710133e"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"pnet_base",
|
||||
@@ -884,20 +811,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pnet_sys"
|
||||
version = "0.26.0"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73f0de0c52609f157b25d79ce24d9016ab1bbf10cde761397200d634a833872c"
|
||||
checksum = "7589e4c4e7ed72a3ffdff8a65d3bea84e8c3a23e19d0a10e8f45efdf632fff15"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi 0.2.8",
|
||||
"ws2_32-sys",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_transport"
|
||||
version = "0.26.0"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6712ab76534340494d849e3c51c64a6261e4b451337b7c05bd3681e384c48b10"
|
||||
checksum = "326abdfd2e70e8e943bd58087b59686de170cac050a3b19c9fcc84db01690af5"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pnet_base",
|
||||
@@ -907,21 +833,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.9"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-nested"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"
|
||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
@@ -1050,9 +964,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.4.1"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b"
|
||||
checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -1062,9 +976,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.20"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c"
|
||||
checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
|
||||
|
||||
[[package]]
|
||||
name = "remain"
|
||||
@@ -1088,9 +1002,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-argon2"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19"
|
||||
checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"blake2b_simd",
|
||||
@@ -1110,6 +1024,15 @@ version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
@@ -1131,16 +1054,31 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.117"
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
|
||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.117"
|
||||
version = "1.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
|
||||
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1149,9 +1087,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.59"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
|
||||
checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -1160,9 +1098,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.1.16"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed"
|
||||
checksum = "cbae917d5719a9884206132a443899e8c9056b48a156233a823417b39f34044f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
@@ -1170,31 +1108,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035"
|
||||
checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.4.2"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
|
||||
checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
|
||||
|
||||
[[package]]
|
||||
name = "ssh2"
|
||||
version = "0.8.2"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba56d741dab9a295bcb131ebfbe57f8fea2e1b7ae203e9184f5d7648213e4460"
|
||||
checksum = "ed024de0a5e6944fe3080a3745e7a5649c5517ea2a6cfb16333f94f89c248985"
|
||||
dependencies = [
|
||||
"bitflags 1.2.1",
|
||||
"libc",
|
||||
@@ -1202,6 +1133,12 @@ dependencies = [
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
@@ -1210,9 +1147,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.48"
|
||||
version = "1.0.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
|
||||
checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1324,6 +1261,15 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
@@ -1336,18 +1282,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.21"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "318234ffa22e0920fe9a40d7b8369b5f649d490980cf7aadcf1eb91594869b42"
|
||||
checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.21"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cae2447b6282786c3493999f40a9be2a6ad20cb8bd268b0a0dbf5a065535c0ab"
|
||||
checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1365,9 +1311,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "0.3.4"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117"
|
||||
checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
@@ -1380,9 +1335,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.13"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977"
|
||||
checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
@@ -1407,10 +1362,11 @@ checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
|
||||
checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
@@ -1493,27 +1449,6 @@ dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vhost_user_fs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 1.2.1",
|
||||
"clap",
|
||||
"epoll",
|
||||
"futures",
|
||||
"libc",
|
||||
"log 0.4.11",
|
||||
"seccomp",
|
||||
"tempdir",
|
||||
"vhost",
|
||||
"vhost_user_backend",
|
||||
"virtio-bindings",
|
||||
"virtio-devices",
|
||||
"vm-memory",
|
||||
"vm-virtio",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vhost_user_net"
|
||||
version = "0.1.0"
|
||||
@@ -1592,8 +1527,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vm-memory"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/cloud-hypervisor/vm-memory?branch=ch#6f2f7107f29a8f2b66b64a849aeace20783ec8f3"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45b5b0a6f371f8147143b1adb95edddafc9cb9e40adaf94edb6f93a1d04b0330"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"libc",
|
||||
@@ -1667,9 +1603,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vmm-sys-util"
|
||||
version = "0.6.1"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "183d25b56a61a6f518ef464ac578e790f04added34dfaab59a453d8a03cb7bd0"
|
||||
checksum = "d1cdd1d72e262bbfb014de65ada24c1ac50e10a2e3b1e8ec052df188c2ee5dfa"
|
||||
dependencies = [
|
||||
"bitflags 1.2.1",
|
||||
"libc",
|
||||
@@ -1720,6 +1656,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
@@ -1727,11 +1672,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "ws2_32-sys"
|
||||
version = "0.2.1"
|
||||
name = "zerocopy"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||
checksum = "6580539ad917b7c026220c4b3f2c08d52ce54d6ce0dc491e66002e35388fab46"
|
||||
dependencies = [
|
||||
"winapi 0.2.8",
|
||||
"winapi-build",
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
25
Cargo.toml
25
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cloud-hypervisor"
|
||||
version = "0.11.0"
|
||||
version = "0.12.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
default-run = "cloud-hypervisor"
|
||||
@@ -18,47 +18,43 @@ api_client = { path = "api_client" }
|
||||
clap = { version = "2.33.3", features = ["wrap_help"] }
|
||||
epoll = ">=4.0.1"
|
||||
hypervisor = { path = "hypervisor" }
|
||||
libc = "0.2.80"
|
||||
libc = "0.2.81"
|
||||
log = { version = "0.4.11", features = ["std"] }
|
||||
option_parser = { path = "option_parser" }
|
||||
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.22.0" }
|
||||
serde_json = "1.0.59"
|
||||
serde_json = "1.0.60"
|
||||
thiserror = "1.0"
|
||||
vhost_user_block = { path = "vhost_user_block"}
|
||||
vhost_user_net = { path = "vhost_user_net"}
|
||||
vmm = { path = "vmm" }
|
||||
vmm-sys-util = "0.6.1"
|
||||
vmm-sys-util = "0.7.0"
|
||||
wait-timeout = "0.2.0"
|
||||
vm-memory = "0.4.0"
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version = "2.33.3", features = ["wrap_help"] }
|
||||
|
||||
# List of patched crates
|
||||
[patch.crates-io]
|
||||
vm-memory = { git = "https://github.com/cloud-hypervisor/vm-memory", branch = "ch" }
|
||||
[patch.'https://github.com/rust-vmm/vhost']
|
||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "ch", package = "vhost", features = ["vhost-user-master", "vhost-user-slave"] }
|
||||
[patch.'https://github.com/rust-vmm/kvm-ioctls']
|
||||
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
|
||||
[patch.'https://github.com/rust-vmm/kvm-bindings']
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch", features = ["with-serde", "fam-wrappers"] }
|
||||
|
||||
[dev-dependencies]
|
||||
ssh2 = "0.8.2"
|
||||
ssh2 = "0.9.0"
|
||||
dirs = "3.0.1"
|
||||
credibility = "0.1.3"
|
||||
tempdir = "0.3.7"
|
||||
lazy_static= "1.4.0"
|
||||
tempfile = "3.1.0"
|
||||
serde_json = "1.0.59"
|
||||
serde_json = "1.0.60"
|
||||
net_util = { path = "net_util" }
|
||||
|
||||
[features]
|
||||
default = ["acpi", "cmos", "io_uring", "kvm"]
|
||||
# Common features for all hypervisors
|
||||
common = ["acpi", "cmos", "fwdebug", "io_uring"]
|
||||
acpi = ["vmm/acpi"]
|
||||
cmos = ["vmm/cmos"]
|
||||
fwdebug = ["vmm/fwdebug"]
|
||||
kvm = ["vmm/kvm"]
|
||||
mshv = ["vmm/mshv"]
|
||||
io_uring = ["vmm/io_uring"]
|
||||
|
||||
# Integration tests require a special environment to run in
|
||||
@@ -80,7 +76,6 @@ members = [
|
||||
"qcow",
|
||||
"vhost_user_backend",
|
||||
"vhost_user_block",
|
||||
"vhost_user_fs",
|
||||
"vhost_user_net",
|
||||
"virtio-devices",
|
||||
"vmm",
|
||||
|
||||
36
Jenkinsfile
vendored
36
Jenkinsfile
vendored
@@ -26,9 +26,6 @@ pipeline{
|
||||
parallel {
|
||||
stage ('Worker build') {
|
||||
agent { node { label 'groovy' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
@@ -46,6 +43,9 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Run integration tests') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration"
|
||||
}
|
||||
@@ -54,9 +54,6 @@ pipeline{
|
||||
}
|
||||
stage ('AArch64 worker build') {
|
||||
agent { node { label 'bionic-arm64' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
@@ -69,6 +66,9 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Run integration tests') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration"
|
||||
}
|
||||
@@ -83,9 +83,6 @@ pipeline{
|
||||
}
|
||||
stage ('Worker build (musl)') {
|
||||
agent { node { label 'groovy' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
@@ -98,6 +95,9 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Run integration tests for musl') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration --libc musl"
|
||||
}
|
||||
@@ -106,9 +106,6 @@ pipeline{
|
||||
}
|
||||
stage ('Worker build SGX') {
|
||||
agent { node { label 'bionic-sgx' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
when { branch 'master' }
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
@@ -117,11 +114,17 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Run SGX integration tests') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration-sgx"
|
||||
}
|
||||
}
|
||||
stage ('Run SGX integration tests for musl') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration-sgx --libc musl"
|
||||
}
|
||||
@@ -136,9 +139,6 @@ pipeline{
|
||||
}
|
||||
stage ('Worker build - Windows guest') {
|
||||
agent { node { label 'groovy-win' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
@@ -161,11 +161,17 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Run Windows guest integration tests') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration-windows"
|
||||
}
|
||||
}
|
||||
stage ('Run Windows guest integration tests for musl') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration-windows --libc musl"
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vm-memory = "0.3.0"
|
||||
vm-memory = "0.4.0"
|
||||
|
||||
|
||||
@@ -11,12 +11,12 @@ acpi = ["acpi_tables"]
|
||||
anyhow = "1.0"
|
||||
byteorder = "1.3.4"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.80"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-memory = { version = "0.3.0", features = ["backend-mmap"] }
|
||||
vm-memory = { version = "0.4.0", features = ["backend-mmap"] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
arch_gen = { path = "../arch_gen" }
|
||||
|
||||
@@ -174,6 +174,7 @@ pub mod kvm {
|
||||
}
|
||||
|
||||
/// Method to initialize the GIC device
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
fn new(vm: &Arc<dyn hypervisor::Vm>, vcpu_count: u64) -> Result<Box<dyn GICDevice>> {
|
||||
let vgic_fd = Self::init_device(vm)?;
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
//
|
||||
//
|
||||
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
/// Below this address will reside the GIC, above this address will reside the MMIO devices.
|
||||
pub const MAPPED_IO_START: u64 = 0x0900_0000;
|
||||
@@ -61,7 +61,7 @@ pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x3000_0000;
|
||||
|
||||
/// PCI MMCONFIG space (start: after the device space at 1 GiB, length: 256MiB)
|
||||
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x4000_0000);
|
||||
pub const PCI_MMCONFIG_SIZE: GuestUsize = 256 << 20;
|
||||
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
|
||||
|
||||
/// Start of RAM on 64 bit ARM.
|
||||
pub const RAM_64BIT_START: u64 = 0x8000_0000;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
/*
|
||||
|
||||
@@ -81,22 +81,22 @@ pub const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
|
||||
|
||||
// ** 32-bit reserved area (start: 3GiB, length: 1GiB) **
|
||||
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xc000_0000);
|
||||
pub const MEM_32BIT_RESERVED_SIZE: GuestUsize = 1024 << 20;
|
||||
pub const MEM_32BIT_RESERVED_SIZE: u64 = 1024 << 20;
|
||||
|
||||
// == Fixed constants within the "32-bit reserved" range ==
|
||||
|
||||
// Sub range: 32-bit PCI devices (start: 3GiB, length: 640Mib)
|
||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = MEM_32BIT_RESERVED_START;
|
||||
pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = 640 << 20;
|
||||
pub const MEM_32BIT_DEVICES_SIZE: u64 = 640 << 20;
|
||||
|
||||
// PCI MMCONFIG space (start: after the device space, length: 256MiB)
|
||||
pub const PCI_MMCONFIG_START: GuestAddress =
|
||||
GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE);
|
||||
pub const PCI_MMCONFIG_SIZE: GuestUsize = 256 << 20;
|
||||
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
|
||||
|
||||
// IOAPIC
|
||||
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
|
||||
pub const IOAPIC_SIZE: GuestUsize = 0x20;
|
||||
pub const IOAPIC_SIZE: u64 = 0x20;
|
||||
|
||||
// APIC
|
||||
pub const APIC_START: GuestAddress = GuestAddress(0xfee0_0000);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
use std::sync::Arc;
|
||||
mod gdt;
|
||||
pub mod interrupts;
|
||||
pub mod layout;
|
||||
mod mptable;
|
||||
@@ -992,7 +991,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_system_configuration() {
|
||||
let no_vcpus = 4;
|
||||
let gm = GuestMemoryMmap::from_ranges(&vec![(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let config_err = configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
use std::sync::Arc;
|
||||
use std::{mem, result};
|
||||
|
||||
use super::gdt::{gdt_entry, segment_from_gdt};
|
||||
use super::BootProtocol;
|
||||
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
||||
use hypervisor::arch::x86::regs::*;
|
||||
use hypervisor::x86_64::{FpuState, SpecialRegisters, StandardRegisters};
|
||||
use layout::{
|
||||
BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START, PML5_START, PVH_INFO_START,
|
||||
@@ -134,14 +135,6 @@ pub fn setup_sregs(
|
||||
|
||||
const BOOT_GDT_MAX: usize = 4;
|
||||
|
||||
const EFER_LMA: u64 = 0x400;
|
||||
const EFER_LME: u64 = 0x100;
|
||||
|
||||
const X86_CR0_PE: u64 = 0x1;
|
||||
const X86_CR0_PG: u64 = 0x80000000;
|
||||
const X86_CR4_PAE: u64 = 0x20;
|
||||
const X86_CR4_LA57: u64 = 0x1000;
|
||||
|
||||
fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
let boot_gdt_addr = BOOT_GDT_START;
|
||||
for (index, entry) in table.iter().enumerate() {
|
||||
@@ -209,12 +202,12 @@ pub fn configure_segments_and_sregs(
|
||||
|
||||
match boot_prot {
|
||||
BootProtocol::PvhBoot => {
|
||||
sregs.cr0 = X86_CR0_PE;
|
||||
sregs.cr0 = CR0_PE;
|
||||
sregs.cr4 = 0;
|
||||
}
|
||||
BootProtocol::LinuxBoot => {
|
||||
/* 64-bit protected mode */
|
||||
sregs.cr0 |= X86_CR0_PE;
|
||||
sregs.cr0 |= CR0_PE;
|
||||
sregs.efer |= EFER_LME | EFER_LMA;
|
||||
}
|
||||
}
|
||||
@@ -230,7 +223,7 @@ pub fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut SpecialRegisters) ->
|
||||
.map_err(Error::WritePML5Address)?;
|
||||
|
||||
sregs.cr3 = PML5_START.raw_value();
|
||||
sregs.cr4 |= X86_CR4_LA57;
|
||||
sregs.cr4 |= CR4_LA57;
|
||||
} else {
|
||||
sregs.cr3 = PML4_START.raw_value();
|
||||
}
|
||||
@@ -250,8 +243,8 @@ pub fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut SpecialRegisters) ->
|
||||
.map_err(Error::WritePDEAddress)?;
|
||||
}
|
||||
|
||||
sregs.cr4 |= X86_CR4_PAE;
|
||||
sregs.cr0 |= X86_CR0_PG;
|
||||
sregs.cr4 |= CR4_PAE;
|
||||
sregs.cr0 |= CR0_PG;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -264,7 +257,7 @@ mod tests {
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
|
||||
fn create_guest_mem() -> GuestMemoryMmap {
|
||||
GuestMemoryMmap::from_ranges(&vec![(GuestAddress(0), 0x10000)]).unwrap()
|
||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
|
||||
}
|
||||
|
||||
fn read_u64(gm: &GuestMemoryMmap, offset: GuestAddress) -> u64 {
|
||||
@@ -301,7 +294,7 @@ mod tests {
|
||||
assert_eq!(0, sregs.tr.base);
|
||||
assert_eq!(0xffffffff, sregs.tr.limit);
|
||||
assert_eq!(0, sregs.tr.avl);
|
||||
assert_eq!(X86_CR0_PE, sregs.cr0);
|
||||
assert_eq!(CR0_PE, sregs.cr0);
|
||||
assert_eq!(EFER_LME | EFER_LMA, sregs.efer);
|
||||
|
||||
configure_segments_and_sregs(&gm, &mut sregs, BootProtocol::PvhBoot).unwrap();
|
||||
@@ -331,7 +324,7 @@ mod tests {
|
||||
assert_eq!(0x67, sregs.tr.limit);
|
||||
assert_eq!(0xb, sregs.tr.type_);
|
||||
assert_eq!(0, sregs.tr.avl);
|
||||
assert_eq!(X86_CR0_PE, sregs.cr0);
|
||||
assert_eq!(CR0_PE, sregs.cr0);
|
||||
assert_eq!(0, sregs.cr4);
|
||||
}
|
||||
|
||||
@@ -358,7 +351,7 @@ mod tests {
|
||||
} else {
|
||||
assert_eq!(PML4_START.raw_value(), sregs.cr3);
|
||||
}
|
||||
assert_eq!(X86_CR4_PAE, sregs.cr4);
|
||||
assert_eq!(X86_CR0_PG, sregs.cr0);
|
||||
assert_eq!(CR4_PAE, sregs.cr4);
|
||||
assert_eq!(CR0_PG, sregs.cr0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
concat!("Alignment of ", stringify!(mpf_intel))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).signature as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).signature as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -77,7 +77,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).physptr as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).physptr as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -87,7 +87,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).length as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).length as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -97,7 +97,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).specification as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).specification as *const _ as usize },
|
||||
9usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -107,7 +107,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).checksum as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).checksum as *const _ as usize },
|
||||
10usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -117,7 +117,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).feature1 as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).feature1 as *const _ as usize },
|
||||
11usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -127,7 +127,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).feature2 as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).feature2 as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -137,7 +137,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).feature3 as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).feature3 as *const _ as usize },
|
||||
13usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -147,7 +147,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).feature4 as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).feature4 as *const _ as usize },
|
||||
14usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -157,7 +157,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).feature5 as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).feature5 as *const _ as usize },
|
||||
15usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -200,7 +200,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
concat!("Alignment of ", stringify!(mpc_table))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).signature as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).signature as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -210,7 +210,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).length as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).length as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -220,7 +220,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).spec as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).spec as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -230,7 +230,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).checksum as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).checksum as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -240,7 +240,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).oem as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).oem as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -250,7 +250,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).productid as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).productid as *const _ as usize },
|
||||
16usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -260,7 +260,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).oemptr as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).oemptr as *const _ as usize },
|
||||
28usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -270,7 +270,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).oemsize as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).oemsize as *const _ as usize },
|
||||
32usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -280,7 +280,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).oemcount as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).oemcount as *const _ as usize },
|
||||
34usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -290,7 +290,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).lapic as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).lapic as *const _ as usize },
|
||||
36usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -300,7 +300,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).reserved as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).reserved as *const _ as usize },
|
||||
40usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -339,7 +339,7 @@ fn bindgen_test_layout_mpc_cpu() {
|
||||
concat!("Alignment of ", stringify!(mpc_cpu))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_cpu)).type_ as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -349,7 +349,7 @@ fn bindgen_test_layout_mpc_cpu() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_cpu)).apicid as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).apicid as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -359,7 +359,7 @@ fn bindgen_test_layout_mpc_cpu() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_cpu)).apicver as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).apicver as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -369,7 +369,7 @@ fn bindgen_test_layout_mpc_cpu() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_cpu)).cpuflag as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).cpuflag as *const _ as usize },
|
||||
3usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -379,7 +379,7 @@ fn bindgen_test_layout_mpc_cpu() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_cpu)).cpufeature as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).cpufeature as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -389,7 +389,7 @@ fn bindgen_test_layout_mpc_cpu() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_cpu)).featureflag as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).featureflag as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -399,7 +399,7 @@ fn bindgen_test_layout_mpc_cpu() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_cpu)).reserved as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).reserved as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -434,7 +434,7 @@ fn bindgen_test_layout_mpc_bus() {
|
||||
concat!("Alignment of ", stringify!(mpc_bus))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_bus)).type_ as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_bus>()).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -444,7 +444,7 @@ fn bindgen_test_layout_mpc_bus() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_bus)).busid as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_bus>()).busid as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -454,7 +454,7 @@ fn bindgen_test_layout_mpc_bus() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_bus)).bustype as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_bus>()).bustype as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -491,7 +491,7 @@ fn bindgen_test_layout_mpc_ioapic() {
|
||||
concat!("Alignment of ", stringify!(mpc_ioapic))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_ioapic)).type_ as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_ioapic>()).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -501,7 +501,7 @@ fn bindgen_test_layout_mpc_ioapic() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_ioapic)).apicid as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_ioapic>()).apicid as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -511,7 +511,7 @@ fn bindgen_test_layout_mpc_ioapic() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_ioapic)).apicver as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_ioapic>()).apicver as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -521,7 +521,7 @@ fn bindgen_test_layout_mpc_ioapic() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_ioapic)).flags as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_ioapic>()).flags as *const _ as usize },
|
||||
3usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -531,7 +531,7 @@ fn bindgen_test_layout_mpc_ioapic() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_ioapic)).apicaddr as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_ioapic>()).apicaddr as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -570,7 +570,7 @@ fn bindgen_test_layout_mpc_intsrc() {
|
||||
concat!("Alignment of ", stringify!(mpc_intsrc))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_intsrc)).type_ as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -580,7 +580,7 @@ fn bindgen_test_layout_mpc_intsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_intsrc)).irqtype as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).irqtype as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -590,7 +590,7 @@ fn bindgen_test_layout_mpc_intsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_intsrc)).irqflag as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).irqflag as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -600,7 +600,7 @@ fn bindgen_test_layout_mpc_intsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_intsrc)).srcbus as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).srcbus as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -610,7 +610,7 @@ fn bindgen_test_layout_mpc_intsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_intsrc)).srcbusirq as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).srcbusirq as *const _ as usize },
|
||||
5usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -620,7 +620,7 @@ fn bindgen_test_layout_mpc_intsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_intsrc)).dstapic as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).dstapic as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -630,7 +630,7 @@ fn bindgen_test_layout_mpc_intsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_intsrc)).dstirq as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).dstirq as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -674,7 +674,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
|
||||
concat!("Alignment of ", stringify!(mpc_lintsrc))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_lintsrc)).type_ as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -684,7 +684,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_lintsrc)).irqtype as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).irqtype as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -694,7 +694,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_lintsrc)).irqflag as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).irqflag as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -704,7 +704,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_lintsrc)).srcbusid as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).srcbusid as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -714,7 +714,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_lintsrc)).srcbusirq as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).srcbusirq as *const _ as usize },
|
||||
5usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -724,7 +724,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_lintsrc)).destapic as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).destapic as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -734,7 +734,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_lintsrc)).destapiclint as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).destapiclint as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -771,7 +771,7 @@ fn bindgen_test_layout_mpc_oemtable() {
|
||||
concat!("Alignment of ", stringify!(mpc_oemtable))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_oemtable)).signature as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_oemtable>()).signature as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -781,7 +781,7 @@ fn bindgen_test_layout_mpc_oemtable() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_oemtable)).length as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_oemtable>()).length as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -791,7 +791,7 @@ fn bindgen_test_layout_mpc_oemtable() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_oemtable)).rev as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_oemtable>()).rev as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -801,7 +801,7 @@ fn bindgen_test_layout_mpc_oemtable() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_oemtable)).checksum as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_oemtable>()).checksum as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -811,7 +811,7 @@ fn bindgen_test_layout_mpc_oemtable() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_oemtable)).mpc as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_oemtable>()).mpc as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
|
||||
@@ -10,12 +10,12 @@ io_uring = []
|
||||
|
||||
[dependencies]
|
||||
io-uring = ">=0.4.0"
|
||||
libc = "0.2.80"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
serde = ">=1.0.27"
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
|
||||
vm-memory = { version = "0.3.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-memory = { version = "0.4.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
@@ -8,14 +8,14 @@ anyhow = "1.0"
|
||||
bitflags = ">=1.2.1"
|
||||
byteorder = "1.3.4"
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.80"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-device = { path = "../vm-device" }
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
vm-memory = "0.3.0"
|
||||
vm-memory = "0.4.0"
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
|
||||
@@ -394,13 +394,13 @@ mod tests {
|
||||
Box::new(serial_out.clone()),
|
||||
);
|
||||
|
||||
serial.write(0, DATA as u64, &['x' as u8, 'y' as u8]);
|
||||
serial.write(0, DATA as u64, &['a' as u8]);
|
||||
serial.write(0, DATA as u64, &['b' as u8]);
|
||||
serial.write(0, DATA as u64, &['c' as u8]);
|
||||
serial.write(0, DATA as u64, &[b'x', b'y']);
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
serial.write(0, DATA as u64, &[b'b']);
|
||||
serial.write(0, DATA as u64, &[b'c']);
|
||||
assert_eq!(
|
||||
serial_out.buf.lock().unwrap().as_slice(),
|
||||
&['a' as u8, 'b' as u8, 'c' as u8]
|
||||
&[b'a', b'b', b'c']
|
||||
);
|
||||
}
|
||||
|
||||
@@ -411,16 +411,14 @@ mod tests {
|
||||
let mut serial = Serial::new_out(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
Box::new(serial_out.clone()),
|
||||
Box::new(serial_out),
|
||||
);
|
||||
|
||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
assert!(intr_evt.write(1).is_ok());
|
||||
serial.write(0, IER as u64, &[IER_RECV_BIT]);
|
||||
serial
|
||||
.queue_input_bytes(&['a' as u8, 'b' as u8, 'c' as u8])
|
||||
.unwrap();
|
||||
serial.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
|
||||
|
||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||
|
||||
@@ -433,11 +431,11 @@ mod tests {
|
||||
serial.read(0, LSR as u64, &mut data[..]);
|
||||
assert_ne!(data[0] & LSR_DATA_BIT, 0);
|
||||
serial.read(0, DATA as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 'a' as u8);
|
||||
assert_eq!(data[0], b'a');
|
||||
serial.read(0, DATA as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 'b' as u8);
|
||||
assert_eq!(data[0], b'b');
|
||||
serial.read(0, DATA as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 'c' as u8);
|
||||
assert_eq!(data[0], b'c');
|
||||
|
||||
// check if reading from the largest u8 offset returns 0
|
||||
serial.read(0, 0xff, &mut data[..]);
|
||||
@@ -456,7 +454,7 @@ mod tests {
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
assert!(intr_evt.write(1).is_ok());
|
||||
serial.write(0, IER as u64, &[IER_THR_BIT]);
|
||||
serial.write(0, DATA as u64, &['a' as u8]);
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
|
||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||
let mut data = [0u8];
|
||||
@@ -496,9 +494,9 @@ mod tests {
|
||||
);
|
||||
|
||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT as u8]);
|
||||
serial.write(0, DATA as u64, &['a' as u8]);
|
||||
serial.write(0, DATA as u64, &['b' as u8]);
|
||||
serial.write(0, DATA as u64, &['c' as u8]);
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
serial.write(0, DATA as u64, &[b'b']);
|
||||
serial.write(0, DATA as u64, &[b'c']);
|
||||
|
||||
let mut data = [0u8];
|
||||
serial.read(0, MSR as u64, &mut data[..]);
|
||||
@@ -506,11 +504,11 @@ mod tests {
|
||||
serial.read(0, MCR as u64, &mut data[..]);
|
||||
assert_eq!(data[0], MCR_LOOP_BIT as u8);
|
||||
serial.read(0, DATA as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 'a' as u8);
|
||||
assert_eq!(data[0], b'a');
|
||||
serial.read(0, DATA as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 'b' as u8);
|
||||
assert_eq!(data[0], b'b');
|
||||
serial.read(0, DATA as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 'c' as u8);
|
||||
assert_eq!(data[0], b'c');
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
45
docs/profiling.md
Normal file
45
docs/profiling.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Profiling
|
||||
|
||||
`perf` can be used to profile the `cloud-hypervisor` binary but it is necessary to make some modifications to the the build in order to produce a binary that gives useful results.
|
||||
|
||||
## Building a suitable binary
|
||||
|
||||
Modify the `Cargo.toml` file to add `debug = 1` to the `[profile.release]` block. It should look like this:
|
||||
|
||||
```
|
||||
[profile.release]
|
||||
lto = true
|
||||
debug = 1
|
||||
```
|
||||
|
||||
This adds the symbol information to the release binary but does not otherwise affect the performance.
|
||||
|
||||
The binary must also be built with frame pointers included so that the call graph can be captured by the profiler.
|
||||
|
||||
```
|
||||
$ cargo clean && RUSTFLAGS='-C force-frame-pointers=y' cargo build --release
|
||||
```
|
||||
|
||||
## Profiling
|
||||
|
||||
`perf` may then be used in the usual manner:
|
||||
|
||||
e.g.
|
||||
|
||||
```
|
||||
$ perf record -g target/release/cloud-hypervisor \
|
||||
--kernel ~/src/linux/vmlinux \
|
||||
--pmem file=~/workloads/focal.raw \
|
||||
--cpus boot=1 --memory size=1G \
|
||||
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
|
||||
--serial tty --console off \
|
||||
--api-socket=/tmp/api1
|
||||
```
|
||||
|
||||
For analysing the samples:
|
||||
|
||||
```
|
||||
$ perf report -g
|
||||
```
|
||||
|
||||
If profiling with a network device attached either the TAP device must be already created and configured or the profiling must be done as root so that the TAP device can be created.
|
||||
35
docs/uefi.md
35
docs/uefi.md
@@ -35,8 +35,41 @@ To make Cloud Hypervisor use UEFI boot, pass the `OVMF.fd` file path as an argum
|
||||
|
||||
The same firmware can be used with Cloud Hypervisor or with QEMU. This is particularly useful if using QEMU for the preparation phase.
|
||||
|
||||
## Building UEFI Firmware with Compatibility Support Module (CSM)
|
||||
|
||||
CSM is a module that allows to boot legacy operating systems using the OVMF firmware. OVMF can embed a CSM build of SeaBIOS. To build the SeaBIOS with CSM support, add `CONFIG_CSM=y` to `.config` before the build. The outcome `out/Csm16.bin` is to be moved into `OvmfPkg/Csm/Csm16/Csm16.bin` before OVMF is built. Then, the OVMF build will have to be passed the `-D CSM_ENABLE` option in order to generate a legacy aware UEFI firmware. At the current stage, all the necessary patches are included in the Cloud Hypervisor specific [SeaBIOS branch](https://github.com/cloud-hypervisor/seabios/tree/ch). Taking into account the previous instructions, the modified command sequence to compile an OVMF binary with CSM support is the following one:
|
||||
|
||||
```shell
|
||||
sudo apt-get update
|
||||
sudo apt-get install uuid-dev nasm iasl build-essential python3-distutils git
|
||||
|
||||
git checkout https://github.com/cloud-hypervisor/seabios -b ch
|
||||
cd seabios
|
||||
make menuconfig
|
||||
# Enable `CONFIG_CSM` and `CONFIG_QEMU_HARDWARE`
|
||||
make CONFIG_CSM=y CONFIG_QEMU_HARDWARE=y
|
||||
cd ..
|
||||
|
||||
git clone https://github.com/cloud-hypervisor/edk2 -b ch
|
||||
cd edk2
|
||||
. edksetup.sh
|
||||
git submodule update --init
|
||||
cp ../seabios/out/Csm16.bin OvmfPkg/Csm/Csm16/
|
||||
|
||||
echo "ACTIVE_PLATFORM=OvmfPkg/OvmfCh.dsc" >> Conf/target.txt
|
||||
echo "TARGET_ARCH=X64" >> Conf/target.txt
|
||||
echo "TOOL_CHAIN_TAG=GCC5" >> Conf/target.txt
|
||||
|
||||
make -C ./BaseTools
|
||||
build
|
||||
|
||||
```
|
||||
|
||||
Please note, that the CSM support has currently only been tested with Linux guests. There are no plans to provide legacy support for other OSes (e.g. Windows).
|
||||
|
||||
# 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)
|
||||
|
||||
@@ -282,6 +282,17 @@ Note, this VM has the networking enabled. It is needed, because symbols and sour
|
||||
|
||||
Also, notice the `-serial` parameter - that's what does the magic on exposing the serial port to the guest while connecting the debugger VM with a client VM through the network. SAC/EMS needs to be disabled in the debugger VM, as otherwise the COM device might be blocked.
|
||||
|
||||
Hereafter, WinDbg can be started using a command below:
|
||||
|
||||
```cmd
|
||||
set _NT_DEBUG_PORT=com1
|
||||
set _NT_DEBUG_BAUD_RATE=115200
|
||||
|
||||
windbg -v -d -k
|
||||
```
|
||||
|
||||
Once started, WinDbg will wait for an incoming connection which is going to be initialized by the debuggee VM started in the next section.
|
||||
|
||||
#### Invoke the Debuggee VM
|
||||
|
||||
##### Under QEMU
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
[package]
|
||||
name = "cloud-hypervisor-fuzz"
|
||||
version = "0.0.0"
|
||||
@@ -10,7 +9,6 @@ edition = "2018"
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = "0.2.72"
|
||||
libfuzzer-sys = "0.3"
|
||||
qcow = { path = "../qcow" }
|
||||
@@ -18,18 +16,11 @@ seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v
|
||||
virtio-devices = { path = "../virtio-devices" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vm-memory = "0.3.0"
|
||||
vm-memory = "0.4.0"
|
||||
|
||||
[dependencies.cloud-hypervisor]
|
||||
path = ".."
|
||||
|
||||
[patch.crates-io]
|
||||
vm-memory = { git = "https://github.com/cloud-hypervisor/vm-memory", branch = "ch" }
|
||||
[patch.'https://github.com/rust-vmm/kvm-ioctls']
|
||||
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
|
||||
[patch.'https://github.com/rust-vmm/kvm-bindings']
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch", features = ["with-serde", "fam-wrappers"] }
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
@@ -7,21 +7,34 @@ license = "Apache-2.0 OR BSD-3-Clause"
|
||||
|
||||
[features]
|
||||
kvm = []
|
||||
mshv = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
arc-swap = ">=0.4.4"
|
||||
epoll = ">=4.0.1"
|
||||
thiserror = "1.0"
|
||||
libc = "0.2.80"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
kvm-bindings = { git = "https://github.com/rust-vmm/kvm-bindings", branch = "master", features = ["with-serde", "fam-wrappers"] }
|
||||
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch", features = ["with-serde", "fam-wrappers"] }
|
||||
|
||||
mshv-bindings = {git = "https://github.com/cloud-hypervisor/mshv", branch = "master", features = ["with-serde", "fam-wrappers"] }
|
||||
mshv-ioctls = { git = "https://github.com/cloud-hypervisor/mshv", branch = "master" }
|
||||
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-memory = { version = "0.3.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-memory = { version = "0.4.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vmm-sys-util = { version = ">=0.5.0", features = ["with-serde"] }
|
||||
|
||||
[dependencies.linux-loader]
|
||||
git = "https://github.com/rust-vmm/linux-loader"
|
||||
features = ["elf", "bzimage"]
|
||||
|
||||
[target.'cfg(target_arch = "x86_64")'.dependencies.iced-x86]
|
||||
version = "1.9.1"
|
||||
default-features = false
|
||||
features = ["std", "decoder", "op_code_info", "instr_info", "fast_fmt"]
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.8.2"
|
||||
|
||||
148
hypervisor/src/arch/emulator/mod.rs
Normal file
148
hypervisor/src/arch/emulator/mod.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
extern crate thiserror;
|
||||
|
||||
use core::fmt::Debug;
|
||||
use std::fmt::{self, Display};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Clone, Copy, Error, Debug)]
|
||||
pub struct Exception<T: Debug> {
|
||||
vector: T,
|
||||
ip: u64,
|
||||
error: Option<u32>,
|
||||
payload: Option<u64>,
|
||||
}
|
||||
|
||||
impl<T: Debug> Display for Exception<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Exception {:?} at IP {:#x}", self.vector, self.ip)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PlatformError {
|
||||
#[error("Invalid address: {0}")]
|
||||
InvalidAddress(#[source] anyhow::Error),
|
||||
|
||||
#[error("Invalid register: {0}")]
|
||||
InvalidRegister(#[source] anyhow::Error),
|
||||
|
||||
#[error("Invalid state: {0}")]
|
||||
InvalidState(#[source] anyhow::Error),
|
||||
|
||||
#[error("Memory read failure: {0}")]
|
||||
MemoryReadFailure(#[source] anyhow::Error),
|
||||
|
||||
#[error("Memory write failure: {0}")]
|
||||
MemoryWriteFailure(#[source] anyhow::Error),
|
||||
|
||||
#[error("Get CPU state failure: {0}")]
|
||||
GetCpuStateFailure(#[source] anyhow::Error),
|
||||
|
||||
#[error("Set CPU state failure: {0}")]
|
||||
SetCpuStateFailure(#[source] anyhow::Error),
|
||||
|
||||
#[error("Unmapped virtual address: {0}")]
|
||||
UnmappedGVA(#[source] anyhow::Error),
|
||||
|
||||
#[error("Unsupported CPU Mode: {0}")]
|
||||
UnsupportedCpuMode(#[source] anyhow::Error),
|
||||
|
||||
#[error("Invalid instruction operand: {0}")]
|
||||
InvalidOperand(#[source] anyhow::Error),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum EmulationError<T: Debug> {
|
||||
#[error("Unsupported instruction: {0}")]
|
||||
UnsupportedInstruction(#[source] anyhow::Error),
|
||||
|
||||
#[error("Unsupported memory size: {0}")]
|
||||
UnsupportedMemorySize(#[source] anyhow::Error),
|
||||
|
||||
#[error("Invalid operand: {0}")]
|
||||
InvalidOperand(#[source] anyhow::Error),
|
||||
|
||||
#[error("Wrong number of operands: {0}")]
|
||||
WrongNumberOperands(#[source] anyhow::Error),
|
||||
|
||||
#[error("Instruction Exception: {0}")]
|
||||
InstructionException(Exception<T>),
|
||||
|
||||
#[error("Instruction fetching error: {0}")]
|
||||
InstructionFetchingError(#[source] anyhow::Error),
|
||||
|
||||
#[error("Platform emulation error: {0}")]
|
||||
PlatformEmulationError(PlatformError),
|
||||
|
||||
#[error(transparent)]
|
||||
EmulationError(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
/// The PlatformEmulator trait emulates a guest platform.
|
||||
/// It's mostly a guest resources (memory and CPU state) getter and setter.
|
||||
///
|
||||
/// A CpuState is an architecture specific type, representing a CPU state.
|
||||
/// The emulator and its instruction handlers modify a given CPU state and
|
||||
/// eventually ask the platform to commit it back through `set_cpu_state`.
|
||||
pub trait PlatformEmulator: Send + Sync {
|
||||
type CpuState: Clone;
|
||||
|
||||
/// Read guest memory into a u8 slice.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `gva` - Guest virtual address to read from.
|
||||
/// * `data` - Data slice to read into.
|
||||
///
|
||||
fn read_memory(&self, gva: u64, data: &mut [u8]) -> Result<(), PlatformError>;
|
||||
|
||||
/// Write a u8 slice into guest memory.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `gva` - Guest virtual address to write into.
|
||||
/// * `data` - Data slice to be written.
|
||||
///
|
||||
fn write_memory(&mut self, gva: u64, data: &[u8]) -> Result<(), PlatformError>;
|
||||
|
||||
/// Get a CPU state from the guest.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cpu_id` - Logical CPU ID.
|
||||
///
|
||||
fn cpu_state(&self, cpu_id: usize) -> Result<Self::CpuState, PlatformError>;
|
||||
|
||||
/// Set a guest CPU state.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cpu_id` - Logical CPU ID.
|
||||
/// * `state` - State to set the CPU into.
|
||||
///
|
||||
fn set_cpu_state(&self, cpu_id: usize, state: Self::CpuState) -> Result<(), PlatformError>;
|
||||
|
||||
/// Translate a guest virtual address into a physical one
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `gva` - Guest virtual address to translate.
|
||||
///
|
||||
fn gva_to_gpa(&self, gva: u64) -> Result<u64, PlatformError>;
|
||||
|
||||
/// Fetch instruction bytes from memory.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `ip` - Instruction pointer virtual address to start fetching instructions from.
|
||||
///
|
||||
fn fetch(&self, ip: u64, instruction_bytes: &mut [u8]) -> Result<(), PlatformError>;
|
||||
}
|
||||
|
||||
pub type EmulationResult<S, E> = std::result::Result<S, EmulationError<E>>;
|
||||
@@ -11,5 +11,7 @@
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
|
||||
pub mod emulator;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod x86;
|
||||
|
||||
355
hypervisor/src/arch/x86/emulator/instructions/cmp.rs
Normal file
355
hypervisor/src/arch/x86/emulator/instructions/cmp.rs
Normal file
@@ -0,0 +1,355 @@
|
||||
//
|
||||
// Copyright © 2020 Microsoft
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
//
|
||||
// CMP-Compare Two Operands
|
||||
//
|
||||
|
||||
extern crate iced_x86;
|
||||
|
||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
||||
use crate::arch::x86::emulator::instructions::*;
|
||||
use crate::arch::x86::regs::*;
|
||||
use crate::arch::x86::Exception;
|
||||
|
||||
// CMP affects OF, SF, ZF, AF, PF and CF
|
||||
const FLAGS_MASK: u64 = CF | PF | AF | ZF | SF | OF;
|
||||
|
||||
// TODO: Switch to inline asm when that's stable. Executing CMP (or any arthimetic instructions)
|
||||
// natively and extracting RFLAGS will be much faster and make the code simpler.
|
||||
fn calc_rflags_cpazso(op0: u64, op1: u64, op_size: usize) -> u64 {
|
||||
let op_bits = op_size * 8;
|
||||
let msb_shift = op_bits - 1;
|
||||
// CMP is the same as SUB.
|
||||
let result = op0.wrapping_sub(op1);
|
||||
|
||||
// Carry-out vector for SUB.
|
||||
let cout = (!op0 & op1) | ((!op0 ^ op1) & result);
|
||||
|
||||
let cf = ((cout >> msb_shift) & 0x1) << CF_SHIFT;
|
||||
|
||||
// PF only needs the least significant byte. XOR its higher 4 bits with its lower 4 bits then
|
||||
// use the value directly.
|
||||
let pf = ((0x9669 >> ((result ^ (result >> 4)) & 0xf)) & 0x1) << PF_SHIFT;
|
||||
|
||||
// AF cares about the lowest 4 bits (nibble). msb_shift is 3 in this case.
|
||||
let af = ((cout >> 3) & 0x1) << AF_SHIFT;
|
||||
|
||||
let zf = if result & (!0u64 >> (63 - msb_shift)) == 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
} << ZF_SHIFT;
|
||||
|
||||
let sf = ((result >> msb_shift) & 0x1) << SF_SHIFT;
|
||||
|
||||
// Overflow happens when two operands have the same sign but the result has a different sign.
|
||||
let of = ((((op0 ^ op1) & (op0 ^ result)) >> msb_shift) & 0x1) << OF_SHIFT;
|
||||
|
||||
cf | pf | af | zf | sf | of
|
||||
}
|
||||
|
||||
macro_rules! cmp_rm_r {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
let op1_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
|
||||
|
||||
state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! cmp_r_rm {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
let op1_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
|
||||
|
||||
state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! cmp_rm_imm {
|
||||
($imm:ty, $bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
let op1_value = get_op(&insn, 1, std::mem::size_of::<$imm>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
|
||||
|
||||
state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Cmp_rm64_r64;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_r64 {
|
||||
cmp_rm_r!(u64);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm32_r32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_r32 {
|
||||
cmp_rm_r!(u32);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm16_r16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_r16 {
|
||||
cmp_rm_r!(u16);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm8_r8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm8_r8 {
|
||||
cmp_rm_r!(u8);
|
||||
}
|
||||
|
||||
pub struct Cmp_r64_rm64;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r64_rm64 {
|
||||
cmp_r_rm!(u64);
|
||||
}
|
||||
|
||||
pub struct Cmp_r32_rm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r32_rm32 {
|
||||
cmp_r_rm!(u32);
|
||||
}
|
||||
|
||||
pub struct Cmp_r16_rm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r16_rm16 {
|
||||
cmp_r_rm!(u16);
|
||||
}
|
||||
|
||||
pub struct Cmp_r8_rm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r8_rm8 {
|
||||
cmp_r_rm!(u8);
|
||||
}
|
||||
|
||||
pub struct Cmp_AL_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_AL_imm8 {
|
||||
cmp_rm_imm!(u8, u8);
|
||||
}
|
||||
|
||||
pub struct Cmp_AX_imm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_AX_imm16 {
|
||||
cmp_rm_imm!(u16, u16);
|
||||
}
|
||||
|
||||
pub struct Cmp_EAX_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_EAX_imm32 {
|
||||
cmp_rm_imm!(u32, u32);
|
||||
}
|
||||
|
||||
pub struct Cmp_RAX_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_RAX_imm32 {
|
||||
cmp_rm_imm!(u32, u64);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm8_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm8_imm8 {
|
||||
cmp_rm_imm!(u8, u8);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm16_imm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_imm16 {
|
||||
cmp_rm_imm!(u16, u16);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm32_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_imm32 {
|
||||
cmp_rm_imm!(u32, u32);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm64_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_imm32 {
|
||||
cmp_rm_imm!(u32, u64);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm16_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_imm8 {
|
||||
cmp_rm_imm!(u8, u16);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm32_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_imm8 {
|
||||
cmp_rm_imm!(u8, u32);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm64_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_imm8 {
|
||||
cmp_rm_imm!(u8, u64);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(unused_mut)]
|
||||
|
||||
use super::*;
|
||||
use crate::arch::x86::emulator::mock_vmm::*;
|
||||
|
||||
#[test]
|
||||
// cmp ah,al
|
||||
fn test_cmp_rm8_r8_1() -> MockResult {
|
||||
let rax: u64 = 0x0;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x38, 0xc4]; // cmp ah,al
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b1000100, rflags);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// cmp eax,100
|
||||
fn test_cmp_rm32_imm8_1() -> MockResult {
|
||||
let rax: u64 = 0xabcdef;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x83, 0xf8, 0x64]; // cmp eax,100
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b100, rflags);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// cmp eax,-1
|
||||
fn test_cmp_rm32_imm8_2() -> MockResult {
|
||||
let rax: u64 = 0xabcdef;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x83, 0xf8, 0xff]; // cmp eax,-1
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b101, rflags);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// cmp rax,rbx
|
||||
fn test_cmp_rm64_r64() -> MockResult {
|
||||
let rax: u64 = 0xabcdef;
|
||||
let rbx: u64 = 0x1234;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x48, 0x39, 0xd8, 0x00, 0xc3]; // cmp rax,rbx + two bytes garbage
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax), (Register::RBX, rbx)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b100, rflags);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmp_64() -> MockResult {
|
||||
let data = [
|
||||
(0xabcdef, 0x1234, 0b100),
|
||||
(0x0, 0x101, 0b1001_0101),
|
||||
(0x0, 0x8000_0000_0000_0000, 0b1000_1000_0101),
|
||||
(0x1234abcd, 0x1234abcd, 0b100_0100),
|
||||
(0x1234abcd, 0xdeadbeef, 0b1001_0101),
|
||||
(0xffff_ffff_ffff_ffff, 0xdeadbeef, 0b1000_0000),
|
||||
(0xffff_ffff_ffff_ffff, 0x0, 0b1000_0100),
|
||||
];
|
||||
|
||||
for d in data.iter() {
|
||||
let rax = d.0;
|
||||
let rbx = d.1;
|
||||
let insn = [0x48, 0x39, 0xd8]; // cmp rax,rbx
|
||||
let mut vmm = MockVMM::new(
|
||||
0x1000,
|
||||
vec![(Register::RAX, rax), (Register::RBX, rbx)],
|
||||
None,
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(0).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(d.2, rflags);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmp_32() -> MockResult {
|
||||
let data = [
|
||||
(0xabcdef, 0x1234, 0b100),
|
||||
(0x0, 0x101, 0b1001_0101),
|
||||
(0x0, 0x8000_0000_0000_0000, 0b100_0100), // Same as cmp 0,0 due to truncation
|
||||
(0x1234abcd, 0x1234abcd, 0b100_0100),
|
||||
(0x1234abcd, 0xdeadbeef, 0b1_0101),
|
||||
(0xffff_ffff_ffff_ffff, 0xdeadbeef, 0b0), // Same as cmp 0xffffffff,0xdeadbeef
|
||||
(0xffff_ffff, 0x0, 0b1000_0100),
|
||||
];
|
||||
|
||||
for d in data.iter() {
|
||||
let rax = d.0;
|
||||
let rbx = d.1;
|
||||
let insn = [0x39, 0xd8]; // cmp eax,ebx
|
||||
let mut vmm = MockVMM::new(
|
||||
0x1000,
|
||||
vec![(Register::RAX, rax), (Register::RBX, rbx)],
|
||||
None,
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(0).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(d.2, rflags);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
146
hypervisor/src/arch/x86/emulator/instructions/mod.rs
Normal file
146
hypervisor/src/arch/x86/emulator/instructions/mod.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
extern crate iced_x86;
|
||||
|
||||
use crate::arch::emulator::{EmulationError, PlatformEmulator, PlatformError};
|
||||
use crate::arch::x86::emulator::CpuStateManager;
|
||||
use crate::arch::x86::Exception;
|
||||
use iced_x86::*;
|
||||
|
||||
pub mod cmp;
|
||||
pub mod mov;
|
||||
|
||||
fn get_op<T: CpuStateManager>(
|
||||
insn: &Instruction,
|
||||
op_index: u32,
|
||||
op_size: usize,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<u64, PlatformError> {
|
||||
if insn.op_count() < op_index + 1 {
|
||||
return Err(PlatformError::InvalidOperand(anyhow!(
|
||||
"Invalid operand {:?}",
|
||||
op_index
|
||||
)));
|
||||
}
|
||||
|
||||
match op_size {
|
||||
1 | 2 | 4 | 8 => {}
|
||||
_ => {
|
||||
return Err(PlatformError::InvalidOperand(anyhow!(
|
||||
"Invalid operand size {:?}",
|
||||
op_size
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
let value = match insn.op_kind(op_index) {
|
||||
OpKind::Register => state.read_reg(insn.op_register(op_index))?,
|
||||
OpKind::Memory => {
|
||||
let addr = memory_operand_address(insn, state, false)?;
|
||||
let mut memory: [u8; 8] = [0; 8];
|
||||
platform.read_memory(addr, &mut memory[0..op_size])?;
|
||||
<u64>::from_le_bytes(memory)
|
||||
}
|
||||
OpKind::Immediate8 => insn.immediate8() as u64,
|
||||
OpKind::Immediate8to16 => insn.immediate8to16() as u64,
|
||||
OpKind::Immediate8to32 => insn.immediate8to32() as u64,
|
||||
OpKind::Immediate8to64 => insn.immediate8to64() as u64,
|
||||
OpKind::Immediate16 => insn.immediate16() as u64,
|
||||
OpKind::Immediate32 => insn.immediate32() as u64,
|
||||
OpKind::Immediate32to64 => insn.immediate32to64() as u64,
|
||||
OpKind::Immediate64 => insn.immediate64() as u64,
|
||||
k => return Err(PlatformError::InvalidOperand(anyhow!("{:?}", k))),
|
||||
};
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn set_op<T: CpuStateManager>(
|
||||
insn: &Instruction,
|
||||
op_index: u32,
|
||||
op_size: usize,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
value: u64,
|
||||
) -> Result<(), PlatformError> {
|
||||
if insn.op_count() < op_index + 1 {
|
||||
return Err(PlatformError::InvalidOperand(anyhow!(
|
||||
"Invalid operand {:?}",
|
||||
op_index
|
||||
)));
|
||||
}
|
||||
|
||||
match op_size {
|
||||
1 | 2 | 4 | 8 => {}
|
||||
_ => {
|
||||
return Err(PlatformError::InvalidOperand(anyhow!(
|
||||
"Invalid operand size {:?}",
|
||||
op_size
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
match insn.op_kind(op_index) {
|
||||
OpKind::Register => state.write_reg(insn.op_register(op_index), value)?,
|
||||
OpKind::Memory => {
|
||||
let addr = memory_operand_address(insn, state, true)?;
|
||||
platform.write_memory(addr, &value.to_le_bytes()[..op_size])?;
|
||||
}
|
||||
k => return Err(PlatformError::InvalidOperand(anyhow!("{:?}", k))),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Returns the linear a.k.a. virtual address for a memory operand.
|
||||
fn memory_operand_address<T: CpuStateManager>(
|
||||
insn: &Instruction,
|
||||
state: &T,
|
||||
write: bool,
|
||||
) -> Result<u64, PlatformError> {
|
||||
let mut address: u64 = 0;
|
||||
|
||||
if insn.memory_base() != iced_x86::Register::None {
|
||||
let base: u64 = state.read_reg(insn.memory_base())?;
|
||||
address += base;
|
||||
}
|
||||
|
||||
if insn.memory_index() != iced_x86::Register::None {
|
||||
let mut index: u64 = state.read_reg(insn.memory_index())?;
|
||||
index *= insn.memory_index_scale() as u64;
|
||||
|
||||
address += index;
|
||||
}
|
||||
|
||||
address += insn.memory_displacement() as u64;
|
||||
|
||||
// Translate to a linear address.
|
||||
state.linearize(insn.memory_segment(), address, write)
|
||||
}
|
||||
|
||||
pub trait InstructionHandler<T: CpuStateManager> {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>>;
|
||||
}
|
||||
|
||||
macro_rules! insn_format {
|
||||
($insn:ident) => {{
|
||||
let mut output = String::new();
|
||||
let mut formatter = FastFormatter::new();
|
||||
formatter
|
||||
.options_mut()
|
||||
.set_space_after_operand_separator(true);
|
||||
formatter.format(&$insn, &mut output);
|
||||
|
||||
output
|
||||
}};
|
||||
}
|
||||
563
hypervisor/src/arch/x86/emulator/instructions/mov.rs
Normal file
563
hypervisor/src/arch/x86/emulator/instructions/mov.rs
Normal file
@@ -0,0 +1,563 @@
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
//
|
||||
// MOV-Move
|
||||
// SDM Volume 1, Chapter 4.3
|
||||
// Copies the second operand (source operand) to the first operand (destination operand).
|
||||
//
|
||||
|
||||
extern crate iced_x86;
|
||||
|
||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
||||
use crate::arch::x86::emulator::instructions::*;
|
||||
use crate::arch::x86::Exception;
|
||||
|
||||
macro_rules! mov_rm_r {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let src_reg_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
set_op(
|
||||
&insn,
|
||||
0,
|
||||
std::mem::size_of::<$bound>(),
|
||||
state,
|
||||
platform,
|
||||
src_reg_value,
|
||||
)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! mov_rm_imm {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let imm = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
set_op(
|
||||
&insn,
|
||||
0,
|
||||
std::mem::size_of::<$bound>(),
|
||||
state,
|
||||
platform,
|
||||
imm,
|
||||
)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! mov_r_rm {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let src_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
set_op(
|
||||
&insn,
|
||||
0,
|
||||
std::mem::size_of::<$bound>(),
|
||||
state,
|
||||
platform,
|
||||
src_value,
|
||||
)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! mov_r_imm {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let imm = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
set_op(
|
||||
&insn,
|
||||
0,
|
||||
std::mem::size_of::<$bound>(),
|
||||
state,
|
||||
platform,
|
||||
imm,
|
||||
)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Mov_r8_rm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r8_rm8 {
|
||||
mov_r_rm!(u8);
|
||||
}
|
||||
|
||||
pub struct Mov_r8_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r8_imm8 {
|
||||
mov_r_imm!(u8);
|
||||
}
|
||||
|
||||
pub struct Mov_r16_rm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r16_rm16 {
|
||||
mov_r_rm!(u16);
|
||||
}
|
||||
|
||||
pub struct Mov_r16_imm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r16_imm16 {
|
||||
mov_r_imm!(u16);
|
||||
}
|
||||
|
||||
pub struct Mov_r32_rm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r32_rm32 {
|
||||
mov_r_rm!(u32);
|
||||
}
|
||||
|
||||
pub struct Mov_r32_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r32_imm32 {
|
||||
mov_r_imm!(u32);
|
||||
}
|
||||
|
||||
pub struct Mov_r64_rm64;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r64_rm64 {
|
||||
mov_r_rm!(u64);
|
||||
}
|
||||
|
||||
pub struct Mov_r64_imm64;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r64_imm64 {
|
||||
mov_r_imm!(u64);
|
||||
}
|
||||
|
||||
pub struct Mov_rm8_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm8_imm8 {
|
||||
mov_rm_imm!(u8);
|
||||
}
|
||||
|
||||
pub struct Mov_rm8_r8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm8_r8 {
|
||||
mov_rm_r!(u8);
|
||||
}
|
||||
|
||||
pub struct Mov_rm16_imm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm16_imm16 {
|
||||
mov_rm_imm!(u16);
|
||||
}
|
||||
|
||||
pub struct Mov_rm16_r16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm16_r16 {
|
||||
mov_rm_r!(u16);
|
||||
}
|
||||
|
||||
pub struct Mov_rm32_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm32_imm32 {
|
||||
mov_rm_imm!(u32);
|
||||
}
|
||||
|
||||
pub struct Mov_rm32_r32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm32_r32 {
|
||||
mov_rm_r!(u32);
|
||||
}
|
||||
|
||||
pub struct Mov_rm64_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm64_imm32 {
|
||||
mov_rm_imm!(u32);
|
||||
}
|
||||
|
||||
pub struct Mov_rm64_r64;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm64_r64 {
|
||||
mov_rm_r!(u64);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(unused_mut)]
|
||||
use super::*;
|
||||
use crate::arch::x86::emulator::mock_vmm::*;
|
||||
|
||||
#[test]
|
||||
// mov rax,rbx
|
||||
fn test_mov_r64_r64() -> MockResult {
|
||||
let rbx: u64 = 0x8899aabbccddeeff;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x48, 0x89, 0xd8];
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RBX, rbx)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, rbx);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax,0x1122334411223344
|
||||
fn test_mov_r64_imm64() -> MockResult {
|
||||
let imm64: u64 = 0x1122334411223344;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x48, 0xb8, 0x44, 0x33, 0x22, 0x11, 0x44, 0x33, 0x22, 0x11];
|
||||
let mut vmm = MockVMM::new(ip, vec![], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, imm64);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax, [rax+rax]
|
||||
fn test_mov_r64_m64() -> MockResult {
|
||||
let target_rax: u64 = 0x1234567812345678;
|
||||
let mut rax: u64 = 0x100;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory: [u8; 8] = target_rax.to_le_bytes();
|
||||
let insn = [0x48, 0x8b, 0x04, 0x00];
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], Some((rax + rax, &memory)));
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
rax = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, target_rax);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov al,0x11
|
||||
fn test_mov_r8_imm8() -> MockResult {
|
||||
let imm8: u8 = 0x11;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0xb0, 0x11];
|
||||
let mut vmm = MockVMM::new(ip, vec![], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let al = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::AL)
|
||||
.unwrap();
|
||||
assert_eq!(al as u8, imm8);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov eax,0x11
|
||||
fn test_mov_r32_imm8() -> MockResult {
|
||||
let imm8: u8 = 0x11;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0xb8, 0x11, 0x00, 0x00, 0x00];
|
||||
let mut vmm = MockVMM::new(ip, vec![], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let eax = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::EAX)
|
||||
.unwrap();
|
||||
assert_eq!(eax as u8, imm8);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax,0x11223344
|
||||
fn test_mov_r64_imm32() -> MockResult {
|
||||
let imm32: u32 = 0x11223344;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x48, 0xc7, 0xc0, 0x44, 0x33, 0x22, 0x11];
|
||||
let mut vmm = MockVMM::new(ip, vec![], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, imm32 as u64);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov byte ptr [rax],dh
|
||||
fn test_mov_m8_r8() -> MockResult {
|
||||
let rax: u64 = 0x100;
|
||||
let dh: u8 = 0x99;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x88, 0x30];
|
||||
let mut vmm = MockVMM::new(
|
||||
ip,
|
||||
vec![(Register::RAX, rax), (Register::DH, dh.into())],
|
||||
None,
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let mut memory: [u8; 1] = [0; 1];
|
||||
vmm.read_memory(rax, &mut memory).unwrap();
|
||||
|
||||
assert_eq!(u8::from_le_bytes(memory), dh);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov dword ptr [rax],esi
|
||||
fn test_mov_m32_r32() -> MockResult {
|
||||
let rax: u64 = 0x100;
|
||||
let esi: u32 = 0x8899;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x89, 0x30];
|
||||
let mut vmm = MockVMM::new(
|
||||
ip,
|
||||
vec![(Register::RAX, rax), (Register::ESI, esi.into())],
|
||||
None,
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let mut memory: [u8; 4] = [0; 4];
|
||||
vmm.read_memory(rax, &mut memory).unwrap();
|
||||
|
||||
assert_eq!(u32::from_le_bytes(memory), esi);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov dword ptr [rax+0x00000001],edi
|
||||
fn test_mov_m32imm32_r32() -> MockResult {
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x1;
|
||||
let edi: u32 = 0x8899;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x89, 0x3c, 0x05, 0x01, 0x00, 0x00, 0x00];
|
||||
let mut vmm = MockVMM::new(
|
||||
ip,
|
||||
vec![(Register::RAX, rax), (Register::EDI, edi.into())],
|
||||
None,
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let mut memory: [u8; 4] = [0; 4];
|
||||
vmm.read_memory(rax + displacement, &mut memory).unwrap();
|
||||
|
||||
assert_eq!(u32::from_le_bytes(memory), edi);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov eax,dword ptr [rax+10h]
|
||||
fn test_mov_r32_m32imm32() -> MockResult {
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x10;
|
||||
let eax: u32 = 0xaabbccdd;
|
||||
let memory: [u8; 4] = eax.to_le_bytes();
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x8b, 0x40, 0x10];
|
||||
let mut vmm = MockVMM::new(
|
||||
ip,
|
||||
vec![(Register::RAX, rax)],
|
||||
Some((rax + displacement, &memory)),
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let new_eax = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::EAX)
|
||||
.unwrap();
|
||||
assert_eq!(new_eax, eax as u64);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov al,byte ptr [rax+10h]
|
||||
fn test_mov_r8_m32imm32() -> MockResult {
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x10;
|
||||
let al: u8 = 0xaa;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x8a, 0x40, 0x10];
|
||||
let memory: [u8; 1] = al.to_le_bytes();
|
||||
let mut vmm = MockVMM::new(
|
||||
ip,
|
||||
vec![(Register::RAX, rax)],
|
||||
Some((rax + displacement, &memory)),
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let new_al = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::AL)
|
||||
.unwrap();
|
||||
assert_eq!(new_al, al as u64);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax, 0x100
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
fn test_mov_r64_imm64_and_r64_m64() -> MockResult {
|
||||
let target_rax: u64 = 0x1234567812345678;
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x10;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory: [u8; 8] = target_rax.to_le_bytes();
|
||||
let insn = [
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x01, 0x00, 0x00, // mov rax, 0x100
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
];
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((rax + displacement, &memory)));
|
||||
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
|
||||
|
||||
let rbx: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RBX)
|
||||
.unwrap();
|
||||
assert_eq!(rbx, target_rax);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax, 0x100
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
fn test_mov_r64_imm64_and_r64_m64_first_insn() -> MockResult {
|
||||
let target_rax: u64 = 0x1234567812345678;
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x10;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory: [u8; 8] = target_rax.to_le_bytes();
|
||||
let insn = [
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x01, 0x00, 0x00, // mov rax, 0x100
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
];
|
||||
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((rax + displacement, &memory)));
|
||||
// Only run the first instruction.
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
assert_eq!(ip + 7 as u64, vmm.cpu_state(cpu_id).unwrap().ip());
|
||||
|
||||
let new_rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, new_rax);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax, 0x100
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
// mov rax, 0x200
|
||||
fn test_mov_r64_imm64_and_r64_m64_two_insns() -> MockResult {
|
||||
let target_rax: u64 = 0x1234567812345678;
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x10;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory: [u8; 8] = target_rax.to_le_bytes();
|
||||
let insn = [
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x01, 0x00, 0x00, // mov rax, 0x100
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x02, 0x00, 0x00, // mov rax, 0x200
|
||||
];
|
||||
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((rax + displacement, &memory)));
|
||||
// Run the 2 first instructions.
|
||||
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
|
||||
|
||||
assert_eq!(ip + 7 + 4 as u64, vmm.cpu_state(cpu_id).unwrap().ip());
|
||||
|
||||
let rbx: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RBX)
|
||||
.unwrap();
|
||||
assert_eq!(rbx, target_rax);
|
||||
|
||||
// Check that rax is still at 0x100
|
||||
let new_rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, new_rax);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
859
hypervisor/src/arch/x86/emulator/mod.rs
Normal file
859
hypervisor/src/arch/x86/emulator/mod.rs
Normal file
@@ -0,0 +1,859 @@
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
extern crate iced_x86;
|
||||
|
||||
use crate::arch::emulator::{EmulationError, EmulationResult, PlatformEmulator, PlatformError};
|
||||
use crate::arch::x86::emulator::instructions::*;
|
||||
use crate::arch::x86::regs::*;
|
||||
use crate::arch::x86::*;
|
||||
use crate::arch::x86::{Exception, SegmentRegisterOps};
|
||||
use crate::x86_64::{SegmentRegister, SpecialRegisters, StandardRegisters};
|
||||
use anyhow::Context;
|
||||
use iced_x86::*;
|
||||
|
||||
#[macro_use]
|
||||
mod instructions;
|
||||
|
||||
/// x86 CPU modes
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum CpuMode {
|
||||
/// Real mode
|
||||
Real,
|
||||
|
||||
/// Virtual 8086 mode
|
||||
Virtual8086,
|
||||
|
||||
/// 16-bit protected mode
|
||||
Protected16,
|
||||
|
||||
/// 32-bit protected mode
|
||||
Protected,
|
||||
|
||||
/// 64-bit mode, a.k.a. long mode
|
||||
Long,
|
||||
}
|
||||
|
||||
/// CpuStateManager manages an x86 CPU state.
|
||||
///
|
||||
/// Instruction emulation handlers get a mutable reference to
|
||||
/// a `CpuStateManager` implementation, representing the current state of the
|
||||
/// CPU they have to emulate an instruction stream against. Usually those
|
||||
/// handlers will modify the CPU state by modifying `CpuState` and it is up to
|
||||
/// the handler caller to commit those changes back by invoking a
|
||||
/// `PlatformEmulator` implementation `set_state()` method.
|
||||
///
|
||||
pub trait CpuStateManager: Clone {
|
||||
/// Reads a CPU register.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `reg` - A general purpose, control or debug register.
|
||||
fn read_reg(&self, reg: Register) -> Result<u64, PlatformError>;
|
||||
|
||||
/// Write to a CPU register.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `reg` - A general purpose, control or debug register.
|
||||
/// * `val` - The value to load.
|
||||
fn write_reg(&mut self, reg: Register, val: u64) -> Result<(), PlatformError>;
|
||||
|
||||
/// Reads a segment register.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `reg` - A segment register.
|
||||
fn read_segment(&self, reg: Register) -> Result<SegmentRegister, PlatformError>;
|
||||
|
||||
/// Write to a segment register.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `reg` - A segment register.
|
||||
/// * `segment_reg` - The segment register value to load.
|
||||
fn write_segment(
|
||||
&mut self,
|
||||
reg: Register,
|
||||
segment_reg: SegmentRegister,
|
||||
) -> Result<(), PlatformError>;
|
||||
|
||||
/// Get the CPU instruction pointer.
|
||||
fn ip(&self) -> u64;
|
||||
|
||||
/// Set the CPU instruction pointer.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `ip` - The CPU instruction pointer.
|
||||
fn set_ip(&mut self, ip: u64);
|
||||
|
||||
/// Get the CPU Extended Feature Enable Register.
|
||||
fn efer(&self) -> u64;
|
||||
|
||||
/// Set the CPU Extended Feature Enable Register.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `efer` - The CPU EFER value.
|
||||
fn set_efer(&mut self, efer: u64);
|
||||
|
||||
/// Get the CPU flags.
|
||||
fn flags(&self) -> u64;
|
||||
|
||||
/// Set the CPU flags.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `flags` - The CPU flags
|
||||
fn set_flags(&mut self, flags: u64);
|
||||
|
||||
/// Get the CPU mode.
|
||||
fn mode(&self) -> Result<CpuMode, PlatformError>;
|
||||
|
||||
/// Translate a logical (segmented) address into a linear (virtual) one.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `segment` - Which segment to use for linearization
|
||||
/// * `logical_addr` - The logical address to be translated
|
||||
fn linearize(
|
||||
&self,
|
||||
segment: Register,
|
||||
logical_addr: u64,
|
||||
write: bool,
|
||||
) -> Result<u64, PlatformError> {
|
||||
let segment_register = self.read_segment(segment)?;
|
||||
let mode = self.mode()?;
|
||||
|
||||
match mode {
|
||||
CpuMode::Long => {
|
||||
// TODO Check that we got a canonical address.
|
||||
Ok(logical_addr
|
||||
.checked_add(segment_register.base)
|
||||
.ok_or_else(|| {
|
||||
PlatformError::InvalidAddress(anyhow!(
|
||||
"Logical address {:#x} can not be linearized with segment {:#x?}",
|
||||
logical_addr,
|
||||
segment_register
|
||||
))
|
||||
})?)
|
||||
}
|
||||
|
||||
CpuMode::Protected | CpuMode::Real => {
|
||||
let segment_type = segment_register.segment_type();
|
||||
|
||||
// Must not write to a read-only segment.
|
||||
if segment_type_ro(segment_type) && write {
|
||||
return Err(PlatformError::InvalidAddress(anyhow!(
|
||||
"Can not write to a read-only segment"
|
||||
)));
|
||||
}
|
||||
|
||||
let logical_addr = logical_addr & 0xffff_ffffu64;
|
||||
let mut segment_limit: u32 = if segment_register.granularity() != 0 {
|
||||
(segment_register.limit << 12) | 0xfff
|
||||
} else {
|
||||
segment_register.limit
|
||||
};
|
||||
|
||||
// Expand-down segment
|
||||
if segment_type_expand_down(segment_type) {
|
||||
if logical_addr >= segment_limit.into() {
|
||||
return Err(PlatformError::InvalidAddress(anyhow!(
|
||||
"{:#x} is off limits {:#x} (expand down)",
|
||||
logical_addr,
|
||||
segment_limit
|
||||
)));
|
||||
}
|
||||
|
||||
if segment_register.db() != 0 {
|
||||
segment_limit = 0xffffffff
|
||||
} else {
|
||||
segment_limit = 0xffff
|
||||
}
|
||||
}
|
||||
|
||||
if logical_addr > segment_limit.into() {
|
||||
return Err(PlatformError::InvalidAddress(anyhow!(
|
||||
"{:#x} is off limits {:#x}",
|
||||
logical_addr,
|
||||
segment_limit
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(logical_addr + segment_register.base)
|
||||
}
|
||||
|
||||
_ => Err(PlatformError::UnsupportedCpuMode(anyhow!("{:?}", mode))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const REGISTER_MASK_64: u64 = 0xffff_ffff_ffff_ffffu64;
|
||||
const REGISTER_MASK_32: u64 = 0xffff_ffffu64;
|
||||
const REGISTER_MASK_16: u64 = 0xffffu64;
|
||||
const REGISTER_MASK_8: u64 = 0xffu64;
|
||||
|
||||
macro_rules! set_reg {
|
||||
($reg:expr, $mask:expr, $value:expr) => {
|
||||
$reg = ($reg & $mask) | $value
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
/// A minimal, emulated CPU state.
|
||||
///
|
||||
/// Hypervisors needing x86 emulation can choose to either use their own
|
||||
/// CPU state structures and implement the CpuStateManager interface for it,
|
||||
/// or use `EmulatorCpuState`. The latter implies creating a new state
|
||||
/// `EmulatorCpuState` instance for each platform `cpu_state()` call, which
|
||||
/// might be less efficient.
|
||||
pub struct EmulatorCpuState {
|
||||
pub regs: StandardRegisters,
|
||||
pub sregs: SpecialRegisters,
|
||||
}
|
||||
|
||||
impl CpuStateManager for EmulatorCpuState {
|
||||
fn read_reg(&self, reg: Register) -> Result<u64, PlatformError> {
|
||||
let mut reg_value: u64 = match reg {
|
||||
Register::RAX | Register::EAX | Register::AX | Register::AL | Register::AH => {
|
||||
self.regs.rax
|
||||
}
|
||||
Register::RBX | Register::EBX | Register::BX | Register::BL | Register::BH => {
|
||||
self.regs.rbx
|
||||
}
|
||||
Register::RCX | Register::ECX | Register::CX | Register::CL | Register::CH => {
|
||||
self.regs.rcx
|
||||
}
|
||||
Register::RDX | Register::EDX | Register::DX | Register::DL | Register::DH => {
|
||||
self.regs.rdx
|
||||
}
|
||||
Register::RSP | Register::ESP | Register::SP => self.regs.rsp,
|
||||
Register::RBP | Register::EBP | Register::BP => self.regs.rbp,
|
||||
Register::RSI | Register::ESI | Register::SI | Register::SIL => self.regs.rsi,
|
||||
Register::RDI | Register::EDI | Register::DI | Register::DIL => self.regs.rdi,
|
||||
Register::R8 | Register::R8D | Register::R8W | Register::R8L => self.regs.r8,
|
||||
Register::R9 | Register::R9D | Register::R9W | Register::R9L => self.regs.r9,
|
||||
Register::R10 | Register::R10D | Register::R10W | Register::R10L => self.regs.r10,
|
||||
Register::R11 | Register::R11D | Register::R11W | Register::R11L => self.regs.r11,
|
||||
Register::R12 | Register::R12D | Register::R12W | Register::R12L => self.regs.r12,
|
||||
Register::R13 | Register::R13D | Register::R13W | Register::R13L => self.regs.r13,
|
||||
Register::R14 | Register::R14D | Register::R14W | Register::R14L => self.regs.r14,
|
||||
Register::R15 | Register::R15D | Register::R15W | Register::R15L => self.regs.r15,
|
||||
Register::CR0 => self.sregs.cr0,
|
||||
Register::CR2 => self.sregs.cr2,
|
||||
Register::CR3 => self.sregs.cr3,
|
||||
Register::CR4 => self.sregs.cr4,
|
||||
Register::CR8 => self.sregs.cr8,
|
||||
|
||||
r => {
|
||||
return Err(PlatformError::InvalidRegister(anyhow!(
|
||||
"read_reg invalid GPR {:?}",
|
||||
r
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
reg_value = if reg.is_gpr64() || reg.is_cr() {
|
||||
reg_value
|
||||
} else if reg.is_gpr32() {
|
||||
reg_value & REGISTER_MASK_32
|
||||
} else if reg.is_gpr16() {
|
||||
reg_value & REGISTER_MASK_16
|
||||
} else if reg.is_gpr8() {
|
||||
if reg == Register::AH
|
||||
|| reg == Register::BH
|
||||
|| reg == Register::CH
|
||||
|| reg == Register::DH
|
||||
{
|
||||
(reg_value >> 8) & REGISTER_MASK_8
|
||||
} else {
|
||||
reg_value & REGISTER_MASK_8
|
||||
}
|
||||
} else {
|
||||
return Err(PlatformError::InvalidRegister(anyhow!(
|
||||
"read_reg invalid GPR {:?}",
|
||||
reg
|
||||
)));
|
||||
};
|
||||
|
||||
debug!("Register read: {:#x} from {:?}", reg_value, reg);
|
||||
|
||||
Ok(reg_value)
|
||||
}
|
||||
|
||||
fn write_reg(&mut self, reg: Register, val: u64) -> Result<(), PlatformError> {
|
||||
debug!("Register write: {:#x} to {:?}", val, reg);
|
||||
|
||||
// SDM Vol 1 - 3.4.1.1
|
||||
//
|
||||
// 8-bit and 16-bit operands generate an 8-bit or 16-bit result.
|
||||
// The upper 56 bits or 48 bits (respectively) of the destination
|
||||
// general-purpose register are not modified by the operation.
|
||||
let (reg_value, mask): (u64, u64) = if reg.is_gpr64() || reg.is_cr() {
|
||||
(val, !REGISTER_MASK_64)
|
||||
} else if reg.is_gpr32() {
|
||||
(val & REGISTER_MASK_32, !REGISTER_MASK_64)
|
||||
} else if reg.is_gpr16() {
|
||||
(val & REGISTER_MASK_16, !REGISTER_MASK_16)
|
||||
} else if reg.is_gpr8() {
|
||||
if reg == Register::AH
|
||||
|| reg == Register::BH
|
||||
|| reg == Register::CH
|
||||
|| reg == Register::DH
|
||||
{
|
||||
((val & REGISTER_MASK_8) << 8, !(REGISTER_MASK_8 << 8))
|
||||
} else {
|
||||
(val & REGISTER_MASK_8, !REGISTER_MASK_8)
|
||||
}
|
||||
} else {
|
||||
return Err(PlatformError::InvalidRegister(anyhow!(
|
||||
"write_reg invalid register {:?}",
|
||||
reg
|
||||
)));
|
||||
};
|
||||
|
||||
match reg {
|
||||
Register::RAX | Register::EAX | Register::AX | Register::AL | Register::AH => {
|
||||
set_reg!(self.regs.rax, mask, reg_value);
|
||||
}
|
||||
Register::RBX | Register::EBX | Register::BX | Register::BL | Register::BH => {
|
||||
set_reg!(self.regs.rbx, mask, reg_value);
|
||||
}
|
||||
Register::RCX | Register::ECX | Register::CX | Register::CL | Register::CH => {
|
||||
set_reg!(self.regs.rcx, mask, reg_value);
|
||||
}
|
||||
Register::RDX | Register::EDX | Register::DX | Register::DL | Register::DH => {
|
||||
set_reg!(self.regs.rdx, mask, reg_value);
|
||||
}
|
||||
Register::RSP | Register::ESP | Register::SP => {
|
||||
set_reg!(self.regs.rsp, mask, reg_value)
|
||||
}
|
||||
Register::RBP | Register::EBP | Register::BP => {
|
||||
set_reg!(self.regs.rbp, mask, reg_value)
|
||||
}
|
||||
Register::RSI | Register::ESI | Register::SI | Register::SIL => {
|
||||
set_reg!(self.regs.rsi, mask, reg_value)
|
||||
}
|
||||
Register::RDI | Register::EDI | Register::DI | Register::DIL => {
|
||||
set_reg!(self.regs.rdi, mask, reg_value)
|
||||
}
|
||||
Register::R8 | Register::R8D | Register::R8W | Register::R8L => {
|
||||
set_reg!(self.regs.r8, mask, reg_value)
|
||||
}
|
||||
Register::R9 | Register::R9D | Register::R9W | Register::R9L => {
|
||||
set_reg!(self.regs.r9, mask, reg_value)
|
||||
}
|
||||
Register::R10 | Register::R10D | Register::R10W | Register::R10L => {
|
||||
set_reg!(self.regs.r10, mask, reg_value)
|
||||
}
|
||||
Register::R11 | Register::R11D | Register::R11W | Register::R11L => {
|
||||
set_reg!(self.regs.r11, mask, reg_value)
|
||||
}
|
||||
Register::R12 | Register::R12D | Register::R12W | Register::R12L => {
|
||||
set_reg!(self.regs.r12, mask, reg_value)
|
||||
}
|
||||
Register::R13 | Register::R13D | Register::R13W | Register::R13L => {
|
||||
set_reg!(self.regs.r13, mask, reg_value)
|
||||
}
|
||||
Register::R14 | Register::R14D | Register::R14W | Register::R14L => {
|
||||
set_reg!(self.regs.r14, mask, reg_value)
|
||||
}
|
||||
Register::R15 | Register::R15D | Register::R15W | Register::R15L => {
|
||||
set_reg!(self.regs.r15, mask, reg_value)
|
||||
}
|
||||
Register::CR0 => set_reg!(self.sregs.cr0, mask, reg_value),
|
||||
Register::CR2 => set_reg!(self.sregs.cr2, mask, reg_value),
|
||||
Register::CR3 => set_reg!(self.sregs.cr3, mask, reg_value),
|
||||
Register::CR4 => set_reg!(self.sregs.cr4, mask, reg_value),
|
||||
Register::CR8 => set_reg!(self.sregs.cr8, mask, reg_value),
|
||||
_ => {
|
||||
return Err(PlatformError::InvalidRegister(anyhow!(
|
||||
"write_reg invalid register {:?}",
|
||||
reg
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_segment(&self, reg: Register) -> Result<SegmentRegister, PlatformError> {
|
||||
if !reg.is_segment_register() {
|
||||
return Err(PlatformError::InvalidRegister(anyhow!(
|
||||
"read_segment {:?} is not a segment register",
|
||||
reg
|
||||
)));
|
||||
}
|
||||
|
||||
match reg {
|
||||
Register::CS => Ok(self.sregs.cs),
|
||||
Register::DS => Ok(self.sregs.ds),
|
||||
Register::ES => Ok(self.sregs.es),
|
||||
Register::FS => Ok(self.sregs.fs),
|
||||
Register::GS => Ok(self.sregs.gs),
|
||||
Register::SS => Ok(self.sregs.ss),
|
||||
r => Err(PlatformError::InvalidRegister(anyhow!(
|
||||
"read_segment invalid register {:?}",
|
||||
r
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_segment(
|
||||
&mut self,
|
||||
reg: Register,
|
||||
segment_register: SegmentRegister,
|
||||
) -> Result<(), PlatformError> {
|
||||
if !reg.is_segment_register() {
|
||||
return Err(PlatformError::InvalidRegister(anyhow!("{:?}", reg)));
|
||||
}
|
||||
|
||||
match reg {
|
||||
Register::CS => self.sregs.cs = segment_register,
|
||||
Register::DS => self.sregs.ds = segment_register,
|
||||
Register::ES => self.sregs.es = segment_register,
|
||||
Register::FS => self.sregs.fs = segment_register,
|
||||
Register::GS => self.sregs.gs = segment_register,
|
||||
Register::SS => self.sregs.ss = segment_register,
|
||||
r => return Err(PlatformError::InvalidRegister(anyhow!("{:?}", r))),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ip(&self) -> u64 {
|
||||
self.regs.rip
|
||||
}
|
||||
|
||||
fn set_ip(&mut self, ip: u64) {
|
||||
self.regs.rip = ip;
|
||||
}
|
||||
|
||||
fn efer(&self) -> u64 {
|
||||
self.sregs.efer
|
||||
}
|
||||
|
||||
fn set_efer(&mut self, efer: u64) {
|
||||
self.sregs.efer = efer
|
||||
}
|
||||
|
||||
fn flags(&self) -> u64 {
|
||||
self.regs.rflags
|
||||
}
|
||||
|
||||
fn set_flags(&mut self, flags: u64) {
|
||||
self.regs.rflags = flags;
|
||||
}
|
||||
|
||||
fn mode(&self) -> Result<CpuMode, PlatformError> {
|
||||
let efer = self.efer();
|
||||
let cr0 = self.read_reg(Register::CR0)?;
|
||||
let mut mode = CpuMode::Real;
|
||||
|
||||
if (cr0 & CR0_PE) == CR0_PE {
|
||||
mode = CpuMode::Protected;
|
||||
}
|
||||
|
||||
if (efer & EFER_LMA) == EFER_LMA {
|
||||
if mode != CpuMode::Protected {
|
||||
return Err(PlatformError::InvalidState(anyhow!(
|
||||
"Protection must be enabled in long mode"
|
||||
)));
|
||||
}
|
||||
|
||||
mode = CpuMode::Long;
|
||||
}
|
||||
|
||||
Ok(mode)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Emulator<'a, T: CpuStateManager> {
|
||||
platform: &'a mut dyn PlatformEmulator<CpuState = T>,
|
||||
}
|
||||
|
||||
// Reduce repetition, see its invocation in get_handler().
|
||||
macro_rules! gen_handler_match {
|
||||
($value: ident, $( ($module:ident, $code:ident) ),* ) => {
|
||||
match $value {
|
||||
$(
|
||||
Code::$code => Some(Box::new($module::$code)),
|
||||
)*
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
||||
pub fn new(platform: &mut dyn PlatformEmulator<CpuState = T>) -> Emulator<T> {
|
||||
Emulator { platform }
|
||||
}
|
||||
|
||||
fn get_handler(code: Code) -> Option<Box<dyn InstructionHandler<T>>> {
|
||||
let handler: Option<Box<dyn InstructionHandler<T>>> = gen_handler_match!(
|
||||
code,
|
||||
// CMP
|
||||
(cmp, Cmp_rm32_r32),
|
||||
(cmp, Cmp_rm8_r8),
|
||||
(cmp, Cmp_rm32_imm8),
|
||||
(cmp, Cmp_rm64_r64),
|
||||
// MOV
|
||||
(mov, Mov_r8_rm8),
|
||||
(mov, Mov_r8_imm8),
|
||||
(mov, Mov_r16_imm16),
|
||||
(mov, Mov_r16_rm16),
|
||||
(mov, Mov_r32_imm32),
|
||||
(mov, Mov_r32_rm32),
|
||||
(mov, Mov_r64_imm64),
|
||||
(mov, Mov_r64_rm64),
|
||||
(mov, Mov_rm8_imm8),
|
||||
(mov, Mov_rm8_r8),
|
||||
(mov, Mov_rm16_imm16),
|
||||
(mov, Mov_rm16_r16),
|
||||
(mov, Mov_rm32_imm32),
|
||||
(mov, Mov_rm32_r32),
|
||||
(mov, Mov_rm64_imm32),
|
||||
(mov, Mov_rm64_r64)
|
||||
);
|
||||
|
||||
handler
|
||||
}
|
||||
|
||||
fn emulate_insn_stream(
|
||||
&mut self,
|
||||
cpu_id: usize,
|
||||
insn_stream: &[u8],
|
||||
num_insn: Option<usize>,
|
||||
) -> EmulationResult<T, Exception> {
|
||||
let mut state = self
|
||||
.platform
|
||||
.cpu_state(cpu_id)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
let mut decoder = Decoder::new(64, insn_stream, DecoderOptions::NONE);
|
||||
let mut insn = Instruction::default();
|
||||
let mut num_insn_emulated: usize = 0;
|
||||
let mut fetched_insn_stream: [u8; 16] = [0; 16];
|
||||
let mut last_decoded_ip: u64 = state.ip();
|
||||
let mut stop_emulation: bool = false;
|
||||
|
||||
decoder.set_ip(state.ip());
|
||||
|
||||
while decoder.can_decode() && !stop_emulation {
|
||||
decoder.decode_out(&mut insn);
|
||||
|
||||
if decoder.last_error() == DecoderError::NoMoreBytes {
|
||||
// The decoder is missing some bytes to decode the current
|
||||
// instruction, for example because the instruction stream
|
||||
// crosses a page boundary.
|
||||
// We fetch 16 more bytes from the instruction segment,
|
||||
// decode and emulate the failing instruction and terminate
|
||||
// the emulation loop.
|
||||
debug!(
|
||||
"Fetching {} bytes from {:#x}",
|
||||
fetched_insn_stream.len(),
|
||||
last_decoded_ip
|
||||
);
|
||||
|
||||
// fetched_insn_stream is 16 bytes long, enough to contain
|
||||
// any complete x86 instruction.
|
||||
self.platform
|
||||
.fetch(last_decoded_ip, &mut fetched_insn_stream)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
debug!("Fetched {:x?}", fetched_insn_stream);
|
||||
|
||||
// Once we have the new stream, we must create a new decoder
|
||||
// and emulate one last instruction from the last decoded IP.
|
||||
decoder = Decoder::new(64, &fetched_insn_stream, DecoderOptions::NONE);
|
||||
decoder.decode_out(&mut insn);
|
||||
if decoder.last_error() != DecoderError::None {
|
||||
return Err(EmulationError::InstructionFetchingError(anyhow!(
|
||||
"{:#x?}",
|
||||
insn_format!(insn)
|
||||
)));
|
||||
}
|
||||
|
||||
stop_emulation = true;
|
||||
}
|
||||
|
||||
// Emulate the decoded instruction
|
||||
Emulator::get_handler(insn.code())
|
||||
.ok_or_else(|| {
|
||||
EmulationError::UnsupportedInstruction(anyhow!(
|
||||
"{:#x?} {:?} {:?}",
|
||||
insn_format!(insn),
|
||||
insn.mnemonic(),
|
||||
insn.code()
|
||||
))
|
||||
})?
|
||||
.emulate(&insn, &mut state, self.platform)
|
||||
.context(anyhow!("Failed to emulate {:#x?}", insn_format!(insn)))?;
|
||||
|
||||
last_decoded_ip = decoder.ip();
|
||||
num_insn_emulated += 1;
|
||||
|
||||
if let Some(num_insn) = num_insn {
|
||||
if num_insn_emulated >= num_insn {
|
||||
// Exit the decoding loop, do not decode the next instruction.
|
||||
stop_emulation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.set_ip(decoder.ip());
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
/// Emulate all instructions from the instructions stream.
|
||||
pub fn emulate(&mut self, cpu_id: usize, insn_stream: &[u8]) -> EmulationResult<T, Exception> {
|
||||
self.emulate_insn_stream(cpu_id, insn_stream, None)
|
||||
}
|
||||
|
||||
/// Only emulate the first instruction from the stream.
|
||||
///
|
||||
/// This is useful for cases where we get readahead instruction stream
|
||||
/// but implicitly must only emulate the first instruction, and then return
|
||||
/// to the guest.
|
||||
pub fn emulate_first_insn(
|
||||
&mut self,
|
||||
cpu_id: usize,
|
||||
insn_stream: &[u8],
|
||||
) -> EmulationResult<T, Exception> {
|
||||
self.emulate_insn_stream(cpu_id, insn_stream, Some(1))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock_vmm {
|
||||
#![allow(unused_mut)]
|
||||
|
||||
extern crate env_logger;
|
||||
|
||||
use super::*;
|
||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
||||
use crate::arch::x86::emulator::{Emulator, EmulatorCpuState as CpuState};
|
||||
use crate::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
||||
use crate::arch::x86::Exception;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MockVMM {
|
||||
memory: Vec<u8>,
|
||||
state: Arc<Mutex<CpuState>>,
|
||||
}
|
||||
|
||||
unsafe impl Sync for MockVMM {}
|
||||
|
||||
pub type MockResult = Result<(), EmulationError<Exception>>;
|
||||
|
||||
impl MockVMM {
|
||||
pub fn new(ip: u64, regs: Vec<(Register, u64)>, memory: Option<(u64, &[u8])>) -> MockVMM {
|
||||
let _ = env_logger::try_init();
|
||||
let cs_reg = segment_from_gdt(gdt_entry(0xc09b, 0, 0xffffffff), 1);
|
||||
let ds_reg = segment_from_gdt(gdt_entry(0xc093, 0, 0xffffffff), 2);
|
||||
let mut initial_state = CpuState::default();
|
||||
initial_state.set_ip(ip);
|
||||
initial_state.write_segment(Register::CS, cs_reg).unwrap();
|
||||
initial_state.write_segment(Register::DS, ds_reg).unwrap();
|
||||
for (reg, value) in regs {
|
||||
initial_state.write_reg(reg, value).unwrap();
|
||||
}
|
||||
|
||||
let mut vmm = MockVMM {
|
||||
memory: vec![0; 8192],
|
||||
state: Arc::new(Mutex::new(initial_state)),
|
||||
};
|
||||
|
||||
if let Some(mem) = memory {
|
||||
vmm.write_memory(mem.0, &mem.1).unwrap();
|
||||
}
|
||||
|
||||
vmm
|
||||
}
|
||||
|
||||
pub fn emulate_insn(
|
||||
&mut self,
|
||||
cpu_id: usize,
|
||||
insn: &[u8],
|
||||
num_insn: Option<usize>,
|
||||
) -> MockResult {
|
||||
let ip = self.cpu_state(cpu_id).unwrap().ip();
|
||||
let mut emulator = Emulator::new(self);
|
||||
|
||||
let new_state = emulator.emulate_insn_stream(cpu_id, &insn, num_insn)?;
|
||||
if num_insn.is_none() {
|
||||
assert_eq!(ip + insn.len() as u64, new_state.ip());
|
||||
}
|
||||
|
||||
self.set_cpu_state(cpu_id, new_state).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn emulate_first_insn(&mut self, cpu_id: usize, insn: &[u8]) -> MockResult {
|
||||
self.emulate_insn(cpu_id, insn, Some(1))
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformEmulator for MockVMM {
|
||||
type CpuState = CpuState;
|
||||
|
||||
fn read_memory(&self, gva: u64, data: &mut [u8]) -> Result<(), PlatformError> {
|
||||
debug!(
|
||||
"Memory read {} bytes from [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gva
|
||||
);
|
||||
data.copy_from_slice(&self.memory[gva as usize..gva as usize + data.len()]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, gva: u64, data: &[u8]) -> Result<(), PlatformError> {
|
||||
debug!(
|
||||
"Memory write {} bytes at [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gva
|
||||
);
|
||||
self.memory[gva as usize..gva as usize + data.len()].copy_from_slice(data);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cpu_state(&self, _cpu_id: usize) -> Result<CpuState, PlatformError> {
|
||||
Ok(self.state.lock().unwrap().clone())
|
||||
}
|
||||
|
||||
fn set_cpu_state(
|
||||
&self,
|
||||
_cpu_id: usize,
|
||||
state: Self::CpuState,
|
||||
) -> Result<(), PlatformError> {
|
||||
*self.state.lock().unwrap() = state;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gva_to_gpa(&self, gva: u64) -> Result<u64, PlatformError> {
|
||||
Ok(gva)
|
||||
}
|
||||
|
||||
fn fetch(&self, ip: u64, instruction_bytes: &mut [u8]) -> Result<(), PlatformError> {
|
||||
let rip = self
|
||||
.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.linearize(Register::CS, ip, false)?;
|
||||
self.read_memory(rip, instruction_bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(unused_mut)]
|
||||
use super::*;
|
||||
use crate::arch::x86::emulator::mock_vmm::*;
|
||||
|
||||
#[test]
|
||||
// Emulate truncated instruction stream, which should cause a fetch.
|
||||
//
|
||||
// mov rax, 0x1000
|
||||
// Test with a first instruction truncated.
|
||||
fn test_fetch_first_instruction() -> MockResult {
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
// Code at IP
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
// Padding
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // Padding is all zeroes
|
||||
// Data at IP + 0x10 (0x1234567812345678 in LE)
|
||||
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
|
||||
];
|
||||
let insn = [
|
||||
// First instruction is truncated
|
||||
0x48, 0xc7, 0xc0, 0x00, // mov rax, 0x1000 -- Missing bytes: 0x00, 0x10, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((ip, &memory)));
|
||||
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
|
||||
|
||||
let rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, ip);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Emulate truncated instruction stream, which should cause a fetch.
|
||||
//
|
||||
// mov rax, 0x1000
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
// Test with a 2nd instruction truncated.
|
||||
fn test_fetch_second_instruction() -> MockResult {
|
||||
let target_rax: u64 = 0x1234567812345678;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
// Code at IP
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
// Padding
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // Padding is all zeroes
|
||||
// Data at IP + 0x10 (0x1234567812345678 in LE)
|
||||
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
|
||||
];
|
||||
let insn = [
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||
0x48, 0x8b, // Truncated mov rbx, qword ptr [rax+10h] -- missing [0x58, 0x10]
|
||||
];
|
||||
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((ip, &memory)));
|
||||
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
|
||||
|
||||
let rbx: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RBX)
|
||||
.unwrap();
|
||||
assert_eq!(rbx, target_rax);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Emulate truncated instruction stream, which should cause a fetch.
|
||||
//
|
||||
// mov rax, 0x1000
|
||||
// Test with a first instruction truncated and a bad fetched instruction.
|
||||
// Verify that the instruction emulation returns an error.
|
||||
fn test_fetch_bad_insn() -> MockResult {
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
// Code at IP
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff,
|
||||
];
|
||||
let insn = [
|
||||
// First instruction is truncated
|
||||
0x48, 0xc7, 0xc0, 0x00, // mov rax, 0x1000 -- Missing bytes: 0x00, 0x10, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((ip, &memory)));
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
// For GDT details see arch/x86/include/asm/segment.h
|
||||
use hypervisor::x86_64::SegmentRegister;
|
||||
use crate::x86_64::SegmentRegister;
|
||||
|
||||
/// Constructor for a conventional segment GDT (or LDT) entry. Derived from the kernel's segment.h.
|
||||
pub fn gdt_entry(flags: u16, base: u32, limit: u32) -> u64 {
|
||||
@@ -11,6 +11,8 @@
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
|
||||
pub mod gdt;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_snake_case)]
|
||||
@@ -28,9 +30,95 @@
|
||||
)]
|
||||
pub mod msr_index;
|
||||
|
||||
pub mod emulator;
|
||||
|
||||
// MTRR constants
|
||||
pub const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11
|
||||
pub const MTRR_MEM_TYPE_WB: u64 = 0x6;
|
||||
|
||||
// IOAPIC pins
|
||||
pub const NUM_IOAPIC_PINS: usize = 24;
|
||||
|
||||
// X86 Exceptions
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Exception {
|
||||
DE = 1, // Divide Error
|
||||
DB = 2, // Debug Exception
|
||||
BP = 3, // Breakpoint
|
||||
OF = 4, // Overflow
|
||||
BR = 5, // BOUND Range Exceeded
|
||||
UD = 6, // Invalid/Undefined Opcode
|
||||
NM = 7, // No Math Coprocessor
|
||||
DF = 8, // Double Fault
|
||||
TS = 10, // Invalid TSS
|
||||
NP = 11, // Segment Not Present
|
||||
SS = 12, // Stack Segment Fault
|
||||
GP = 13, // General Protection
|
||||
PF = 14, // Page Fault
|
||||
MF = 16, // Math Fault
|
||||
AC = 17, // Alignment Check
|
||||
MC = 18, // Machine Check
|
||||
XM = 19, // SIMD Floating-Point Exception
|
||||
VE = 20, // Virtualization Exception
|
||||
CP = 21, // Control Protection Exception
|
||||
}
|
||||
|
||||
pub mod regs;
|
||||
|
||||
// Abstracted segment register ops.
|
||||
// Each x86 hypervisor should implement those.
|
||||
pub trait SegmentRegisterOps {
|
||||
// Segment type
|
||||
fn segment_type(&self) -> u8;
|
||||
fn set_segment_type(&mut self, val: u8);
|
||||
|
||||
// Descriptor Privilege Level (DPL)
|
||||
fn dpl(&self) -> u8;
|
||||
fn set_dpl(&mut self, val: u8);
|
||||
|
||||
// Granularity
|
||||
fn granularity(&self) -> u8;
|
||||
fn set_granularity(&mut self, val: u8);
|
||||
|
||||
// Memory Presence
|
||||
fn present(&self) -> u8;
|
||||
fn set_present(&mut self, val: u8);
|
||||
|
||||
// Long mode
|
||||
fn long(&self) -> u8;
|
||||
fn set_long(&mut self, val: u8);
|
||||
|
||||
// Available for system use (AVL)
|
||||
fn avl(&self) -> u8;
|
||||
fn set_avl(&mut self, val: u8);
|
||||
|
||||
// Descriptor type (System or code/data)
|
||||
fn desc_type(&self) -> u8;
|
||||
fn set_desc_type(&mut self, val: u8);
|
||||
|
||||
// D/B
|
||||
fn db(&self) -> u8;
|
||||
fn set_db(&mut self, val: u8);
|
||||
}
|
||||
|
||||
// Code segment
|
||||
pub const CODE_SEGMENT_TYPE: u8 = 0x8;
|
||||
|
||||
// Read/Write or Read/Exec segment
|
||||
pub const RWRX_SEGMENT_TYPE: u8 = 0x2;
|
||||
|
||||
// Expand down segment
|
||||
pub const EXPAND_DOWN_SEGMENT_TYPE: u8 = 0x4;
|
||||
|
||||
pub fn segment_type_code(t: u8) -> bool {
|
||||
t & CODE_SEGMENT_TYPE != 0
|
||||
}
|
||||
|
||||
pub fn segment_type_ro(t: u8) -> bool {
|
||||
t & !RWRX_SEGMENT_TYPE == 0
|
||||
}
|
||||
|
||||
pub fn segment_type_expand_down(t: u8) -> bool {
|
||||
!segment_type_code(t) && (t & EXPAND_DOWN_SEGMENT_TYPE != 0)
|
||||
}
|
||||
|
||||
32
hypervisor/src/arch/x86/regs.rs
Normal file
32
hypervisor/src/arch/x86/regs.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// EFER (technically not a register) bits
|
||||
pub const EFER_LMA: u64 = 0x400;
|
||||
pub const EFER_LME: u64 = 0x100;
|
||||
|
||||
// CR0 bits
|
||||
pub const CR0_PE: u64 = 0x1;
|
||||
pub const CR0_PG: u64 = 0x80000000;
|
||||
|
||||
// CR4 bits
|
||||
pub const CR4_PAE: u64 = 0x20;
|
||||
pub const CR4_LA57: u64 = 0x1000;
|
||||
|
||||
// RFlags bits
|
||||
pub const CF_SHIFT: usize = 0;
|
||||
pub const PF_SHIFT: usize = 2;
|
||||
pub const AF_SHIFT: usize = 4;
|
||||
pub const ZF_SHIFT: usize = 6;
|
||||
pub const SF_SHIFT: usize = 7;
|
||||
pub const OF_SHIFT: usize = 11;
|
||||
|
||||
pub const CF: u64 = 1 << CF_SHIFT;
|
||||
pub const PF: u64 = 1 << PF_SHIFT;
|
||||
pub const AF: u64 = 1 << AF_SHIFT;
|
||||
pub const ZF: u64 = 1 << ZF_SHIFT;
|
||||
pub const SF: u64 = 1 << SF_SHIFT;
|
||||
pub const OF: u64 = 1 << OF_SHIFT;
|
||||
@@ -12,13 +12,17 @@
|
||||
use crate::aarch64::VcpuInit;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::aarch64::{RegList, Register, StandardRegisters};
|
||||
use crate::{CpuState, MpState};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::x86_64::{CpuId, LapicState};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::x86_64::{
|
||||
CpuId, ExtendedControlRegisters, FpuState, LapicState, MsrEntries, SpecialRegisters,
|
||||
StandardRegisters, VcpuEvents, Xsave,
|
||||
ExtendedControlRegisters, FpuState, MsrEntries, SpecialRegisters, StandardRegisters, VcpuEvents,
|
||||
};
|
||||
use crate::CpuState;
|
||||
#[cfg(feature = "kvm")]
|
||||
use crate::MpState;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::Xsave;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -151,6 +155,20 @@ pub enum HypervisorCpuError {
|
||||
#[error("Failed to notify guest its clock was paused: {0}")]
|
||||
NotifyGuestClockPaused(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting debug register error
|
||||
///
|
||||
#[error("Failed to set debug registers: {0}")]
|
||||
SetDebugRegs(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting debug register error
|
||||
///
|
||||
#[error("Failed to get debug registers: {0}")]
|
||||
GetDebugRegs(#[source] anyhow::Error),
|
||||
///
|
||||
/// Write to Guest Mem
|
||||
///
|
||||
#[error("Failed to write to Guest Mem at: {0}")]
|
||||
GuestMemWrite(#[source] anyhow::Error),
|
||||
/// Enabling HyperV SynIC error
|
||||
///
|
||||
#[error("Failed to enable HyperV SynIC")]
|
||||
@@ -194,6 +212,7 @@ pub enum VmExit<'a> {
|
||||
MmioWrite(u64 /* address */, &'a [u8]),
|
||||
Ignore,
|
||||
Reset,
|
||||
Shutdown,
|
||||
Hyperv,
|
||||
}
|
||||
|
||||
@@ -271,10 +290,12 @@ pub trait Vcpu: Send + Sync {
|
||||
/// Setup the model-specific registers (MSR) for this vCPU.
|
||||
///
|
||||
fn set_msrs(&self, msrs: &MsrEntries) -> Result<usize>;
|
||||
#[cfg(feature = "kvm")]
|
||||
///
|
||||
/// Returns the vcpu's current "multiprocessing state".
|
||||
///
|
||||
fn get_mp_state(&self) -> Result<MpState>;
|
||||
#[cfg(feature = "kvm")]
|
||||
///
|
||||
/// Sets the vcpu's current "multiprocessing state".
|
||||
///
|
||||
@@ -311,7 +332,7 @@ pub trait Vcpu: Send + Sync {
|
||||
/// of the vcpu.
|
||||
///
|
||||
fn set_vcpu_events(&self, events: &VcpuEvents) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
///
|
||||
/// Let the guest know that it has been paused, which prevents from
|
||||
/// potential soft lockups when being resumed.
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
//
|
||||
use crate::vm::Vm;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::x86_64::{CpuId, MsrList};
|
||||
use crate::x86_64::CpuId;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::x86_64::MsrList;
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
use kvm_ioctls::Cap;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -20,6 +22,11 @@ use thiserror::Error;
|
||||
///
|
||||
///
|
||||
pub enum HypervisorError {
|
||||
///
|
||||
/// hypervisor creation error
|
||||
///
|
||||
#[error("Failed to create the hypervisor: {0}")]
|
||||
HypervisorCreate(#[source] anyhow::Error),
|
||||
///
|
||||
/// Vm creation failure
|
||||
///
|
||||
@@ -78,19 +85,22 @@ pub trait Hypervisor: Send + Sync {
|
||||
/// Return a hypervisor-agnostic Vm trait object
|
||||
///
|
||||
fn create_vm(&self) -> Result<Arc<dyn Vm>>;
|
||||
#[cfg(feature = "kvm")]
|
||||
///
|
||||
/// Returns the size of the memory mapping required to use the vcpu's structures
|
||||
///
|
||||
fn get_vcpu_mmap_size(&self) -> Result<usize>;
|
||||
#[cfg(feature = "kvm")]
|
||||
///
|
||||
/// Gets the recommended maximum number of VCPUs per VM.
|
||||
///
|
||||
fn get_max_vcpus(&self) -> Result<usize>;
|
||||
#[cfg(feature = "kvm")]
|
||||
///
|
||||
/// Gets the recommended number of VCPUs per VM.
|
||||
///
|
||||
fn get_nr_vcpus(&self) -> Result<usize>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
///
|
||||
/// Checks if a particular `Cap` is available.
|
||||
///
|
||||
@@ -100,6 +110,7 @@ pub trait Hypervisor: Send + Sync {
|
||||
/// Get the supported CpuID
|
||||
///
|
||||
fn get_cpuid(&self) -> Result<CpuId>;
|
||||
#[cfg(not(feature = "mshv"))]
|
||||
///
|
||||
/// Check particular extensions if any
|
||||
///
|
||||
|
||||
@@ -19,7 +19,6 @@ use crate::hypervisor;
|
||||
use crate::vm::{self, VmmOps};
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::{arm64_core_reg_id, offset__of};
|
||||
use arc_swap::ArcSwapOption;
|
||||
use kvm_ioctls::{NoDatamatch, VcpuFd, VmFd};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
@@ -70,7 +69,8 @@ use std::mem;
|
||||
pub use kvm_bindings;
|
||||
pub use kvm_bindings::{
|
||||
kvm_create_device, kvm_device_type_KVM_DEV_TYPE_VFIO, kvm_irq_routing, kvm_irq_routing_entry,
|
||||
kvm_userspace_memory_region, KVM_IRQ_ROUTING_MSI, KVM_MEM_READONLY, KVM_MSI_VALID_DEVID,
|
||||
kvm_userspace_memory_region, KVM_IRQ_ROUTING_MSI, KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY,
|
||||
KVM_MSI_VALID_DEVID,
|
||||
};
|
||||
pub use kvm_ioctls;
|
||||
pub use kvm_ioctls::{Cap, Kvm};
|
||||
@@ -96,7 +96,6 @@ pub struct KvmVm {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
msrs: MsrEntries,
|
||||
state: KvmVmState,
|
||||
vmmops: ArcSwapOption<Box<dyn vm::VmmOps>>,
|
||||
}
|
||||
|
||||
// Returns a `Vec<T>` with a size in bytes at least as large as `size_in_bytes`.
|
||||
@@ -176,7 +175,11 @@ impl vm::Vm for KvmVm {
|
||||
///
|
||||
/// Creates a VcpuFd object from a vcpu RawFd.
|
||||
///
|
||||
fn create_vcpu(&self, id: u8) -> vm::Result<Arc<dyn cpu::Vcpu>> {
|
||||
fn create_vcpu(
|
||||
&self,
|
||||
id: u8,
|
||||
vmmops: Option<Arc<Box<dyn VmmOps>>>,
|
||||
) -> vm::Result<Arc<dyn cpu::Vcpu>> {
|
||||
let vc = self
|
||||
.fd
|
||||
.create_vcpu(id)
|
||||
@@ -185,7 +188,7 @@ impl vm::Vm for KvmVm {
|
||||
fd: vc,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
msrs: self.msrs.clone(),
|
||||
vmmops: self.vmmops.clone(),
|
||||
vmmops,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
hyperv_synic: AtomicBool::new(false),
|
||||
};
|
||||
@@ -255,13 +258,19 @@ impl vm::Vm for KvmVm {
|
||||
memory_size: u64,
|
||||
userspace_addr: u64,
|
||||
readonly: bool,
|
||||
log_dirty_pages: bool,
|
||||
) -> MemoryRegion {
|
||||
MemoryRegion {
|
||||
slot,
|
||||
guest_phys_addr,
|
||||
memory_size,
|
||||
userspace_addr,
|
||||
flags: if readonly { KVM_MEM_READONLY } else { 0 },
|
||||
flags: if readonly { KVM_MEM_READONLY } else { 0 }
|
||||
| if log_dirty_pages {
|
||||
KVM_MEM_LOG_DIRTY_PAGES
|
||||
} else {
|
||||
0
|
||||
},
|
||||
}
|
||||
}
|
||||
///
|
||||
@@ -356,11 +365,12 @@ impl vm::Vm for KvmVm {
|
||||
}
|
||||
|
||||
///
|
||||
/// Set the VmmOps interface
|
||||
/// Get dirty pages bitmap (one bit per page)
|
||||
///
|
||||
fn set_vmmops(&self, vmmops: Box<dyn VmmOps>) -> vm::Result<()> {
|
||||
self.vmmops.store(Some(Arc::new(vmmops)));
|
||||
Ok(())
|
||||
fn get_dirty_log(&self, slot: u32, memory_size: u64) -> vm::Result<Vec<u64>> {
|
||||
self.fd
|
||||
.get_dirty_log(slot, memory_size as usize)
|
||||
.map_err(|e| vm::HypervisorVmError::GetDirtyLog(e.into()))
|
||||
}
|
||||
}
|
||||
/// Wrapper over KVM system ioctls.
|
||||
@@ -439,7 +449,6 @@ impl hypervisor::Hypervisor for KvmHypervisor {
|
||||
fd: vm_fd,
|
||||
msrs,
|
||||
state: VmState {},
|
||||
vmmops: ArcSwapOption::from(None),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -448,7 +457,6 @@ impl hypervisor::Hypervisor for KvmHypervisor {
|
||||
Ok(Arc::new(KvmVm {
|
||||
fd: vm_fd,
|
||||
state: VmState {},
|
||||
vmmops: ArcSwapOption::from(None),
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -509,7 +517,7 @@ pub struct KvmVcpu {
|
||||
fd: VcpuFd,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
msrs: MsrEntries,
|
||||
vmmops: ArcSwapOption<Box<dyn vm::VmmOps>>,
|
||||
vmmops: Option<Arc<Box<dyn vm::VmmOps>>>,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
hyperv_synic: AtomicBool,
|
||||
}
|
||||
@@ -520,7 +528,7 @@ pub struct KvmVcpu {
|
||||
/// let kvm = hypervisor::kvm::KvmHypervisor::new().unwrap();
|
||||
/// let hypervisor: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
|
||||
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
|
||||
/// let vcpu = vm.create_vcpu(0).unwrap();
|
||||
/// let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
/// vcpu.get/set().unwrap()
|
||||
///
|
||||
impl cpu::Vcpu for KvmVcpu {
|
||||
@@ -594,7 +602,7 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
fn enable_hyperv_synic(&self) -> cpu::Result<()> {
|
||||
// Update the information about Hyper-V SynIC being enabled and
|
||||
// emulated as it will influence later which MSRs should be saved.
|
||||
self.hyperv_synic.store(true, Ordering::SeqCst);
|
||||
self.hyperv_synic.store(true, Ordering::Release);
|
||||
|
||||
let mut cap: kvm_enable_cap = Default::default();
|
||||
cap.cap = KVM_CAP_HYPERV_SYNIC;
|
||||
@@ -708,7 +716,7 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
Ok(run) => match run {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
VcpuExit::IoIn(addr, data) => {
|
||||
if let Some(vmmops) = self.vmmops.load_full() {
|
||||
if let Some(vmmops) = &self.vmmops {
|
||||
return vmmops
|
||||
.pio_read(addr.into(), data)
|
||||
.map(|_| cpu::VmExit::Ignore)
|
||||
@@ -719,7 +727,7 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
VcpuExit::IoOut(addr, data) => {
|
||||
if let Some(vmmops) = self.vmmops.load_full() {
|
||||
if let Some(vmmops) = &self.vmmops {
|
||||
return vmmops
|
||||
.pio_write(addr.into(), data)
|
||||
.map(|_| cpu::VmExit::Ignore)
|
||||
@@ -738,10 +746,10 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
use kvm_bindings::{KVM_SYSTEM_EVENT_RESET, KVM_SYSTEM_EVENT_SHUTDOWN};
|
||||
// On Aarch64, when the VM is shutdown, run() returns
|
||||
// VcpuExit::SystemEvent with reason KVM_SYSTEM_EVENT_SHUTDOWN
|
||||
if event_type == KVM_SYSTEM_EVENT_SHUTDOWN
|
||||
|| event_type == KVM_SYSTEM_EVENT_RESET
|
||||
{
|
||||
if event_type == KVM_SYSTEM_EVENT_RESET {
|
||||
Ok(cpu::VmExit::Reset)
|
||||
} else if event_type == KVM_SYSTEM_EVENT_SHUTDOWN {
|
||||
Ok(cpu::VmExit::Shutdown)
|
||||
} else {
|
||||
Err(cpu::HypervisorCpuError::RunVcpu(anyhow!(
|
||||
"Unexpected system event with type 0x{:x}, flags 0x{:x}",
|
||||
@@ -752,7 +760,7 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
}
|
||||
|
||||
VcpuExit::MmioRead(addr, data) => {
|
||||
if let Some(vmmops) = self.vmmops.load_full() {
|
||||
if let Some(vmmops) = &self.vmmops {
|
||||
return vmmops
|
||||
.mmio_read(addr, data)
|
||||
.map(|_| cpu::VmExit::Ignore)
|
||||
@@ -762,7 +770,7 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
Ok(cpu::VmExit::MmioRead(addr, data))
|
||||
}
|
||||
VcpuExit::MmioWrite(addr, data) => {
|
||||
if let Some(vmmops) = self.vmmops.load_full() {
|
||||
if let Some(vmmops) = &self.vmmops {
|
||||
return vmmops
|
||||
.mmio_write(addr, data)
|
||||
.map(|_| cpu::VmExit::Ignore)
|
||||
@@ -1121,7 +1129,7 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
/// let hv: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
|
||||
/// let vm = hv.create_vm().expect("new VM fd creation failed");
|
||||
/// vm.enable_split_irq().unwrap();
|
||||
/// let vcpu = vm.create_vcpu(0).unwrap();
|
||||
/// let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
/// let state = vcpu.state().unwrap();
|
||||
/// ```
|
||||
fn state(&self) -> cpu::Result<CpuState> {
|
||||
@@ -1143,7 +1151,7 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
|
||||
// Save extra MSRs if the Hyper-V synthetic interrupt controller is
|
||||
// emulated.
|
||||
if self.hyperv_synic.load(Ordering::SeqCst) {
|
||||
if self.hyperv_synic.load(Ordering::Acquire) {
|
||||
let hyperv_synic_msrs = vec![
|
||||
0x40000020, 0x40000021, 0x40000080, 0x40000081, 0x40000082, 0x40000083, 0x40000084,
|
||||
0x40000090, 0x40000091, 0x40000092, 0x40000093, 0x40000094, 0x40000095, 0x40000096,
|
||||
@@ -1267,7 +1275,7 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
/// let hv: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
|
||||
/// let vm = hv.create_vm().expect("new VM fd creation failed");
|
||||
/// vm.enable_split_irq().unwrap();
|
||||
/// let vcpu = vm.create_vcpu(0).unwrap();
|
||||
/// let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
/// let state = vcpu.state().unwrap();
|
||||
/// vcpu.set_state(&state).unwrap();
|
||||
/// ```
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
use crate::arch::x86::{msr_index, MTRR_ENABLE, MTRR_MEM_TYPE_WB};
|
||||
use crate::arch::x86::{msr_index, SegmentRegisterOps, MTRR_ENABLE, MTRR_MEM_TYPE_WB};
|
||||
use crate::kvm::{Cap, Kvm, KvmError, KvmResult};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
@@ -28,6 +28,71 @@ pub use {
|
||||
kvm_bindings::KVM_CPUID_FLAG_SIGNIFCANT_INDEX as CPUID_FLAG_VALID_INDEX,
|
||||
};
|
||||
|
||||
impl SegmentRegisterOps for SegmentRegister {
|
||||
fn segment_type(&self) -> u8 {
|
||||
self.type_
|
||||
}
|
||||
fn set_segment_type(&mut self, val: u8) {
|
||||
self.type_ = val;
|
||||
}
|
||||
|
||||
fn dpl(&self) -> u8 {
|
||||
self.dpl
|
||||
}
|
||||
|
||||
fn set_dpl(&mut self, val: u8) {
|
||||
self.dpl = val;
|
||||
}
|
||||
|
||||
fn present(&self) -> u8 {
|
||||
self.present
|
||||
}
|
||||
|
||||
fn set_present(&mut self, val: u8) {
|
||||
self.present = val;
|
||||
}
|
||||
|
||||
fn long(&self) -> u8 {
|
||||
self.l
|
||||
}
|
||||
|
||||
fn set_long(&mut self, val: u8) {
|
||||
self.l = val;
|
||||
}
|
||||
|
||||
fn avl(&self) -> u8 {
|
||||
self.avl
|
||||
}
|
||||
|
||||
fn set_avl(&mut self, val: u8) {
|
||||
self.avl = val;
|
||||
}
|
||||
|
||||
fn desc_type(&self) -> u8 {
|
||||
self.s
|
||||
}
|
||||
|
||||
fn set_desc_type(&mut self, val: u8) {
|
||||
self.s = val;
|
||||
}
|
||||
|
||||
fn granularity(&self) -> u8 {
|
||||
self.g
|
||||
}
|
||||
|
||||
fn set_granularity(&mut self, val: u8) {
|
||||
self.g = val;
|
||||
}
|
||||
|
||||
fn db(&self) -> u8 {
|
||||
self.db
|
||||
}
|
||||
|
||||
fn set_db(&mut self, val: u8) {
|
||||
self.db = val;
|
||||
}
|
||||
}
|
||||
|
||||
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffb_d000);
|
||||
|
||||
macro_rules! msr {
|
||||
|
||||
@@ -28,9 +28,14 @@ extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate thiserror;
|
||||
|
||||
#[cfg(feature = "kvm")]
|
||||
/// KVM implementation module
|
||||
pub mod kvm;
|
||||
|
||||
/// Microsoft Hypervisor implementation module
|
||||
#[cfg(all(feature = "mshv", target_arch = "x86_64"))]
|
||||
pub mod mshv;
|
||||
|
||||
/// Hypevisor related module
|
||||
pub mod hypervisor;
|
||||
|
||||
@@ -49,7 +54,10 @@ mod device;
|
||||
pub use crate::hypervisor::{Hypervisor, HypervisorError};
|
||||
pub use cpu::{HypervisorCpuError, Vcpu, VmExit};
|
||||
pub use device::{Device, HypervisorDeviceError};
|
||||
#[cfg(feature = "kvm")]
|
||||
pub use kvm::*;
|
||||
#[cfg(all(feature = "mshv", target_arch = "x86_64"))]
|
||||
pub use mshv::*;
|
||||
pub use vm::{DataMatch, HypervisorVmError, Vm};
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -58,5 +66,8 @@ pub fn new() -> std::result::Result<Arc<dyn Hypervisor>, HypervisorError> {
|
||||
#[cfg(feature = "kvm")]
|
||||
let hv = kvm::KvmHypervisor::new()?;
|
||||
|
||||
#[cfg(feature = "mshv")]
|
||||
let hv = mshv::MshvHypervisor::new()?;
|
||||
|
||||
Ok(Arc::new(hv))
|
||||
}
|
||||
|
||||
1082
hypervisor/src/mshv/mod.rs
Normal file
1082
hypervisor/src/mshv/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
148
hypervisor/src/mshv/x86_64/mod.rs
Normal file
148
hypervisor/src/mshv/x86_64/mod.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
// Copyright 2018-2019 CrowdStrike, Inc.
|
||||
//
|
||||
//
|
||||
|
||||
use crate::arch::x86::{msr_index, SegmentRegisterOps, MTRR_ENABLE, MTRR_MEM_TYPE_WB};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
///
|
||||
/// Export generically-named wrappers of mshv_bindings for Unix-based platforms
|
||||
///
|
||||
pub use {
|
||||
mshv_bindings::mshv_user_mem_region as MemoryRegion, mshv_bindings::msr_entry as MsrEntry,
|
||||
mshv_bindings::CpuId, mshv_bindings::DebugRegisters,
|
||||
mshv_bindings::FloatingPointUnit as FpuState, mshv_bindings::LapicState,
|
||||
mshv_bindings::MsrList, mshv_bindings::Msrs as MsrEntries, mshv_bindings::Msrs,
|
||||
mshv_bindings::SegmentRegister, mshv_bindings::SpecialRegisters,
|
||||
mshv_bindings::StandardRegisters, mshv_bindings::VcpuEvents, mshv_bindings::XSave as Xsave,
|
||||
mshv_bindings::Xcrs as ExtendedControlRegisters,
|
||||
};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct VcpuMshvState {
|
||||
pub msrs: MsrEntries,
|
||||
pub vcpu_events: VcpuEvents,
|
||||
pub regs: StandardRegisters,
|
||||
pub sregs: SpecialRegisters,
|
||||
pub fpu: FpuState,
|
||||
pub xcrs: ExtendedControlRegisters,
|
||||
pub lapic: LapicState,
|
||||
pub dbg: DebugRegisters,
|
||||
pub xsave: Xsave,
|
||||
}
|
||||
|
||||
pub struct CreateDevice {}
|
||||
pub struct DeviceAttr {}
|
||||
pub struct IrqRouting {}
|
||||
pub enum VcpuExit {}
|
||||
pub struct MpState {}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Clone, Debug, Copy)]
|
||||
pub enum IoEventAddress {
|
||||
/// Representation of an programmable I/O address.
|
||||
Pio(u64),
|
||||
/// Representation of an memory mapped I/O address.
|
||||
Mmio(u64),
|
||||
}
|
||||
macro_rules! msr {
|
||||
($msr:expr) => {
|
||||
MsrEntry {
|
||||
index: $msr,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! msr_data {
|
||||
($msr:expr, $data:expr) => {
|
||||
MsrEntry {
|
||||
index: $msr,
|
||||
data: $data,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl SegmentRegisterOps for SegmentRegister {
|
||||
fn segment_type(&self) -> u8 {
|
||||
self.type_
|
||||
}
|
||||
fn set_segment_type(&mut self, val: u8) {
|
||||
self.type_ = val;
|
||||
}
|
||||
|
||||
fn dpl(&self) -> u8 {
|
||||
self.dpl
|
||||
}
|
||||
|
||||
fn set_dpl(&mut self, val: u8) {
|
||||
self.dpl = val;
|
||||
}
|
||||
|
||||
fn present(&self) -> u8 {
|
||||
self.present
|
||||
}
|
||||
|
||||
fn set_present(&mut self, val: u8) {
|
||||
self.present = val;
|
||||
}
|
||||
|
||||
fn long(&self) -> u8 {
|
||||
self.l
|
||||
}
|
||||
|
||||
fn set_long(&mut self, val: u8) {
|
||||
self.l = val;
|
||||
}
|
||||
|
||||
fn avl(&self) -> u8 {
|
||||
self.avl
|
||||
}
|
||||
|
||||
fn set_avl(&mut self, val: u8) {
|
||||
self.avl = val;
|
||||
}
|
||||
|
||||
fn desc_type(&self) -> u8 {
|
||||
self.s
|
||||
}
|
||||
|
||||
fn set_desc_type(&mut self, val: u8) {
|
||||
self.s = val;
|
||||
}
|
||||
|
||||
fn granularity(&self) -> u8 {
|
||||
self.g
|
||||
}
|
||||
|
||||
fn set_granularity(&mut self, val: u8) {
|
||||
self.g = val;
|
||||
}
|
||||
|
||||
fn db(&self) -> u8 {
|
||||
self.db
|
||||
}
|
||||
|
||||
fn set_db(&mut self, val: u8) {
|
||||
self.db = val;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn boot_msr_entries() -> MsrEntries {
|
||||
MsrEntries::from_entries(&[
|
||||
msr!(msr_index::MSR_IA32_SYSENTER_CS),
|
||||
msr!(msr_index::MSR_IA32_SYSENTER_ESP),
|
||||
msr!(msr_index::MSR_IA32_SYSENTER_EIP),
|
||||
msr!(msr_index::MSR_STAR),
|
||||
msr!(msr_index::MSR_CSTAR),
|
||||
msr!(msr_index::MSR_LSTAR),
|
||||
msr!(msr_index::MSR_KERNEL_GS_BASE),
|
||||
msr!(msr_index::MSR_SYSCALL_MASK),
|
||||
msr!(msr_index::MSR_IA32_TSC),
|
||||
])
|
||||
}
|
||||
@@ -12,10 +12,16 @@
|
||||
use crate::aarch64::VcpuInit;
|
||||
use crate::cpu::Vcpu;
|
||||
use crate::device::Device;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
use crate::ClockData;
|
||||
#[cfg(feature = "kvm")]
|
||||
use crate::CreateDevice;
|
||||
#[cfg(feature = "mshv")]
|
||||
use crate::HvState as VmState;
|
||||
#[cfg(feature = "kvm")]
|
||||
use crate::KvmVmState as VmState;
|
||||
use crate::{CreateDevice, IoEventAddress, IrqRoutingEntry, MemoryRegion};
|
||||
use crate::{IoEventAddress, IrqRoutingEntry, MemoryRegion};
|
||||
#[cfg(feature = "kvm")]
|
||||
use kvm_ioctls::Cap;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
@@ -24,6 +30,7 @@ use vmm_sys_util::eventfd::EventFd;
|
||||
///
|
||||
/// I/O events data matches (32 or 64 bits).
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub enum DataMatch {
|
||||
DataMatch32(u32),
|
||||
DataMatch64(u64),
|
||||
@@ -117,7 +124,6 @@ pub enum HypervisorVmError {
|
||||
///
|
||||
#[error("Failed to create passthrough device: {0}")]
|
||||
CreatePassthroughDevice(#[source] anyhow::Error),
|
||||
///
|
||||
/// Write to Guest memory
|
||||
///
|
||||
#[error("Failed to write to guest memory: {0}")]
|
||||
@@ -147,6 +153,16 @@ pub enum HypervisorVmError {
|
||||
///
|
||||
#[error("Failed to write to IO Bus: {0}")]
|
||||
IoBusWrite(#[source] anyhow::Error),
|
||||
///
|
||||
/// Get dirty log error
|
||||
///
|
||||
#[error("Failed to get dirty log: {0}")]
|
||||
GetDirtyLog(#[source] anyhow::Error),
|
||||
///
|
||||
/// Assert virtual interrupt error
|
||||
///
|
||||
#[error("Failed to assert virtual Interrupt: {0}")]
|
||||
AsserttVirtualInterrupt(#[source] anyhow::Error),
|
||||
}
|
||||
///
|
||||
/// Result type for returning from a function
|
||||
@@ -169,7 +185,7 @@ pub trait Vm: Send + Sync {
|
||||
/// Unregister an event that will, when signaled, trigger the `gsi` IRQ.
|
||||
fn unregister_irqfd(&self, fd: &EventFd, gsi: u32) -> Result<()>;
|
||||
/// Creates a new KVM vCPU file descriptor and maps the memory corresponding
|
||||
fn create_vcpu(&self, id: u8) -> Result<Arc<dyn Vcpu>>;
|
||||
fn create_vcpu(&self, id: u8, vmmops: Option<Arc<Box<dyn VmmOps>>>) -> Result<Arc<dyn Vcpu>>;
|
||||
/// Registers an event to be signaled whenever a certain address is written to.
|
||||
fn register_ioevent(
|
||||
&self,
|
||||
@@ -189,9 +205,11 @@ pub trait Vm: Send + Sync {
|
||||
memory_size: u64,
|
||||
userspace_addr: u64,
|
||||
readonly: bool,
|
||||
log_dirty_pages: bool,
|
||||
) -> MemoryRegion;
|
||||
/// Creates/modifies a guest physical memory slot.
|
||||
fn set_user_memory_region(&self, user_memory_region: MemoryRegion) -> Result<()>;
|
||||
#[cfg(feature = "kvm")]
|
||||
/// Creates an emulated device in the kernel.
|
||||
fn create_device(&self, device: &mut CreateDevice) -> Result<Arc<dyn Device>>;
|
||||
/// Returns the preferred CPU target type which can be emulated by KVM on underlying host.
|
||||
@@ -201,11 +219,12 @@ pub trait Vm: Send + Sync {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn enable_split_irq(&self) -> Result<()>;
|
||||
/// Retrieve guest clock.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
fn get_clock(&self) -> Result<ClockData>;
|
||||
/// Set guest clock.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
fn set_clock(&self, data: &ClockData) -> Result<()>;
|
||||
#[cfg(feature = "kvm")]
|
||||
/// Checks if a particular `Cap` is available.
|
||||
fn check_extension(&self, c: Cap) -> bool;
|
||||
/// Create a device that is used for passthrough
|
||||
@@ -214,8 +233,8 @@ pub trait Vm: Send + Sync {
|
||||
fn state(&self) -> Result<VmState>;
|
||||
/// Set the VM state
|
||||
fn set_state(&self, state: VmState) -> Result<()>;
|
||||
/// Set VmmOps interface
|
||||
fn set_vmmops(&self, vmmops: Box<dyn VmmOps>) -> Result<()>;
|
||||
/// Get dirty pages bitmap
|
||||
fn get_dirty_log(&self, slot: u32, memory_size: u64) -> Result<Vec<u64>>;
|
||||
}
|
||||
|
||||
pub trait VmmOps: Send + Sync {
|
||||
|
||||
@@ -5,17 +5,17 @@ authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.80"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
net_gen = { path = "../net_gen" }
|
||||
rand = "0.7.3"
|
||||
serde = "1.0.117"
|
||||
serde = "1.0.118"
|
||||
virtio-bindings = "0.1.0"
|
||||
vm-memory = { version = "0.3.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-memory = { version = "0.4.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.3.0"
|
||||
pnet = "0.26.0"
|
||||
serde_json = "1.0.59"
|
||||
pnet = "0.27.2"
|
||||
serde_json = "1.0.60"
|
||||
|
||||
@@ -10,14 +10,14 @@ byteorder = "1.3.4"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
vfio-ioctls = { git = "https://github.com/cloud-hypervisor/vfio-ioctls", branch = "ch" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
libc = "0.2.80"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = "0.3.0"
|
||||
vm-memory = "0.4.0"
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
|
||||
[dependencies.vfio-bindings]
|
||||
|
||||
@@ -570,7 +570,7 @@ impl PciConfiguration {
|
||||
let end_addr = config
|
||||
.addr
|
||||
.checked_add(config.size - 1)
|
||||
.ok_or_else(|| Error::BarAddressInvalid(config.addr, config.size))?;
|
||||
.ok_or(Error::BarAddressInvalid(config.addr, config.size))?;
|
||||
match config.region_type {
|
||||
PciBarRegionType::Memory32BitRegion | PciBarRegionType::IORegion => {
|
||||
if end_addr > u64::from(u32::max_value()) {
|
||||
@@ -642,7 +642,7 @@ impl PciConfiguration {
|
||||
let end_addr = config
|
||||
.addr
|
||||
.checked_add(config.size - 1)
|
||||
.ok_or_else(|| Error::RomBarAddressInvalid(config.addr, config.size))?;
|
||||
.ok_or(Error::RomBarAddressInvalid(config.addr, config.size))?;
|
||||
|
||||
if end_addr > u64::from(u32::max_value()) {
|
||||
return Err(Error::RomBarAddressInvalid(config.addr, config.size));
|
||||
@@ -698,7 +698,7 @@ impl PciConfiguration {
|
||||
};
|
||||
let end_offset = cap_offset
|
||||
.checked_add(total_len)
|
||||
.ok_or_else(|| Error::CapabilitySpaceFull(total_len))?;
|
||||
.ok_or(Error::CapabilitySpaceFull(total_len))?;
|
||||
if end_offset > CAPABILITY_MAX_OFFSET {
|
||||
return Err(Error::CapabilitySpaceFull(total_len));
|
||||
}
|
||||
|
||||
@@ -221,9 +221,9 @@ impl Interrupt {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct MmioRegion {
|
||||
start: GuestAddress,
|
||||
length: GuestUsize,
|
||||
pub struct MmioRegion {
|
||||
pub start: GuestAddress,
|
||||
pub length: GuestUsize,
|
||||
type_: PciBarRegionType,
|
||||
index: u32,
|
||||
mem_slot: Option<u32>,
|
||||
@@ -561,6 +561,7 @@ impl VfioPciDevice {
|
||||
mmap_size as u64,
|
||||
host_addr as u64,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
vm.set_user_memory_region(mem_region)
|
||||
@@ -590,6 +591,7 @@ impl VfioPciDevice {
|
||||
0,
|
||||
host_addr as u64,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
if let Err(e) = self.vm.set_user_memory_region(r) {
|
||||
@@ -613,6 +615,10 @@ impl VfioPciDevice {
|
||||
.extend_dma_map(new_region)
|
||||
.map_err(VfioPciError::UpdateMemory)
|
||||
}
|
||||
|
||||
pub fn mmio_regions(&self) -> Vec<MmioRegion> {
|
||||
self.mmio_regions.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VfioPciDevice {
|
||||
@@ -748,7 +754,7 @@ impl PciDevice for VfioPciDevice {
|
||||
// The address needs to be 4 bytes aligned.
|
||||
bar_addr = allocator
|
||||
.allocate_io_addresses(None, region_size, Some(0x4))
|
||||
.ok_or_else(|| PciDeviceError::IoAllocationFailed(region_size))?;
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
|
||||
}
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
unimplemented!()
|
||||
@@ -795,11 +801,11 @@ impl PciDevice for VfioPciDevice {
|
||||
if is_64bit_bar {
|
||||
bar_addr = allocator
|
||||
.allocate_mmio_addresses(None, region_size, Some(bar_alignment))
|
||||
.ok_or_else(|| PciDeviceError::IoAllocationFailed(region_size))?;
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
|
||||
} else {
|
||||
bar_addr = allocator
|
||||
.allocate_mmio_hole_addresses(None, region_size, Some(bar_alignment))
|
||||
.ok_or_else(|| PciDeviceError::IoAllocationFailed(region_size))?;
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1012,6 +1018,7 @@ impl PciDevice for VfioPciDevice {
|
||||
0,
|
||||
host_addr as u64,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
self.vm
|
||||
@@ -1025,6 +1032,7 @@ impl PciDevice for VfioPciDevice {
|
||||
mmap_size as u64,
|
||||
host_addr as u64,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
self.vm
|
||||
|
||||
@@ -10,7 +10,7 @@ path = "src/qcow.rs"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3.4"
|
||||
libc = "0.2.80"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
remain = "0.2.2"
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
@@ -1868,7 +1868,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_header_crazy_file_size_rejected() {
|
||||
let mut header = valid_header_v3();
|
||||
&mut header[24..32].copy_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1e]);
|
||||
header[24..32].copy_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1e]);
|
||||
with_basic_file(&header, |disk_file: RawFile| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
@@ -1917,7 +1917,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_header_huge_num_refcounts() {
|
||||
let mut header = valid_header_v3();
|
||||
&mut header[56..60].copy_from_slice(&[0x02, 0x00, 0xe8, 0xff]);
|
||||
header[56..60].copy_from_slice(&[0x02, 0x00, 0xe8, 0xff]);
|
||||
with_basic_file(&header, |disk_file: RawFile| {
|
||||
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount clusters");
|
||||
});
|
||||
@@ -1926,7 +1926,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_header_huge_refcount_offset() {
|
||||
let mut header = valid_header_v3();
|
||||
&mut header[48..56].copy_from_slice(&[0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00]);
|
||||
header[48..56].copy_from_slice(&[0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00]);
|
||||
with_basic_file(&header, |disk_file: RawFile| {
|
||||
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount offset");
|
||||
});
|
||||
@@ -1936,11 +1936,11 @@ mod tests {
|
||||
fn write_read_start() {
|
||||
with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
|
||||
let mut q = QcowFile::from(disk_file).unwrap();
|
||||
q.write(b"test first bytes")
|
||||
q.write_all(b"test first bytes")
|
||||
.expect("Failed to write test string.");
|
||||
let mut buf = [0u8; 4];
|
||||
q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
|
||||
q.read(&mut buf).expect("Failed to read.");
|
||||
q.read_exact(&mut buf).expect("Failed to read.");
|
||||
assert_eq!(&buf, b"test");
|
||||
});
|
||||
}
|
||||
@@ -1951,10 +1951,10 @@ mod tests {
|
||||
let mut q = QcowFile::from(disk_file).unwrap();
|
||||
let b = [0x55u8; 0x1000];
|
||||
q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
|
||||
q.write(&b).expect("Failed to write test string.");
|
||||
q.write_all(&b).expect("Failed to write test string.");
|
||||
let mut buf = [0u8; 4];
|
||||
q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
|
||||
q.read(&mut buf).expect("Failed to read.");
|
||||
q.read_exact(&mut buf).expect("Failed to read.");
|
||||
assert_eq!(buf[0], 0x55);
|
||||
});
|
||||
}
|
||||
@@ -1966,7 +1966,7 @@ mod tests {
|
||||
// Write some test data.
|
||||
let b = [0x55u8; 0x1000];
|
||||
q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
|
||||
q.write(&b).expect("Failed to write test string.");
|
||||
q.write_all(&b).expect("Failed to write test string.");
|
||||
// Overwrite the test data with zeroes.
|
||||
q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
|
||||
let nwritten = q.write_zeroes(0x200).expect("Failed to write zeroes.");
|
||||
@@ -1974,7 +1974,7 @@ mod tests {
|
||||
// Verify that the correct part of the data was zeroed out.
|
||||
let mut buf = [0u8; 0x1000];
|
||||
q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
|
||||
q.read(&mut buf).expect("Failed to read.");
|
||||
q.read_exact(&mut buf).expect("Failed to read.");
|
||||
assert_eq!(buf[0], 0);
|
||||
assert_eq!(buf[0x1FF], 0);
|
||||
assert_eq!(buf[0x200], 0x55);
|
||||
@@ -1992,7 +1992,7 @@ mod tests {
|
||||
// Write some test data.
|
||||
let b = [0x55u8; CHUNK_SIZE];
|
||||
q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
|
||||
q.write(&b).expect("Failed to write test string.");
|
||||
q.write_all(&b).expect("Failed to write test string.");
|
||||
// Overwrite the full cluster with zeroes.
|
||||
q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
|
||||
let nwritten = q.write_zeroes(CHUNK_SIZE).expect("Failed to write zeroes.");
|
||||
@@ -2000,7 +2000,7 @@ mod tests {
|
||||
// Verify that the data was zeroed out.
|
||||
let mut buf = [0u8; CHUNK_SIZE];
|
||||
q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
|
||||
q.read(&mut buf).expect("Failed to read.");
|
||||
q.read_exact(&mut buf).expect("Failed to read.");
|
||||
assert_eq!(buf[0], 0);
|
||||
assert_eq!(buf[CHUNK_SIZE - 1], 0);
|
||||
});
|
||||
@@ -2024,7 +2024,7 @@ mod tests {
|
||||
let mut q = QcowFile::from(disk_file).unwrap();
|
||||
let mut b = [5u8; 16];
|
||||
q.seek(SeekFrom::Start(1000)).expect("Failed to seek.");
|
||||
q.read(&mut b).expect("Failed to read.");
|
||||
q.read_exact(&mut b).expect("Failed to read.");
|
||||
assert_eq!(0, b[0]);
|
||||
assert_eq!(0, b[15]);
|
||||
});
|
||||
@@ -2397,7 +2397,7 @@ mod tests {
|
||||
for xfer in &xfers {
|
||||
q.seek(SeekFrom::Start(xfer.addr)).expect("Failed to seek.");
|
||||
if xfer.write {
|
||||
q.write(&b).expect("Failed to write.");
|
||||
q.write_all(&b).expect("Failed to write.");
|
||||
} else {
|
||||
let read_count: usize = q.read(&mut b).expect("Failed to read.");
|
||||
assert_eq!(read_count, BUF_SIZE);
|
||||
|
||||
@@ -1,3 +1,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)
|
||||
- [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)
|
||||
@@ -10,14 +16,14 @@
|
||||
- [New `--balloon` Parameter Added](#new---balloon-parameter-added)
|
||||
- [Experimental `virtio-watchdog` Support](#experimental-virtio-watchdog-support)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes)
|
||||
- [Contributors](#contributors)
|
||||
- [Contributors](#contributors-1)
|
||||
- [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-1)
|
||||
- [Contributors](#contributors-1)
|
||||
- [Contributors](#contributors-2)
|
||||
- [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)
|
||||
@@ -31,7 +37,7 @@
|
||||
- [Intel SGX Support](#intel-sgx-support)
|
||||
- [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements-1)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-2)
|
||||
- [Contributors](#contributors-2)
|
||||
- [Contributors](#contributors-3)
|
||||
- [v0.8.0](#v080)
|
||||
- [Experimental Snapshot and Restore Support](#experimental-snapshot-and-restore-support)
|
||||
- [Experimental ARM64 Support](#experimental-arm64-support)
|
||||
@@ -40,7 +46,7 @@
|
||||
- [`vhost_user_fs` Improvements](#vhost_user_fs-improvements)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-3)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes)
|
||||
- [Contributors](#contributors-3)
|
||||
- [Contributors](#contributors-4)
|
||||
- [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)
|
||||
@@ -50,14 +56,14 @@
|
||||
- [`Seccomp` Sandboxing](#seccomp-sandboxing)
|
||||
- [Updated Distribution Support](#updated-distribution-support)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes-1)
|
||||
- [Contributors](#contributors-4)
|
||||
- [Contributors](#contributors-5)
|
||||
- [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-5)
|
||||
- [Contributors](#contributors-6)
|
||||
- [v0.5.1](#v051)
|
||||
- [v0.5.0](#v050)
|
||||
- [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing)
|
||||
@@ -65,7 +71,7 @@
|
||||
- [New Interrupt Management Framework](#new-interrupt-management-framework)
|
||||
- [Development Tools](#development-tools)
|
||||
- [Kata Containers Integration](#kata-containers-integration)
|
||||
- [Contributors](#contributors-6)
|
||||
- [Contributors](#contributors-7)
|
||||
- [v0.4.0](#v040)
|
||||
- [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition)
|
||||
- [Programmatic firmware tables generation](#programmatic-firmware-tables-generation)
|
||||
@@ -74,7 +80,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-7)
|
||||
- [Contributors](#contributors-8)
|
||||
- [v0.3.0](#v030)
|
||||
- [Block device offloading](#block-device-offloading)
|
||||
- [Network device backend](#network-device-backend)
|
||||
@@ -101,6 +107,47 @@
|
||||
- [Unit testing](#unit-testing)
|
||||
- [Integration tests parallelization](#integration-tests-parallelization)
|
||||
|
||||
# v0.12.0
|
||||
|
||||
This release has been tracked through the [0.12.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/15).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.12.0 include:
|
||||
|
||||
### ARM64 enhancements
|
||||
|
||||
The use of `--watchdog` is now fully supported as is the ability to reboot the
|
||||
VM from within the guest when running Cloud Hypervisor on an ARM64 system.
|
||||
|
||||
### Removal of `vhost-user-net` and `vhost-user-block` self spawning
|
||||
|
||||
In order to use `vhost-user-net` or `vhost-user-block` backends the user is now
|
||||
responsible for starting the backend and providing the socket for the VMM to
|
||||
use. This functionality was deprecated in the last release and how now been
|
||||
removed.
|
||||
|
||||
### Migration of `vhost-user-fs` backend
|
||||
|
||||
The `vhost-user-fs` backend is no longer included in Cloud Hypervisor and it is
|
||||
instead hosted in [it's own
|
||||
repository](https://gitlab.com/virtio-fs/virtiofsd-rs)
|
||||
|
||||
### Enhanced "info" API
|
||||
|
||||
The `vm.info` HTTP API endpoint has been extended to include the details of the
|
||||
devices used by the VM including any VFIO devices used.
|
||||
|
||||
### Contributors
|
||||
|
||||
Many thanks to everyone who has contributed to our 0.12.0 release:
|
||||
|
||||
* Anatol Belski <anbelski@linux.microsoft.com>
|
||||
* Julio Montes <julio.montes@intel.com>
|
||||
* Michael Zhao <michael.zhao@arm.com>
|
||||
* Muminul Islam <muislam@microsoft.com>
|
||||
* Rob Bradford <robert.bradford@intel.com>
|
||||
* Samuel Ortiz <sameo@linux.intel.com>
|
||||
* Wei Liu <liuwe@microsoft.com>
|
||||
|
||||
# v0.11.0
|
||||
|
||||
This release has been tracked through the [0.11.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/14).
|
||||
|
||||
@@ -1543,7 +1543,39 @@ CONFIG_THERMAL_GOV_USER_SPACE=y
|
||||
# CONFIG_CPU_THERMAL is not set
|
||||
# CONFIG_THERMAL_EMULATION is not set
|
||||
# CONFIG_THERMAL_MMIO is not set
|
||||
# CONFIG_WATCHDOG is not set
|
||||
CONFIG_WATCHDOG=y
|
||||
CONFIG_WATCHDOG_CORE=y
|
||||
# CONFIG_WATCHDOG_NOWAYOUT is not set
|
||||
CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED=y
|
||||
CONFIG_WATCHDOG_OPEN_TIMEOUT=0
|
||||
# CONFIG_WATCHDOG_SYSFS is not set
|
||||
|
||||
#
|
||||
# Watchdog Pretimeout Governors
|
||||
#
|
||||
# CONFIG_WATCHDOG_PRETIMEOUT_GOV is not set
|
||||
|
||||
#
|
||||
# Watchdog Device Drivers
|
||||
#
|
||||
# CONFIG_SOFT_WATCHDOG is not set
|
||||
# CONFIG_WDAT_WDT is not set
|
||||
# CONFIG_XILINX_WATCHDOG is not set
|
||||
# CONFIG_ARM_SP805_WATCHDOG is not set
|
||||
# CONFIG_ARM_SBSA_WATCHDOG is not set
|
||||
# CONFIG_CADENCE_WATCHDOG is not set
|
||||
# CONFIG_DW_WATCHDOG is not set
|
||||
# CONFIG_MAX63XX_WATCHDOG is not set
|
||||
# CONFIG_ARM_SMC_WATCHDOG is not set
|
||||
# CONFIG_ALIM7101_WDT is not set
|
||||
# CONFIG_I6300ESB_WDT is not set
|
||||
CONFIG_VIRTIO_WDT=y
|
||||
|
||||
#
|
||||
# PCI-based Watchdog Cards
|
||||
#
|
||||
# CONFIG_PCIPCWATCHDOG is not set
|
||||
# CONFIG_WDTPCI is not set
|
||||
CONFIG_SSB_POSSIBLE=y
|
||||
# CONFIG_SSB is not set
|
||||
CONFIG_BCMA_POSSIBLE=y
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name: cloud-hypervisor
|
||||
Summary: Cloud Hypervisor is an open source Virtual Machine Monitor (VMM) that runs on top of KVM.
|
||||
Version: 0.8.0
|
||||
Version: 0.12.0
|
||||
Release: 0%{?dist}
|
||||
License: ASL 2.0 or BSD-3-clause
|
||||
Group: Applications/System
|
||||
@@ -36,14 +36,12 @@ install -D -m755 ./target/x86_64-unknown-linux-gnu/release/cloud-hypervisor %{b
|
||||
install -D -m755 ./target/x86_64-unknown-linux-gnu/release/ch-remote %{buildroot}%{_bindir}
|
||||
install -d %{buildroot}%{_libdir}
|
||||
install -d %{buildroot}%{_libdir}/cloud-hypervisor
|
||||
install -D -m755 target/x86_64-unknown-linux-gnu/release/vhost_user_blk %{buildroot}%{_libdir}/cloud-hypervisor
|
||||
install -D -m755 target/x86_64-unknown-linux-gnu/release/vhost_user_fs %{buildroot}%{_libdir}/cloud-hypervisor
|
||||
install -D -m755 target/x86_64-unknown-linux-gnu/release/vhost_user_block %{buildroot}%{_libdir}/cloud-hypervisor
|
||||
install -D -m755 target/x86_64-unknown-linux-gnu/release/vhost_user_net %{buildroot}%{_libdir}/cloud-hypervisor
|
||||
|
||||
install -d %{buildroot}%{_libdir}/cloud-hypervisor/static
|
||||
install -D -m755 target/x86_64-unknown-linux-musl/release/cloud-hypervisor %{buildroot}%{_libdir}/cloud-hypervisor/static
|
||||
install -D -m755 target/x86_64-unknown-linux-musl/release/vhost_user_blk %{buildroot}%{_libdir}/cloud-hypervisor/static
|
||||
install -D -m755 target/x86_64-unknown-linux-musl/release/vhost_user_fs %{buildroot}%{_libdir}/cloud-hypervisor/static
|
||||
install -D -m755 target/x86_64-unknown-linux-musl/release/vhost_user_block %{buildroot}%{_libdir}/cloud-hypervisor/static
|
||||
install -D -m755 target/x86_64-unknown-linux-musl/release/vhost_user_net %{buildroot}%{_libdir}/cloud-hypervisor/static
|
||||
install -D -m755 target/x86_64-unknown-linux-musl/release/ch-remote %{buildroot}%{_libdir}/cloud-hypervisor/static
|
||||
|
||||
@@ -87,8 +85,6 @@ setcap cap_net_admin+ep %{_bindir}/cloud-hypervisor
|
||||
setcap cap_net_admin+ep %{_libdir}/cloud-hypervisor/vhost_user_net
|
||||
setcap cap_net_admin+ep %{_libdir}/cloud-hypervisor/static/cloud-hypervisor
|
||||
setcap cap_net_admin+ep %{_libdir}/cloud-hypervisor/static/vhost_user_net
|
||||
setcap cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_setgid,cap_setuid,cap_mknod,cap_setfcap,cap_sys_admin+epi %{_libdir}/cloud-hypervisor/vhost_user_fs
|
||||
setcap cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_setgid,cap_setuid,cap_mknod,cap_setfcap,cap_sys_admin+epi %{_libdir}/cloud-hypervisor/static/vhost_user_fs
|
||||
|
||||
|
||||
%changelog
|
||||
|
||||
@@ -175,6 +175,7 @@ cmd_help() {
|
||||
echo " --release Build the release binaries."
|
||||
echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu"
|
||||
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 " Run the Cloud Hypervisor tests."
|
||||
@@ -185,6 +186,7 @@ cmd_help() {
|
||||
echo " --integration-windows Run the Windows guest integration tests."
|
||||
echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu"
|
||||
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]"
|
||||
@@ -205,12 +207,14 @@ cmd_help() {
|
||||
cmd_build() {
|
||||
build="debug"
|
||||
libc="gnu"
|
||||
hypervisor="kvm"
|
||||
features_build=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--debug") { build="debug"; } ;;
|
||||
"--release") { build="release"; } ;;
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--debug") { build="debug"; } ;;
|
||||
"--release") { build="release"; } ;;
|
||||
"--libc")
|
||||
shift
|
||||
[[ "$1" =~ ^(musl|gnu)$ ]] || \
|
||||
@@ -221,7 +225,11 @@ cmd_build() {
|
||||
shift
|
||||
arg_vols="$1"
|
||||
;;
|
||||
"--") { shift; break; } ;;
|
||||
"--hypervisor")
|
||||
shift
|
||||
hypervisor="$1"
|
||||
;;
|
||||
"--") { shift; break; } ;;
|
||||
*)
|
||||
die "Unknown build argument: $1. Please use --help for help."
|
||||
;;
|
||||
@@ -229,6 +237,9 @@ cmd_build() {
|
||||
shift
|
||||
done
|
||||
process_volumes_args
|
||||
if [[ "$hypervisor" != "kvm" ]]; then
|
||||
die "Hypervisor value must be kvm"
|
||||
fi
|
||||
|
||||
target="$(uname -m)-unknown-linux-${libc}"
|
||||
|
||||
@@ -251,7 +262,7 @@ cmd_build() {
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
--env RUSTFLAGS="$rustflags" \
|
||||
"$CTR_IMAGE" \
|
||||
cargo build --all \
|
||||
cargo build --all $features_build \
|
||||
--target-dir "$CTR_CLH_CARGO_TARGET" \
|
||||
"${cargo_args[@]}" && say "Binaries placed under $CLH_CARGO_TARGET/$target/$build"
|
||||
}
|
||||
@@ -278,15 +289,15 @@ cmd_tests() {
|
||||
integration_windows=false
|
||||
libc="gnu"
|
||||
arg_vols=""
|
||||
|
||||
hypervisor="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-windows") { integration_windows=true; } ;;
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--unit") { unit=true; } ;;
|
||||
"--cargo") { cargo=true; } ;;
|
||||
"--integration") { integration=true; } ;;
|
||||
"--integration-sgx") { integration_sgx=true; } ;;
|
||||
"--integration-windows") { integration_windows=true; } ;;
|
||||
"--libc")
|
||||
shift
|
||||
[[ "$1" =~ ^(musl|gnu)$ ]] || \
|
||||
@@ -297,14 +308,22 @@ cmd_tests() {
|
||||
shift
|
||||
arg_vols="$1"
|
||||
;;
|
||||
"--all") { cargo=true; unit=true; integration=true; } ;;
|
||||
"--") { shift; break; } ;;
|
||||
"--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" ]]; then
|
||||
die "Hypervisor value must be kvm"
|
||||
fi
|
||||
set -- "$@" '--hypervisor' $hypervisor
|
||||
|
||||
process_volumes_args
|
||||
target="$(uname -m)-unknown-linux-${libc}"
|
||||
@@ -315,7 +334,7 @@ cmd_tests() {
|
||||
cflags="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
|
||||
fi
|
||||
|
||||
if [ "$unit" = true ] ; then
|
||||
if [[ "$unit" = true && $hypervisor = "kvm" ]] ; then
|
||||
say "Running unit tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
@@ -338,7 +357,7 @@ cmd_tests() {
|
||||
--rm \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_cargo_tests.sh || fix_dir_perms $? || exit $?
|
||||
./scripts/run_cargo_tests.sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
if [ "$integration" = true ] ; then
|
||||
@@ -390,7 +409,7 @@ cmd_tests() {
|
||||
--net="$CTR_CLH_NET" \
|
||||
--mount type=tmpfs,destination=/tmp \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||
--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}" \
|
||||
@@ -405,9 +424,9 @@ cmd_build-container() {
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--dev") { container_type="dev"; } ;;
|
||||
"--") { shift; break; } ;;
|
||||
"--") { shift; break; } ;;
|
||||
*)
|
||||
die "Unknown build-container argument: $1. Please use --help for help."
|
||||
;;
|
||||
@@ -457,8 +476,8 @@ cmd_shell() {
|
||||
#
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h|--help) { cmd_help; exit 1; } ;;
|
||||
-y|--unattended) { OPT_UNATTENDED=true; } ;;
|
||||
-h|--help) { cmd_help; exit 1; } ;;
|
||||
-y|--unattended) { OPT_UNATTENDED=true; } ;;
|
||||
-*)
|
||||
die "Unknown arg: $1. Please use \`$0 help\` for help."
|
||||
;;
|
||||
|
||||
@@ -3,6 +3,9 @@ set -e
|
||||
set -x
|
||||
|
||||
source $HOME/.cargo/env
|
||||
source $(dirname "$0")/test-util.sh
|
||||
|
||||
process_common_args "$@"
|
||||
|
||||
# Install cargo components
|
||||
time rustup component add clippy
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
set -x
|
||||
|
||||
source $HOME/.cargo/env
|
||||
source $(dirname "$0")/test-util.sh
|
||||
|
||||
export BUILD_TARGET=${BUILD_TARGET-aarch64-unknown-linux-gnu}
|
||||
|
||||
@@ -106,19 +107,25 @@ update_workloads() {
|
||||
}
|
||||
|
||||
SRCDIR=$PWD
|
||||
if [ ! -d "$LINUX_CUSTOM_DIR" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
time git clone --depth 1 "https://github.com/cloud-hypervisor/linux.git" -b "virtio-fs-virtio-iommu-5.8-rc4" $LINUX_CUSTOM_DIR
|
||||
cp $SRCDIR/resources/linux-config-aarch64 $LINUX_CUSTOM_DIR/.config
|
||||
popd
|
||||
else
|
||||
LINUX_CUSTOM_BRANCH="virtio-fs-virtio-iommu-5.8-rc4"
|
||||
|
||||
# Check whether the local HEAD commit same as the remote HEAD or not. Remove the folder if they are different.
|
||||
if [ -d "$LINUX_CUSTOM_DIR" ]; then
|
||||
pushd $LINUX_CUSTOM_DIR
|
||||
git fetch
|
||||
git checkout -f "virtio-fs-virtio-iommu-5.8-rc4"
|
||||
cp $SRCDIR/resources/linux-config-aarch64 $LINUX_CUSTOM_DIR/.config
|
||||
LINUX_CUSTOM_LOCAL_HEAD=$(git rev-parse HEAD)
|
||||
LINUX_CUSTOM_REMOTE_HEAD=$(git rev-parse remotes/origin/$LINUX_CUSTOM_BRANCH)
|
||||
popd
|
||||
if [ "$LINUX_CUSTOM_LOCAL_HEAD" != "$LINUX_CUSTOM_REMOTE_HEAD" ]; then
|
||||
rm -rf "$LINUX_CUSTOM_DIR"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -d "$LINUX_CUSTOM_DIR" ]; then
|
||||
time git clone --depth 1 "https://github.com/cloud-hypervisor/linux.git" -b $LINUX_CUSTOM_BRANCH $LINUX_CUSTOM_DIR
|
||||
fi
|
||||
|
||||
cp $SRCDIR/resources/linux-config-aarch64 $LINUX_CUSTOM_DIR/.config
|
||||
build_custom_linux_kernel
|
||||
|
||||
VIRTIOFSD="$WORKLOADS_DIR/virtiofsd"
|
||||
@@ -136,6 +143,19 @@ update_workloads() {
|
||||
popd
|
||||
fi
|
||||
|
||||
VIRTIOFSD_RS="$WORKLOADS_DIR/virtiofsd-rs"
|
||||
VIRTIOFSD_RS_DIR="virtiofsd_rs_build"
|
||||
if [ ! -f "$VIRTIOFSD_RS" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
git clone --depth 1 "https://gitlab.com/virtio-fs/virtiofsd-rs.git" $VIRTIOFSD_RS_DIR
|
||||
pushd $VIRTIOFSD_RS_DIR
|
||||
time cargo build --release
|
||||
cp target/release/virtiofsd-rs $VIRTIOFSD_RS || exit 1
|
||||
popd
|
||||
rm -rf $VIRTIOFSD_RS_DIR
|
||||
popd
|
||||
fi
|
||||
|
||||
BLK_IMAGE="$WORKLOADS_DIR/blk.img"
|
||||
MNT_DIR="mount_image"
|
||||
if [ ! -f "$BLK_IMAGE" ]; then
|
||||
@@ -158,6 +178,10 @@ update_workloads() {
|
||||
fi
|
||||
}
|
||||
|
||||
process_common_args "$@"
|
||||
features_build="--no-default-features --features kvm "
|
||||
features_test="--no-default-features --features integration_tests,kvm"
|
||||
|
||||
# lock the workloads folder to avoid parallel updating by different containers
|
||||
(
|
||||
echo "try to lock $WORKLOADS_DIR folder and update"
|
||||
@@ -185,7 +209,7 @@ TARGET_CC="musl-gcc"
|
||||
CFLAGS="-I /usr/include/aarch64-linux-musl/ -idirafter /usr/include/"
|
||||
fi
|
||||
|
||||
cargo build --all --release --no-default-features --features kvm --target $BUILD_TARGET
|
||||
cargo build --all --release $features_build --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
|
||||
@@ -197,7 +221,7 @@ sudo bash -c "echo 10 > /sys/kernel/mm/ksm/sleep_millisecs"
|
||||
sudo bash -c "echo 1 > /sys/kernel/mm/ksm/run"
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
time cargo test --no-default-features --features "integration_tests,kvm" "tests::parallel::$@" -- --skip test_snapshot_restore
|
||||
time cargo test $features_test "tests::parallel::"
|
||||
RES=$?
|
||||
|
||||
# Tear vhost_user_net test network down
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
set -x
|
||||
|
||||
source $HOME/.cargo/env
|
||||
source $(dirname "$0")/test-util.sh
|
||||
|
||||
process_common_args "$@"
|
||||
# For now these values are deafult for kvm
|
||||
features_build=""
|
||||
features_test="--features integration_tests"
|
||||
|
||||
BUILD_TARGET="$(uname -m)-unknown-linux-${CH_LIBC}"
|
||||
CFLAGS=""
|
||||
@@ -11,12 +17,12 @@ TARGET_CC="musl-gcc"
|
||||
CFLAGS="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
|
||||
fi
|
||||
|
||||
cargo build --all --release --target $BUILD_TARGET
|
||||
cargo build --all --release $features_build --target $BUILD_TARGET
|
||||
strip target/$BUILD_TARGET/release/cloud-hypervisor
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
|
||||
time cargo test --features "integration_tests" "tests::sgx::$@"
|
||||
time cargo test $features_test "tests::sgx::"
|
||||
RES=$?
|
||||
|
||||
exit $RES
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
set -x
|
||||
|
||||
source $HOME/.cargo/env
|
||||
source $(dirname "$0")/test-util.sh
|
||||
|
||||
process_common_args "$@"
|
||||
# For now these values are deafult for kvm
|
||||
features_build=""
|
||||
features_test="--features integration_tests"
|
||||
|
||||
BUILD_TARGET="$(uname -m)-unknown-linux-${CH_LIBC}"
|
||||
CFLAGS=""
|
||||
@@ -11,14 +17,14 @@ TARGET_CC="musl-gcc"
|
||||
CFLAGS="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
|
||||
fi
|
||||
|
||||
cargo build --all --release --target $BUILD_TARGET
|
||||
cargo build --all --release $features_build --target $BUILD_TARGET
|
||||
strip target/$BUILD_TARGET/release/cloud-hypervisor
|
||||
|
||||
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 "integration_tests" "tests::windows::$@" -- --test-threads=1
|
||||
time cargo test $features_test "tests::windows::" -- --test-threads=1
|
||||
RES=$?
|
||||
|
||||
exit $RES
|
||||
|
||||
@@ -2,12 +2,20 @@
|
||||
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 deafult for kvm
|
||||
features_build=""
|
||||
features_test="--features integration_tests"
|
||||
|
||||
cp scripts/sha1sums-x86_64 $WORKLOADS_DIR
|
||||
|
||||
FW_URL=$(curl --silent https://api.github.com/repos/cloud-hypervisor/rust-hypervisor-firmware/releases/latest | grep "browser_download_url" | grep -o 'https://.*[^ "]')
|
||||
@@ -137,6 +145,20 @@ if [ ! -f "$VIRTIOFSD" ]; then
|
||||
popd
|
||||
fi
|
||||
|
||||
VIRTIOFSD_RS="$WORKLOADS_DIR/virtiofsd-rs"
|
||||
VIRTIOFSD_RS_DIR="virtiofsd_rs_build"
|
||||
if [ ! -f "$VIRTIOFSD_RS" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
git clone --depth 1 "https://gitlab.com/virtio-fs/virtiofsd-rs.git" $VIRTIOFSD_RS_DIR
|
||||
pushd $VIRTIOFSD_RS_DIR
|
||||
time cargo build --release
|
||||
cp target/release/virtiofsd-rs $VIRTIOFSD_RS || exit 1
|
||||
popd
|
||||
rm -rf $VIRTIOFSD_RS_DIR
|
||||
popd
|
||||
fi
|
||||
|
||||
|
||||
BLK_IMAGE="$WORKLOADS_DIR/blk.img"
|
||||
MNT_DIR="mount_image"
|
||||
if [ ! -f "$BLK_IMAGE" ]; then
|
||||
@@ -201,7 +223,7 @@ TARGET_CC="musl-gcc"
|
||||
CFLAGS="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
|
||||
fi
|
||||
|
||||
cargo build --all --release --target $BUILD_TARGET
|
||||
cargo build --all --release $features_build --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
|
||||
@@ -221,14 +243,14 @@ echo 4096 | sudo tee /proc/sys/vm/nr_hugepages
|
||||
sudo chmod a+rwX /dev/hugepages
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
time cargo test --features "integration_tests" "tests::parallel::$@"
|
||||
time cargo test $features_test "tests::parallel::"
|
||||
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 "integration_tests" "tests::sequential::$@" -- --test-threads=1
|
||||
time cargo test $features_test "tests::sequential::" -- --test-threads=1
|
||||
RES=$?
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
source $HOME/.cargo/env
|
||||
source $(dirname "$0")/test-util.sh
|
||||
|
||||
process_common_args "$@"
|
||||
|
||||
BUILD_TARGET=${BUILD_TARGET-x86_64-unknown-linux-gnu}
|
||||
cargo_args=("$@")
|
||||
cargo_args=("")
|
||||
[ $(uname -m) = "aarch64" ] && cargo_args+=("--no-default-features")
|
||||
[ $(uname -m) = "aarch64" ] && cargo_args+=("--features kvm")
|
||||
|
||||
|
||||
34
scripts/test-util.sh
Normal file
34
scripts/test-util.sh
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
hypervisor="kvm"
|
||||
cmd_help() {
|
||||
echo ""
|
||||
echo "Cloud Hypervisor $(basename $0)"
|
||||
echo "Usage: $(basename $0) [<args>]"
|
||||
echo ""
|
||||
echo "Available arguments:"
|
||||
echo ""
|
||||
echo " --hypervisor Underlying hypervisor. Options kvm, mshv"
|
||||
echo ""
|
||||
echo " --help Display this help message."
|
||||
echo ""
|
||||
}
|
||||
|
||||
process_common_args() {
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--hypervisor")
|
||||
shift
|
||||
hypervisor="$1"
|
||||
;;
|
||||
*)
|
||||
# We only care about hypervisor , do nothing for other arguments
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
if [[ "$hypervisor" != "kvm" ]]; then
|
||||
echo "Hypervisor value must be kvm"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
@@ -38,7 +38,7 @@ impl fmt::Display for Error {
|
||||
use Error::*;
|
||||
match self {
|
||||
ApiClient(e) => e.fmt(f),
|
||||
Connect(e) => write!(f, "Error openning HTTP socket: {}", e),
|
||||
Connect(e) => write!(f, "Error opening HTTP socket: {}", e),
|
||||
InvalidCPUCount(e) => write!(f, "Error parsing CPU count: {}", e),
|
||||
InvalidMemorySize(e) => write!(f, "Error parsing memory size: {:?}", e),
|
||||
InvalidBalloonSize(e) => write!(f, "Error parsing balloon size: {:?}", e),
|
||||
@@ -230,6 +230,32 @@ fn restore_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Erro
|
||||
.map_err(Error::ApiClient)
|
||||
}
|
||||
|
||||
fn receive_migration_api_command(socket: &mut UnixStream, url: &str) -> Result<(), Error> {
|
||||
let receive_migration_data = vmm::api::VmReceiveMigrationData {
|
||||
receiver_url: url.to_owned(),
|
||||
};
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"receive-migration",
|
||||
Some(&serde_json::to_string(&receive_migration_data).unwrap()),
|
||||
)
|
||||
.map_err(Error::ApiClient)
|
||||
}
|
||||
|
||||
fn send_migration_api_command(socket: &mut UnixStream, url: &str) -> Result<(), Error> {
|
||||
let send_migration_data = vmm::api::VmSendMigrationData {
|
||||
destination_url: url.to_owned(),
|
||||
};
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"send-migration",
|
||||
Some(&serde_json::to_string(&send_migration_data).unwrap()),
|
||||
)
|
||||
.map_err(Error::ApiClient)
|
||||
}
|
||||
|
||||
fn do_command(matches: &ArgMatches) -> Result<(), Error> {
|
||||
let mut socket =
|
||||
UnixStream::connect(matches.value_of("api-socket").unwrap()).map_err(Error::Connect)?;
|
||||
@@ -341,6 +367,22 @@ fn do_command(matches: &ArgMatches) -> Result<(), Error> {
|
||||
.value_of("restore_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("send-migration") => send_migration_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("send-migration")
|
||||
.unwrap()
|
||||
.value_of("send_migration_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("receive-migration") => receive_migration_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("receive-migration")
|
||||
.unwrap()
|
||||
.value_of("receive_migration_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some(c) => simple_api_command(&mut socket, "PUT", c, None).map_err(Error::ApiClient),
|
||||
None => unreachable!(),
|
||||
}
|
||||
@@ -484,6 +526,24 @@ fn main() {
|
||||
.index(1)
|
||||
.help(vmm::config::RestoreConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("send-migration")
|
||||
.about("Initiate a VM migration")
|
||||
.arg(
|
||||
Arg::with_name("send_migration_config")
|
||||
.index(1)
|
||||
.help("<destination_url>"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("receive-migration")
|
||||
.about("Receive a VM migration")
|
||||
.arg(
|
||||
Arg::with_name("receive_migration_config")
|
||||
.index(1)
|
||||
.help("<receiver_url>"),
|
||||
),
|
||||
);
|
||||
|
||||
let matches = app.get_matches();
|
||||
|
||||
57
src/main.rs
57
src/main.rs
@@ -18,8 +18,6 @@ use std::env;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use thiserror::Error;
|
||||
use vhost_user_block::start_block_backend;
|
||||
use vhost_user_net::start_net_backend;
|
||||
use vmm::config;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
@@ -27,7 +25,14 @@ use vmm_sys_util::eventfd::EventFd;
|
||||
enum Error {
|
||||
#[error("Failed to create API EventFd: {0}")]
|
||||
CreateAPIEventFd(#[source] std::io::Error),
|
||||
#[error("Failed to open hypervisor interface (is /dev/kvm available?): {0}")]
|
||||
#[cfg_attr(
|
||||
feature = "kvm",
|
||||
error("Failed to open hypervisor interface (is /dev/kvm available?): {0}")
|
||||
)]
|
||||
#[cfg_attr(
|
||||
feature = "mshv",
|
||||
error("Failed to open hypervisor interface (is /dev/mshv available?): {0}")
|
||||
)]
|
||||
CreateHypervisor(#[source] hypervisor::HypervisorError),
|
||||
#[error("Failed to start the VMM thread: {0}")]
|
||||
StartVMMThread(#[source] vmm::Error),
|
||||
@@ -307,22 +312,6 @@ fn create_app<'a, 'b>(
|
||||
.min_values(1)
|
||||
.group("vmm-config"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("net-backend")
|
||||
.long("net-backend")
|
||||
.help(vhost_user_net::SYNTAX)
|
||||
.takes_value(true)
|
||||
.conflicts_with_all(&["block-backend", "kernel"])
|
||||
.min_values(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("block-backend")
|
||||
.long("block-backend")
|
||||
.help(vhost_user_block::SYNTAX)
|
||||
.takes_value(true)
|
||||
.conflicts_with_all(&["net-backend", "kernel"])
|
||||
.min_values(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("seccomp")
|
||||
.long("seccomp")
|
||||
@@ -471,23 +460,17 @@ fn main() {
|
||||
.map(|()| log::set_max_level(log_level))
|
||||
.expect("Expected to be able to setup logger");
|
||||
|
||||
if let Some(backend_command) = cmd_arguments.value_of("net-backend") {
|
||||
start_net_backend(backend_command);
|
||||
} else if let Some(backend_command) = cmd_arguments.value_of("block-backend") {
|
||||
start_block_backend(backend_command);
|
||||
} else {
|
||||
let api_socket_path = cmd_arguments
|
||||
.value_of("api-socket")
|
||||
.expect("Missing argument: api-socket")
|
||||
.to_string();
|
||||
let api_socket_path = cmd_arguments
|
||||
.value_of("api-socket")
|
||||
.expect("Missing argument: api-socket")
|
||||
.to_string();
|
||||
|
||||
if let Err(e) = start_vmm(cmd_arguments, &api_socket_path) {
|
||||
eprintln!("{}", e);
|
||||
std::fs::remove_file(api_socket_path).ok();
|
||||
std::process::exit(1);
|
||||
}
|
||||
if let Err(e) = start_vmm(cmd_arguments, &api_socket_path) {
|
||||
eprintln!("{}", e);
|
||||
std::fs::remove_file(api_socket_path).ok();
|
||||
std::process::exit(1);
|
||||
}
|
||||
std::fs::remove_file(api_socket_path).ok();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -812,14 +795,12 @@ mod unit_tests {
|
||||
"shared=true",
|
||||
"--disk",
|
||||
"vhost_user=true,socket=/tmp/sock1",
|
||||
"path=/path/to/disk/2",
|
||||
],
|
||||
r#"{
|
||||
"kernel": {"path": "/path/to/kernel"},
|
||||
"memory" : { "shared": true, "size": 536870912 },
|
||||
"disks": [
|
||||
{"vhost_user":true, "vhost_socket":"/tmp/sock1"},
|
||||
{"path": "/path/to/disk/2"}
|
||||
{"vhost_user":true, "vhost_socket":"/tmp/sock1"}
|
||||
]
|
||||
}"#,
|
||||
true,
|
||||
@@ -833,14 +814,12 @@ mod unit_tests {
|
||||
"shared=true",
|
||||
"--disk",
|
||||
"vhost_user=true,socket=/tmp/sock1",
|
||||
"path=/path/to/disk/2",
|
||||
],
|
||||
r#"{
|
||||
"kernel": {"path": "/path/to/kernel"},
|
||||
"memory" : { "shared": true, "size": 536870912 },
|
||||
"disks": [
|
||||
{"vhost_user":true, "vhost_socket":"/tmp/sock1"},
|
||||
{"path": "/path/to/disk/2"}
|
||||
{"vhost_user":true, "vhost_socket":"/tmp/sock1"}
|
||||
]
|
||||
}"#,
|
||||
true,
|
||||
|
||||
@@ -31,6 +31,7 @@ mod tests {
|
||||
use std::thread;
|
||||
use tempdir::TempDir;
|
||||
use tempfile::NamedTempFile;
|
||||
#[cfg_attr(target_arch = "aarch64", allow(unused_imports))]
|
||||
use wait_timeout::ChildExt;
|
||||
|
||||
lazy_static! {
|
||||
@@ -348,16 +349,23 @@ mod tests {
|
||||
(child, virtiofsd_socket_path)
|
||||
}
|
||||
|
||||
fn prepare_vhost_user_fs_daemon(
|
||||
fn prepare_virtofsd_rs_daemon(
|
||||
tmp_dir: &TempDir,
|
||||
shared_dir: &str,
|
||||
_cache: &str,
|
||||
) -> (std::process::Child, String) {
|
||||
let mut workload_path = dirs::home_dir().unwrap();
|
||||
workload_path.push("workloads");
|
||||
|
||||
let mut virtiofsd_path = workload_path;
|
||||
virtiofsd_path.push("virtiofsd-rs");
|
||||
let virtiofsd_path = String::from(virtiofsd_path.to_str().unwrap());
|
||||
|
||||
let virtiofsd_socket_path =
|
||||
String::from(tmp_dir.path().join("virtiofs.sock").to_str().unwrap());
|
||||
|
||||
// Start the daemon
|
||||
let child = Command::new(clh_command("vhost_user_fs"))
|
||||
let child = Command::new(virtiofsd_path.as_str())
|
||||
.args(&["--shared-dir", shared_dir])
|
||||
.args(&["--socket", virtiofsd_socket_path.as_str()])
|
||||
.spawn()
|
||||
@@ -385,7 +393,7 @@ mod tests {
|
||||
let vubd_socket_path = String::from(tmp_dir.path().join("vub.sock").to_str().unwrap());
|
||||
|
||||
// Start the daemon
|
||||
let child = Command::new(clh_command("cloud-hypervisor"))
|
||||
let child = Command::new(clh_command("vhost_user_block"))
|
||||
.args(&[
|
||||
"--block-backend",
|
||||
format!(
|
||||
@@ -460,7 +468,7 @@ mod tests {
|
||||
)
|
||||
};
|
||||
|
||||
let child = Command::new(clh_command("cloud-hypervisor"))
|
||||
let child = Command::new(clh_command("vhost_user_net"))
|
||||
.args(&["--net-backend", net_params.as_str()])
|
||||
.spawn()
|
||||
.unwrap();
|
||||
@@ -856,10 +864,20 @@ mod tests {
|
||||
)
|
||||
.expect("Cannot add 'tcp_listener' event to epoll");
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); 1];
|
||||
let num_events = epoll::wait(epoll_fd, timeout * 1000 as i32, &mut events[..])
|
||||
.map_err(Error::EpollWait)?;
|
||||
if num_events == 0 {
|
||||
return Err(Error::EpollWaitTimeout);
|
||||
loop {
|
||||
let num_events =
|
||||
match epoll::wait(epoll_fd, timeout * 1000 as i32, &mut events[..]) {
|
||||
Ok(num_events) => Ok(num_events),
|
||||
Err(e) => match e.raw_os_error() {
|
||||
Some(libc::EAGAIN) | Some(libc::EINTR) => continue,
|
||||
_ => Err(e),
|
||||
},
|
||||
}
|
||||
.map_err(Error::EpollWait)?;
|
||||
if num_events == 0 {
|
||||
return Err(Error::EpollWaitTimeout);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
match listener.accept() {
|
||||
@@ -1289,7 +1307,6 @@ mod tests {
|
||||
tap: Option<&str>,
|
||||
num_queues: usize,
|
||||
prepare_vhost_user_net_daemon: Option<&PrepareNetDaemon>,
|
||||
self_spawned: bool,
|
||||
generate_host_mac: bool,
|
||||
) {
|
||||
let mut focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
@@ -1307,20 +1324,7 @@ mod tests {
|
||||
None
|
||||
};
|
||||
|
||||
let (net_params, daemon_child) = if self_spawned {
|
||||
(
|
||||
format!(
|
||||
"vhost_user=true,mac={},ip={},mask=255.255.255.0,num_queues={},queue_size=1024{}",
|
||||
guest.network.guest_mac, guest.network.host_ip, num_queues,
|
||||
if let Some(host_mac) =host_mac {
|
||||
format!(",host_mac={}", host_mac)
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
),
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
let (net_params, daemon_child) = {
|
||||
let prepare_daemon = prepare_vhost_user_net_daemon.unwrap();
|
||||
// Start the daemon
|
||||
let (daemon_child, vunet_socket_path) =
|
||||
@@ -1456,7 +1460,6 @@ mod tests {
|
||||
readonly: bool,
|
||||
direct: bool,
|
||||
prepare_vhost_user_blk_daemon: Option<&PrepareBlkDaemon>,
|
||||
self_spawned: bool,
|
||||
) {
|
||||
let mut focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
let guest = Guest::new(&mut focal);
|
||||
@@ -1467,19 +1470,7 @@ mod tests {
|
||||
|
||||
let kernel_path = direct_kernel_boot_path().unwrap();
|
||||
|
||||
let (blk_params, daemon_child) = if self_spawned {
|
||||
let mut blk_file_path = workload_path;
|
||||
blk_file_path.push("blk.img");
|
||||
let blk_file_path = String::from(blk_file_path.to_str().unwrap());
|
||||
|
||||
(
|
||||
format!(
|
||||
"vhost_user=true,path={},num_queues={},queue_size=128",
|
||||
blk_file_path, num_queues,
|
||||
),
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
let (blk_params, daemon_child) = {
|
||||
let prepare_daemon = prepare_vhost_user_blk_daemon.unwrap();
|
||||
// Start the daemon
|
||||
let (daemon_child, vubd_socket_path) =
|
||||
@@ -1629,7 +1620,6 @@ mod tests {
|
||||
readonly: bool,
|
||||
direct: bool,
|
||||
prepare_vhost_user_blk_daemon: Option<&PrepareBlkDaemon>,
|
||||
self_spawned: bool,
|
||||
) {
|
||||
let mut focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
let guest = Guest::new(&mut focal);
|
||||
@@ -1644,15 +1634,7 @@ mod tests {
|
||||
.disk(DiskType::RawOperatingSystem)
|
||||
.unwrap();
|
||||
|
||||
let (blk_boot_params, daemon_child) = if self_spawned {
|
||||
(
|
||||
format!(
|
||||
"vhost_user=true,path={},num_queues={},queue_size=128",
|
||||
disk_path, num_queues,
|
||||
),
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
let (blk_boot_params, daemon_child) = {
|
||||
let prepare_daemon = prepare_vhost_user_blk_daemon.unwrap();
|
||||
// Start the daemon
|
||||
let (daemon_child, vubd_socket_path) = prepare_daemon(
|
||||
@@ -1697,28 +1679,6 @@ mod tests {
|
||||
// Just check the VM booted correctly.
|
||||
assert_eq!(guest.get_cpu_count().unwrap_or_default(), num_queues as u32);
|
||||
assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
|
||||
|
||||
if self_spawned {
|
||||
let reboot_count = guest
|
||||
.ssh_command("sudo journalctl | grep -c -- \"-- Reboot --\"")
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.parse::<u32>()
|
||||
.unwrap_or(1);
|
||||
|
||||
assert_eq!(reboot_count, 0);
|
||||
guest.ssh_command("sudo reboot").unwrap_or_default();
|
||||
|
||||
guest.wait_vm_boot(None).unwrap();
|
||||
|
||||
let reboot_count = guest
|
||||
.ssh_command("sudo journalctl | grep -c -- \"-- Reboot --\"")
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.parse::<u32>()
|
||||
.unwrap_or_default();
|
||||
assert_eq!(reboot_count, 1);
|
||||
}
|
||||
});
|
||||
let _ = child.kill();
|
||||
let output = child.wait_with_output().unwrap();
|
||||
@@ -1855,7 +1815,7 @@ mod tests {
|
||||
let desired_ram = 1024 << 20;
|
||||
resize_command(&api_socket, None, Some(desired_ram), None);
|
||||
|
||||
thread::sleep(std::time::Duration::new(10, 0));
|
||||
thread::sleep(std::time::Duration::new(30, 0));
|
||||
assert!(guest.get_total_memory().unwrap_or_default() > 960_000);
|
||||
|
||||
// After the resize, check again that file1 exists and its
|
||||
@@ -2468,6 +2428,8 @@ mod tests {
|
||||
cmd.args(&["--cpus", "boot=48"])
|
||||
.args(&["--memory", "size=5120M"])
|
||||
.args(&["--kernel", guest.fw_path.as_str()])
|
||||
.args(&["--serial", "tty"])
|
||||
.args(&["--console", "off"])
|
||||
.capture_output()
|
||||
.default_disks()
|
||||
.default_net();
|
||||
@@ -2910,8 +2872,7 @@ mod tests {
|
||||
handle_child_output(r, &output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_virtio_blk() {
|
||||
fn _test_virtio_block(disable_io_uring: bool) {
|
||||
let mut focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
let guest = Guest::new(&mut focal);
|
||||
|
||||
@@ -2941,8 +2902,9 @@ mod tests {
|
||||
)
|
||||
.as_str(),
|
||||
format!(
|
||||
"path={},readonly=on,direct=on,num_queues=4",
|
||||
blk_file_path.to_str().unwrap()
|
||||
"path={},readonly=on,direct=on,num_queues=4,_disable_io_uring={}",
|
||||
blk_file_path.to_str().unwrap(),
|
||||
disable_io_uring
|
||||
)
|
||||
.as_str(),
|
||||
])
|
||||
@@ -2994,9 +2956,19 @@ mod tests {
|
||||
handle_child_output(r, &output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_virtio_block() {
|
||||
_test_virtio_block(false)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_virtio_block_disable_io_uring() {
|
||||
_test_virtio_block(false)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vhost_user_net_default() {
|
||||
test_vhost_user_net(None, 2, Some(&prepare_vhost_user_net_daemon), false, false)
|
||||
test_vhost_user_net(None, 2, Some(&prepare_vhost_user_net_daemon), false)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3006,7 +2978,6 @@ mod tests {
|
||||
2,
|
||||
Some(&prepare_vhost_user_net_daemon),
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3017,13 +2988,12 @@ mod tests {
|
||||
2,
|
||||
Some(&prepare_vhost_user_net_daemon),
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vhost_user_net_multiple_queues() {
|
||||
test_vhost_user_net(None, 4, Some(&prepare_vhost_user_net_daemon), false, false)
|
||||
test_vhost_user_net(None, 4, Some(&prepare_vhost_user_net_daemon), false)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3033,60 +3003,37 @@ mod tests {
|
||||
4,
|
||||
Some(&prepare_vhost_user_net_daemon),
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vhost_user_net_self_spawning() {
|
||||
test_vhost_user_net(None, 4, None, true, false)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vhost_user_net_self_spawning_host_mac() {
|
||||
test_vhost_user_net(None, 2, None, true, true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_vhost_user_net_host_mac() {
|
||||
test_vhost_user_net(None, 2, None, true, true)
|
||||
test_vhost_user_net(None, 2, Some(&prepare_vhost_user_net_daemon), true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_vhost_user_blk_default() {
|
||||
test_vhost_user_blk(2, false, false, Some(&prepare_vubd), false)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_vhost_user_blk_self_spawning() {
|
||||
test_vhost_user_blk(1, false, false, None, true)
|
||||
test_vhost_user_blk(2, false, false, Some(&prepare_vubd))
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_vhost_user_blk_readonly() {
|
||||
test_vhost_user_blk(1, true, false, Some(&prepare_vubd), false)
|
||||
test_vhost_user_blk(1, true, false, Some(&prepare_vubd))
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_vhost_user_blk_direct() {
|
||||
test_vhost_user_blk(1, false, true, Some(&prepare_vubd), false)
|
||||
test_vhost_user_blk(1, false, true, Some(&prepare_vubd))
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_boot_from_vhost_user_blk_default() {
|
||||
test_boot_from_vhost_user_blk(1, false, false, Some(&prepare_vubd), false)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_boot_from_vhost_user_blk_self_spawning() {
|
||||
test_boot_from_vhost_user_blk(1, false, false, None, true)
|
||||
test_boot_from_vhost_user_blk(1, false, false, Some(&prepare_vubd))
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3150,24 +3097,24 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_virtio_fs_dax_on_default_cache_size_w_vhost_user_fs_daemon() {
|
||||
test_virtio_fs(true, None, "none", &prepare_vhost_user_fs_daemon, false)
|
||||
fn test_virtio_fs_dax_on_default_cache_size_w_virtiofsd_rs_daemon() {
|
||||
test_virtio_fs(true, None, "none", &prepare_virtofsd_rs_daemon, false)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_virtio_fs_dax_on_cache_size_1_gib_w_vhost_user_fs_daemon() {
|
||||
fn test_virtio_fs_dax_on_cache_size_1_gib_w_virtiofsd_rs_daemon() {
|
||||
test_virtio_fs(
|
||||
true,
|
||||
Some(0x4000_0000),
|
||||
"none",
|
||||
&prepare_vhost_user_fs_daemon,
|
||||
&prepare_virtofsd_rs_daemon,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_virtio_fs_dax_off_w_vhost_user_fs_daemon() {
|
||||
test_virtio_fs(false, None, "none", &prepare_vhost_user_fs_daemon, false)
|
||||
fn test_virtio_fs_dax_off_w_virtiofsd_rs_daemon() {
|
||||
test_virtio_fs(false, None, "none", &prepare_virtofsd_rs_daemon, false)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3184,14 +3131,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_virtio_fs_hotplug_dax_on_w_vhost_user_fs_daemon() {
|
||||
test_virtio_fs(true, None, "none", &prepare_vhost_user_fs_daemon, true)
|
||||
fn test_virtio_fs_hotplug_dax_on_w_virtiofsd_rs_daemon() {
|
||||
test_virtio_fs(true, None, "none", &prepare_virtofsd_rs_daemon, true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_virtio_fs_hotplug_dax_off_w_vhost_user_fs_daemon() {
|
||||
test_virtio_fs(false, None, "none", &prepare_vhost_user_fs_daemon, true)
|
||||
fn test_virtio_fs_hotplug_dax_off_w_virtiofsd_rs_daemon() {
|
||||
test_virtio_fs(false, None, "none", &prepare_virtofsd_rs_daemon, true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3913,7 +3860,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_reboot() {
|
||||
let mut bionic = UbuntuDiskConfig::new(BIONIC_IMAGE_NAME.to_string());
|
||||
let mut focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
@@ -3926,16 +3872,18 @@ mod tests {
|
||||
.for_each(|disk_config| {
|
||||
let guest = Guest::new(*disk_config);
|
||||
|
||||
let mut child = GuestCommand::new(&guest)
|
||||
.args(&["--cpus", "boot=1"])
|
||||
let mut cmd = GuestCommand::new(&guest);
|
||||
cmd.args(&["--cpus", "boot=1"])
|
||||
.args(&["--memory", "size=512M"])
|
||||
.args(&["--kernel", guest.fw_path.as_str()])
|
||||
.default_raw_disks()
|
||||
.default_net()
|
||||
.args(&["--serial", "tty", "--console", "off"])
|
||||
.capture_output()
|
||||
.spawn()
|
||||
.unwrap();
|
||||
.capture_output();
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
cmd.args(&["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]);
|
||||
|
||||
let mut child = cmd.spawn().unwrap();
|
||||
|
||||
let r = std::panic::catch_unwind(|| {
|
||||
guest.wait_vm_boot(Some(120)).unwrap();
|
||||
@@ -5247,6 +5195,7 @@ mod tests {
|
||||
// through each ssh command. There's no need to perform a dedicated test to
|
||||
// verify the migration went well for virtio-net.
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_snapshot_restore() {
|
||||
let mut focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
let guest = Guest::new(&mut focal);
|
||||
@@ -5254,10 +5203,7 @@ mod tests {
|
||||
workload_path.push("workloads");
|
||||
|
||||
let mut kernel_path = workload_path;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
kernel_path.push("bzImage");
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
kernel_path.push("Image");
|
||||
|
||||
let api_socket = temp_api_path(&guest.tmp_dir);
|
||||
|
||||
@@ -5459,7 +5405,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_watchdog() {
|
||||
let mut focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
|
||||
let guest = Guest::new(&mut focal);
|
||||
@@ -5539,7 +5484,7 @@ mod tests {
|
||||
guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap();
|
||||
|
||||
// Allow some time for the watchdog to trigger (max 30s) and reboot to happen
|
||||
thread::sleep(std::time::Duration::new(50, 0));
|
||||
guest.wait_vm_boot(Some(50)).unwrap();
|
||||
|
||||
// Check that watchdog triggered reboot
|
||||
let boot_count = guest
|
||||
@@ -5550,19 +5495,22 @@ mod tests {
|
||||
.unwrap_or_default();
|
||||
assert_eq!(boot_count, 3);
|
||||
|
||||
// Now pause the VM and remain offline for 30s
|
||||
assert!(remote_command(&api_socket, "pause", None));
|
||||
thread::sleep(std::time::Duration::new(30, 0));
|
||||
assert!(remote_command(&api_socket, "resume", None));
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
{
|
||||
// Now pause the VM and remain offline for 30s
|
||||
assert!(remote_command(&api_socket, "pause", None));
|
||||
thread::sleep(std::time::Duration::new(30, 0));
|
||||
assert!(remote_command(&api_socket, "resume", None));
|
||||
|
||||
// Check no reboot
|
||||
let boot_count = guest
|
||||
.ssh_command("sudo journalctl --list-boots | wc -l")
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.parse::<u32>()
|
||||
.unwrap_or_default();
|
||||
assert_eq!(boot_count, 3);
|
||||
// Check no reboot
|
||||
let boot_count = guest
|
||||
.ssh_command("sudo journalctl --list-boots | wc -l")
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.parse::<u32>()
|
||||
.unwrap_or_default();
|
||||
assert_eq!(boot_count, 3);
|
||||
}
|
||||
});
|
||||
|
||||
let _ = child.kill();
|
||||
@@ -5581,10 +5529,10 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod windows {
|
||||
use crate::tests::*;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn windows_auth() -> PasswordAuth {
|
||||
PasswordAuth {
|
||||
username: String::from("administrator"),
|
||||
@@ -5593,7 +5541,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_windows_guest() {
|
||||
let mut workload_path = dirs::home_dir().unwrap();
|
||||
workload_path.push("workloads");
|
||||
@@ -5605,7 +5552,7 @@ mod tests {
|
||||
osdisk_path.push("windows-server-2019.raw");
|
||||
|
||||
let mut child = Command::new(clh_command("cloud-hypervisor"))
|
||||
.args(&["--cpus", "boot=2,kvm_hyperv=on,max_phys_bits=39"])
|
||||
.args(&["--cpus", "boot=2,kvm_hyperv=on"])
|
||||
.args(&["--memory", "size=4G"])
|
||||
.args(&["--kernel", ovmf_path.to_str().unwrap()])
|
||||
.args(&["--disk", &format!("path={}", osdisk_path.to_str().unwrap())])
|
||||
@@ -5645,7 +5592,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_windows_guest_snapshot_restore() {
|
||||
let tmp_dir = TempDir::new("ch").unwrap();
|
||||
let mut workload_path = dirs::home_dir().unwrap();
|
||||
@@ -5661,7 +5607,7 @@ mod tests {
|
||||
|
||||
let mut child = Command::new(clh_command("cloud-hypervisor"))
|
||||
.args(&["--api-socket", &api_socket])
|
||||
.args(&["--cpus", "boot=2,kvm_hyperv=on,max_phys_bits=39"])
|
||||
.args(&["--cpus", "boot=2,kvm_hyperv=on"])
|
||||
.args(&["--memory", "size=4G"])
|
||||
.args(&["--kernel", ovmf_path.to_str().unwrap()])
|
||||
.args(&["--disk", &format!("path={}", osdisk_path.to_str().unwrap())])
|
||||
@@ -5740,11 +5686,11 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod sgx {
|
||||
use crate::tests::*;
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_sgx() {
|
||||
let mut focal = UbuntuDiskConfig::new(FOCAL_SGX_IMAGE_NAME.to_string());
|
||||
let guest = Guest::new(&mut focal);
|
||||
|
||||
@@ -9,10 +9,10 @@ default = []
|
||||
|
||||
[dependencies]
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.80"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
virtio-bindings = "0.1.0"
|
||||
vm-memory = "0.3.0"
|
||||
vm-memory = "0.4.0"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
vhost_rs = { git = "https://github.com/rust-vmm/vhost", branch = "master", package = "vhost", features = ["vhost-user-slave"] }
|
||||
|
||||
@@ -8,12 +8,12 @@ edition = "2018"
|
||||
block_util = { path = "../block_util" }
|
||||
clap = { version = "2.33.3", features=["wrap_help"] }
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.80"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
option_parser = { path = "../option_parser" }
|
||||
qcow = { path = "../qcow" }
|
||||
vhost_user_backend = { path = "../vhost_user_backend" }
|
||||
vhost_rs = { git = "https://github.com/rust-vmm/vhost", branch = "master", package = "vhost", features = ["vhost-user-slave"] }
|
||||
virtio-bindings = "0.1.0"
|
||||
vm-memory = "0.3.0"
|
||||
vm-memory = "0.4.0"
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
@@ -132,7 +132,7 @@ impl VhostUserBlkThread {
|
||||
match Request::parse(&head, mem) {
|
||||
Ok(mut request) => {
|
||||
debug!("element is a valid request");
|
||||
request.set_writeback(self.writeback.load(Ordering::SeqCst));
|
||||
request.set_writeback(self.writeback.load(Ordering::Acquire));
|
||||
let status = match request.execute(
|
||||
&mut self.disk_image.lock().unwrap().deref_mut(),
|
||||
self.disk_nsectors,
|
||||
@@ -273,7 +273,7 @@ impl VhostUserBlkBackend {
|
||||
"writethrough"
|
||||
}
|
||||
);
|
||||
self.writeback.store(writeback, Ordering::SeqCst);
|
||||
self.writeback.store(writeback, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,7 +455,7 @@ impl VhostUserBlkBackendConfig {
|
||||
let poll_queue = parser
|
||||
.convert::<Toggle>("poll_queue")
|
||||
.map_err(Error::FailedConfigParse)?
|
||||
.unwrap_or_else(|| Toggle(true))
|
||||
.unwrap_or(Toggle(true))
|
||||
.0;
|
||||
let queue_size = parser
|
||||
.convert("queue_size")
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "vhost_user_fs"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.1.0"
|
||||
clap = { version = "2.33.3", features=["wrap_help"] }
|
||||
epoll = ">=4.0.1"
|
||||
futures = { version = "0.3.7", features = ["thread-pool"] }
|
||||
libc = "0.2.80"
|
||||
log = "0.4.11"
|
||||
# Match the version in vmm
|
||||
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.22.0" }
|
||||
tempdir = "0.3.7"
|
||||
virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
|
||||
virtio-devices = { path = "../virtio-devices" }
|
||||
vhost_rs = { git = "https://github.com/rust-vmm/vhost", branch = "master", package = "vhost", features = ["vhost-user-slave"] }
|
||||
vhost_user_backend = { path = "../vhost_user_backend"}
|
||||
vm-memory = "0.3.0"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = "0.6.1"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,409 +0,0 @@
|
||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Error, ErrorKind, Result};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use vm_memory::VolatileSlice;
|
||||
|
||||
use libc::{
|
||||
c_int, c_void, off64_t, pread64, preadv64, pwrite64, pwritev64, read, readv, size_t, write,
|
||||
writev,
|
||||
};
|
||||
|
||||
/// A trait for setting the size of a file.
|
||||
/// This is equivalent to File's `set_len` method, but
|
||||
/// wrapped in a trait so that it can be implemented for
|
||||
/// other types.
|
||||
pub trait FileSetLen {
|
||||
// Set the size of this file.
|
||||
// This is the moral equivalent of `ftruncate()`.
|
||||
fn set_len(&self, _len: u64) -> Result<()>;
|
||||
}
|
||||
|
||||
impl FileSetLen for File {
|
||||
fn set_len(&self, len: u64) -> Result<()> {
|
||||
File::set_len(self, len)
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait similar to `Read` and `Write`, but uses volatile memory as buffers.
|
||||
pub trait FileReadWriteVolatile {
|
||||
/// Read bytes from this file into the given slice, returning the number of bytes read on
|
||||
/// success.
|
||||
fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize>;
|
||||
|
||||
/// Like `read_volatile`, except it reads to a slice of buffers. Data is copied to fill each
|
||||
/// buffer in order, with the final buffer written to possibly being only partially filled. This
|
||||
/// method must behave as a single call to `read_volatile` with the buffers concatenated would.
|
||||
/// The default implementation calls `read_volatile` with either the first nonempty buffer
|
||||
/// provided, or returns `Ok(0)` if none exists.
|
||||
fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
|
||||
bufs.iter()
|
||||
.find(|b| !b.is_empty())
|
||||
.map(|&b| self.read_volatile(b))
|
||||
.unwrap_or(Ok(0))
|
||||
}
|
||||
|
||||
/// Reads bytes from this into the given slice until all bytes in the slice are written, or an
|
||||
/// error is returned.
|
||||
fn read_exact_volatile(&mut self, mut slice: VolatileSlice) -> Result<()> {
|
||||
while !slice.is_empty() {
|
||||
let bytes_read = self.read_volatile(slice)?;
|
||||
if bytes_read == 0 {
|
||||
return Err(Error::from(ErrorKind::UnexpectedEof));
|
||||
}
|
||||
// Will panic if read_volatile read more bytes than we gave it, which would be worthy of
|
||||
// a panic.
|
||||
slice = slice.offset(bytes_read).unwrap();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write bytes from the slice to the given file, returning the number of bytes written on
|
||||
/// success.
|
||||
fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize>;
|
||||
|
||||
/// Like `write_volatile`, except that it writes from a slice of buffers. Data is copied from
|
||||
/// each buffer in order, with the final buffer read from possibly being only partially
|
||||
/// consumed. This method must behave as a call to `write_volatile` with the buffers
|
||||
/// concatenated would. The default implementation calls `write_volatile` with either the first
|
||||
/// nonempty buffer provided, or returns `Ok(0)` if none exists.
|
||||
fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
|
||||
bufs.iter()
|
||||
.find(|b| !b.is_empty())
|
||||
.map(|&b| self.write_volatile(b))
|
||||
.unwrap_or(Ok(0))
|
||||
}
|
||||
|
||||
/// Write bytes from the slice to the given file until all the bytes from the slice have been
|
||||
/// written, or an error is returned.
|
||||
fn write_all_volatile(&mut self, mut slice: VolatileSlice) -> Result<()> {
|
||||
while !slice.is_empty() {
|
||||
let bytes_written = self.write_volatile(slice)?;
|
||||
if bytes_written == 0 {
|
||||
return Err(Error::from(ErrorKind::WriteZero));
|
||||
}
|
||||
// Will panic if read_volatile read more bytes than we gave it, which would be worthy of
|
||||
// a panic.
|
||||
slice = slice.offset(bytes_written).unwrap();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: FileReadWriteVolatile + ?Sized> FileReadWriteVolatile for &'a mut T {
|
||||
fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
|
||||
(**self).read_volatile(slice)
|
||||
}
|
||||
|
||||
fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
|
||||
(**self).read_vectored_volatile(bufs)
|
||||
}
|
||||
|
||||
fn read_exact_volatile(&mut self, slice: VolatileSlice) -> Result<()> {
|
||||
(**self).read_exact_volatile(slice)
|
||||
}
|
||||
|
||||
fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
|
||||
(**self).write_volatile(slice)
|
||||
}
|
||||
|
||||
fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
|
||||
(**self).write_vectored_volatile(bufs)
|
||||
}
|
||||
|
||||
fn write_all_volatile(&mut self, slice: VolatileSlice) -> Result<()> {
|
||||
(**self).write_all_volatile(slice)
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait similar to the unix `ReadExt` and `WriteExt` traits, but for volatile memory.
|
||||
pub trait FileReadWriteAtVolatile {
|
||||
/// Reads bytes from this file at `offset` into the given slice, returning the number of bytes
|
||||
/// read on success.
|
||||
fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize>;
|
||||
|
||||
/// Like `read_at_volatile`, except it reads to a slice of buffers. Data is copied to fill each
|
||||
/// buffer in order, with the final buffer written to possibly being only partially filled. This
|
||||
/// method must behave as a single call to `read_at_volatile` with the buffers concatenated
|
||||
/// would. The default implementation calls `read_at_volatile` with either the first nonempty
|
||||
/// buffer provided, or returns `Ok(0)` if none exists.
|
||||
fn read_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
|
||||
if let Some(&slice) = bufs.first() {
|
||||
self.read_at_volatile(slice, offset)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads bytes from this file at `offset` into the given slice until all bytes in the slice are
|
||||
/// read, or an error is returned.
|
||||
fn read_exact_at_volatile(&mut self, mut slice: VolatileSlice, mut offset: u64) -> Result<()> {
|
||||
while !slice.is_empty() {
|
||||
match self.read_at_volatile(slice, offset) {
|
||||
Ok(0) => return Err(Error::from(ErrorKind::UnexpectedEof)),
|
||||
Ok(n) => {
|
||||
slice = slice.offset(n).unwrap();
|
||||
offset = offset.checked_add(n as u64).unwrap();
|
||||
}
|
||||
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes bytes from this file at `offset` into the given slice, returning the number of bytes
|
||||
/// written on success.
|
||||
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize>;
|
||||
|
||||
/// Like `write_at_at_volatile`, except that it writes from a slice of buffers. Data is copied
|
||||
/// from each buffer in order, with the final buffer read from possibly being only partially
|
||||
/// consumed. This method must behave as a call to `write_at_volatile` with the buffers
|
||||
/// concatenated would. The default implementation calls `write_at_volatile` with either the
|
||||
/// first nonempty buffer provided, or returns `Ok(0)` if none exists.
|
||||
fn write_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
|
||||
if let Some(&slice) = bufs.first() {
|
||||
self.write_at_volatile(slice, offset)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes bytes from this file at `offset` into the given slice until all bytes in the slice
|
||||
/// are written, or an error is returned.
|
||||
fn write_all_at_volatile(&mut self, mut slice: VolatileSlice, mut offset: u64) -> Result<()> {
|
||||
while !slice.is_empty() {
|
||||
match self.write_at_volatile(slice, offset) {
|
||||
Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
|
||||
Ok(n) => {
|
||||
slice = slice.offset(n).unwrap();
|
||||
offset = offset.checked_add(n as u64).unwrap();
|
||||
}
|
||||
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: FileReadWriteAtVolatile + ?Sized> FileReadWriteAtVolatile for &'a mut T {
|
||||
fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
|
||||
(**self).read_at_volatile(slice, offset)
|
||||
}
|
||||
|
||||
fn read_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
|
||||
(**self).read_vectored_at_volatile(bufs, offset)
|
||||
}
|
||||
|
||||
fn read_exact_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<()> {
|
||||
(**self).read_exact_at_volatile(slice, offset)
|
||||
}
|
||||
|
||||
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
|
||||
(**self).write_at_volatile(slice, offset)
|
||||
}
|
||||
|
||||
fn write_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
|
||||
(**self).write_vectored_at_volatile(bufs, offset)
|
||||
}
|
||||
|
||||
fn write_all_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<()> {
|
||||
(**self).write_all_at_volatile(slice, offset)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! volatile_impl {
|
||||
($ty:ty) => {
|
||||
impl FileReadWriteVolatile for $ty {
|
||||
fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
|
||||
// Safe because only bytes inside the slice are accessed and the kernel is expected
|
||||
// to handle arbitrary memory for I/O.
|
||||
let ret =
|
||||
unsafe { read(self.as_raw_fd(), slice.as_ptr() as *mut c_void, slice.len()) };
|
||||
if ret >= 0 {
|
||||
Ok(ret as usize)
|
||||
} else {
|
||||
Err(Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
|
||||
let iovecs: Vec<libc::iovec> = bufs
|
||||
.iter()
|
||||
.map(|s| libc::iovec {
|
||||
iov_base: s.as_ptr() as *mut c_void,
|
||||
iov_len: s.len() as size_t,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if iovecs.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Safe because only bytes inside the buffers are accessed and the kernel is
|
||||
// expected to handle arbitrary memory for I/O.
|
||||
let ret = unsafe { readv(self.as_raw_fd(), &iovecs[0], iovecs.len() as c_int) };
|
||||
if ret >= 0 {
|
||||
Ok(ret as usize)
|
||||
} else {
|
||||
Err(Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
|
||||
// Safe because only bytes inside the slice are accessed and the kernel is expected
|
||||
// to handle arbitrary memory for I/O.
|
||||
let ret = unsafe {
|
||||
write(
|
||||
self.as_raw_fd(),
|
||||
slice.as_ptr() as *const c_void,
|
||||
slice.len(),
|
||||
)
|
||||
};
|
||||
if ret >= 0 {
|
||||
Ok(ret as usize)
|
||||
} else {
|
||||
Err(Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
|
||||
let iovecs: Vec<libc::iovec> = bufs
|
||||
.iter()
|
||||
.map(|s| libc::iovec {
|
||||
iov_base: s.as_ptr() as *mut c_void,
|
||||
iov_len: s.len() as size_t,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if iovecs.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Safe because only bytes inside the buffers are accessed and the kernel is
|
||||
// expected to handle arbitrary memory for I/O.
|
||||
let ret = unsafe { writev(self.as_raw_fd(), &iovecs[0], iovecs.len() as c_int) };
|
||||
if ret >= 0 {
|
||||
Ok(ret as usize)
|
||||
} else {
|
||||
Err(Error::last_os_error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileReadWriteAtVolatile for $ty {
|
||||
fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
|
||||
// Safe because only bytes inside the slice are accessed and the kernel is expected
|
||||
// to handle arbitrary memory for I/O.
|
||||
let ret = unsafe {
|
||||
pread64(
|
||||
self.as_raw_fd(),
|
||||
slice.as_ptr() as *mut c_void,
|
||||
slice.len(),
|
||||
offset as off64_t,
|
||||
)
|
||||
};
|
||||
|
||||
if ret >= 0 {
|
||||
Ok(ret as usize)
|
||||
} else {
|
||||
Err(Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
fn read_vectored_at_volatile(
|
||||
&mut self,
|
||||
bufs: &[VolatileSlice],
|
||||
offset: u64,
|
||||
) -> Result<usize> {
|
||||
let iovecs: Vec<libc::iovec> = bufs
|
||||
.iter()
|
||||
.map(|s| libc::iovec {
|
||||
iov_base: s.as_ptr() as *mut c_void,
|
||||
iov_len: s.len() as size_t,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if iovecs.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Safe because only bytes inside the buffers are accessed and the kernel is
|
||||
// expected to handle arbitrary memory for I/O.
|
||||
let ret = unsafe {
|
||||
preadv64(
|
||||
self.as_raw_fd(),
|
||||
&iovecs[0],
|
||||
iovecs.len() as c_int,
|
||||
offset as off64_t,
|
||||
)
|
||||
};
|
||||
if ret >= 0 {
|
||||
Ok(ret as usize)
|
||||
} else {
|
||||
Err(Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
|
||||
// Safe because only bytes inside the slice are accessed and the kernel is expected
|
||||
// to handle arbitrary memory for I/O.
|
||||
let ret = unsafe {
|
||||
pwrite64(
|
||||
self.as_raw_fd(),
|
||||
slice.as_ptr() as *const c_void,
|
||||
slice.len(),
|
||||
offset as off64_t,
|
||||
)
|
||||
};
|
||||
|
||||
if ret >= 0 {
|
||||
Ok(ret as usize)
|
||||
} else {
|
||||
Err(Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_vectored_at_volatile(
|
||||
&mut self,
|
||||
bufs: &[VolatileSlice],
|
||||
offset: u64,
|
||||
) -> Result<usize> {
|
||||
let iovecs: Vec<libc::iovec> = bufs
|
||||
.iter()
|
||||
.map(|s| libc::iovec {
|
||||
iov_base: s.as_ptr() as *mut c_void,
|
||||
iov_len: s.len() as size_t,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if iovecs.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Safe because only bytes inside the buffers are accessed and the kernel is
|
||||
// expected to handle arbitrary memory for I/O.
|
||||
let ret = unsafe {
|
||||
pwritev64(
|
||||
self.as_raw_fd(),
|
||||
&iovecs[0],
|
||||
iovecs.len() as c_int,
|
||||
offset as off64_t,
|
||||
)
|
||||
};
|
||||
if ret >= 0 {
|
||||
Ok(ret as usize)
|
||||
} else {
|
||||
Err(Error::last_os_error())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
volatile_impl!(File);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,62 +0,0 @@
|
||||
use crate::fuse;
|
||||
use std::io;
|
||||
use std::os::unix::io::RawFd;
|
||||
use vhost_rs::vhost_user::message::{
|
||||
VhostUserFSSlaveMsg, VhostUserFSSlaveMsgFlags, VHOST_USER_FS_SLAVE_ENTRIES,
|
||||
};
|
||||
use vhost_rs::vhost_user::{SlaveFsCacheReq, VhostUserMasterReqHandler};
|
||||
|
||||
/// Trait for virtio-fs cache requests operations. This is mainly used to hide
|
||||
/// vhost-user details from virtio-fs's fuse part.
|
||||
pub trait FsCacheReqHandler: Send + Sync + 'static {
|
||||
/// Setup a dedicated mapping so that guest can access file data in DAX style.
|
||||
fn map(
|
||||
&mut self,
|
||||
foffset: u64,
|
||||
moffset: u64,
|
||||
len: u64,
|
||||
flags: u64,
|
||||
fd: RawFd,
|
||||
) -> io::Result<()>;
|
||||
|
||||
/// Remove those mappings that provide the access to file data.
|
||||
fn unmap(&mut self, requests: Vec<fuse::RemovemappingOne>) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl FsCacheReqHandler for SlaveFsCacheReq {
|
||||
fn map(
|
||||
&mut self,
|
||||
foffset: u64,
|
||||
moffset: u64,
|
||||
len: u64,
|
||||
flags: u64,
|
||||
fd: RawFd,
|
||||
) -> io::Result<()> {
|
||||
let mut msg: VhostUserFSSlaveMsg = Default::default();
|
||||
msg.fd_offset[0] = foffset;
|
||||
msg.cache_offset[0] = moffset;
|
||||
msg.len[0] = len;
|
||||
msg.flags[0] = if (flags & fuse::SetupmappingFlags::WRITE.bits()) != 0 {
|
||||
VhostUserFSSlaveMsgFlags::MAP_W | VhostUserFSSlaveMsgFlags::MAP_R
|
||||
} else {
|
||||
VhostUserFSSlaveMsgFlags::MAP_R
|
||||
};
|
||||
|
||||
self.fs_slave_map(&msg, fd)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unmap(&mut self, requests: Vec<fuse::RemovemappingOne>) -> io::Result<()> {
|
||||
for chunk in requests.chunks(VHOST_USER_FS_SLAVE_ENTRIES) {
|
||||
let mut msg: VhostUserFSSlaveMsg = Default::default();
|
||||
|
||||
for (ind, req) in chunk.iter().enumerate() {
|
||||
msg.len[ind] = req.len;
|
||||
msg.cache_offset[ind] = req.moffset;
|
||||
}
|
||||
|
||||
self.fs_slave_unmap(&msg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,60 +0,0 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
pub mod descriptor_utils;
|
||||
pub mod file_traits;
|
||||
pub mod filesystem;
|
||||
pub mod fs_cache_req_handler;
|
||||
pub mod fuse;
|
||||
pub mod multikey;
|
||||
pub mod passthrough;
|
||||
pub mod sandbox;
|
||||
pub mod seccomp;
|
||||
pub mod server;
|
||||
|
||||
use std::ffi::FromBytesWithNulError;
|
||||
use std::{error, fmt, io};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to decode protocol messages.
|
||||
DecodeMessage(io::Error),
|
||||
/// Failed to encode protocol messages.
|
||||
EncodeMessage(io::Error),
|
||||
/// One or more parameters are missing.
|
||||
MissingParameter,
|
||||
/// A C string parameter is invalid.
|
||||
InvalidCString(FromBytesWithNulError),
|
||||
/// The `len` field of the header is too small.
|
||||
InvalidHeaderLength,
|
||||
/// The `size` field of the `SetxattrIn` message does not match the length
|
||||
/// of the decoded value.
|
||||
InvalidXattrSize((u32, usize)),
|
||||
}
|
||||
|
||||
impl error::Error for Error {}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use Error::*;
|
||||
match self {
|
||||
DecodeMessage(err) => write!(f, "failed to decode fuse message: {}", err),
|
||||
EncodeMessage(err) => write!(f, "failed to encode fuse message: {}", err),
|
||||
MissingParameter => write!(f, "one or more parameters are missing"),
|
||||
InvalidHeaderLength => write!(f, "the `len` field of the header is too small"),
|
||||
InvalidCString(err) => write!(f, "a c string parameter is invalid: {}", err),
|
||||
InvalidXattrSize((size, len)) => write!(
|
||||
f,
|
||||
"The `size` field of the `SetxattrIn` message does not match the length of the\
|
||||
decoded value: size = {}, value.len() = {}",
|
||||
size, len
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
@@ -1,423 +0,0 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause)
|
||||
|
||||
#[macro_use(crate_version, crate_authors)]
|
||||
extern crate clap;
|
||||
extern crate log;
|
||||
extern crate vhost_rs;
|
||||
extern crate vhost_user_backend;
|
||||
extern crate virtio_devices;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use futures::executor::{ThreadPool, ThreadPoolBuilder};
|
||||
use libc::EFD_NONBLOCK;
|
||||
use log::*;
|
||||
use seccomp::SeccompAction;
|
||||
use std::num::Wrapping;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::{convert, error, fmt, io, process};
|
||||
|
||||
use vhost_rs::vhost_user::message::*;
|
||||
use vhost_rs::vhost_user::{Listener, SlaveFsCacheReq};
|
||||
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring};
|
||||
use vhost_user_fs::descriptor_utils::Error as VufDescriptorError;
|
||||
use vhost_user_fs::descriptor_utils::{Reader, Writer};
|
||||
use vhost_user_fs::filesystem::FileSystem;
|
||||
use vhost_user_fs::passthrough::{self, PassthroughFs};
|
||||
use vhost_user_fs::sandbox::Sandbox;
|
||||
use vhost_user_fs::seccomp::enable_seccomp;
|
||||
use vhost_user_fs::server::Server;
|
||||
use vhost_user_fs::Error as VhostUserFsError;
|
||||
use virtio_bindings::bindings::virtio_net::*;
|
||||
use virtio_bindings::bindings::virtio_ring::{
|
||||
VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC,
|
||||
};
|
||||
use vm_memory::{GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vm_virtio::queue::DescriptorChain;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: usize = 1024;
|
||||
const NUM_QUEUES: usize = 2;
|
||||
const THREAD_POOL_SIZE: usize = 64;
|
||||
|
||||
// The guest queued an available buffer for the high priority queue.
|
||||
const HIPRIO_QUEUE_EVENT: u16 = 0;
|
||||
// The guest queued an available buffer for the request queue.
|
||||
const REQ_QUEUE_EVENT: u16 = 1;
|
||||
// The device has been dropped.
|
||||
const KILL_EVENT: u16 = 2;
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
/// Failed to create kill eventfd.
|
||||
CreateKillEventFd(io::Error),
|
||||
/// Failed to create thread pool.
|
||||
CreateThreadPool(io::Error),
|
||||
/// Failed to handle event other than input event.
|
||||
HandleEventNotEpollIn,
|
||||
/// Failed to handle unknown event.
|
||||
HandleEventUnknownEvent,
|
||||
/// No memory configured.
|
||||
NoMemoryConfigured,
|
||||
/// Processing queue failed.
|
||||
ProcessQueue(VhostUserFsError),
|
||||
/// Creating a queue reader failed.
|
||||
QueueReader(VufDescriptorError),
|
||||
/// Creating a queue writer failed.
|
||||
QueueWriter(VufDescriptorError),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "vhost_user_fs_error: {:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {}
|
||||
|
||||
impl convert::From<Error> for io::Error {
|
||||
fn from(e: Error) -> Self {
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
}
|
||||
}
|
||||
|
||||
struct VhostUserFsThread<F: FileSystem + Send + Sync + 'static> {
|
||||
mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
kill_evt: EventFd,
|
||||
server: Arc<Server<F>>,
|
||||
// handle request from slave to master
|
||||
vu_req: Option<SlaveFsCacheReq>,
|
||||
event_idx: bool,
|
||||
pool: ThreadPool,
|
||||
}
|
||||
|
||||
impl<F: FileSystem + Send + Sync + 'static> Clone for VhostUserFsThread<F> {
|
||||
fn clone(&self) -> Self {
|
||||
VhostUserFsThread {
|
||||
mem: self.mem.clone(),
|
||||
kill_evt: self.kill_evt.try_clone().unwrap(),
|
||||
server: self.server.clone(),
|
||||
vu_req: self.vu_req.clone(),
|
||||
event_idx: self.event_idx,
|
||||
pool: self.pool.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsThread<F> {
|
||||
fn new(fs: F, thread_pool_size: usize) -> Result<Self> {
|
||||
Ok(VhostUserFsThread {
|
||||
mem: None,
|
||||
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
|
||||
server: Arc::new(Server::new(fs)),
|
||||
vu_req: None,
|
||||
event_idx: false,
|
||||
pool: ThreadPoolBuilder::new()
|
||||
.pool_size(thread_pool_size)
|
||||
.create()
|
||||
.map_err(Error::CreateThreadPool)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn process_queue(&mut self, vring_lock: Arc<RwLock<Vring>>) -> Result<bool> {
|
||||
let mut used_any = false;
|
||||
let (atomic_mem, mem) = match &self.mem {
|
||||
Some(m) => (m, m.memory()),
|
||||
None => return Err(Error::NoMemoryConfigured),
|
||||
};
|
||||
let mut vring = vring_lock.write().unwrap();
|
||||
|
||||
while let Some(avail_desc) = vring.mut_queue().iter(&mem).next() {
|
||||
used_any = true;
|
||||
|
||||
// Prepare a set of objects that can be moved to the worker thread.
|
||||
let desc_head = avail_desc.get_head();
|
||||
let atomic_mem = atomic_mem.clone();
|
||||
let server = self.server.clone();
|
||||
let mut vu_req = self.vu_req.clone();
|
||||
let event_idx = self.event_idx;
|
||||
let vring_lock = vring_lock.clone();
|
||||
|
||||
self.pool.spawn_ok(async move {
|
||||
let mem = atomic_mem.memory();
|
||||
let desc = DescriptorChain::new_from_head(&mem, desc_head).unwrap();
|
||||
let head_index = desc.index;
|
||||
|
||||
let reader = Reader::new(&mem, desc.clone())
|
||||
.map_err(Error::QueueReader)
|
||||
.unwrap();
|
||||
let writer = Writer::new(&mem, desc.clone())
|
||||
.map_err(Error::QueueWriter)
|
||||
.unwrap();
|
||||
|
||||
server
|
||||
.handle_message(reader, writer, vu_req.as_mut())
|
||||
.map_err(Error::ProcessQueue)
|
||||
.unwrap();
|
||||
|
||||
let mut vring = vring_lock.write().unwrap();
|
||||
|
||||
if event_idx {
|
||||
let queue = vring.mut_queue();
|
||||
if let Some(used_idx) = queue.add_used(&mem, head_index, 0) {
|
||||
if queue.needs_notification(&mem, Wrapping(used_idx)) {
|
||||
vring.signal_used_queue().unwrap();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
vring.mut_queue().add_used(&mem, head_index, 0);
|
||||
vring.signal_used_queue().unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(used_any)
|
||||
}
|
||||
}
|
||||
|
||||
struct VhostUserFsBackend<F: FileSystem + Send + Sync + 'static> {
|
||||
thread: Mutex<VhostUserFsThread<F>>,
|
||||
}
|
||||
|
||||
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> {
|
||||
fn new(fs: F, thread_pool_size: usize) -> Result<Self> {
|
||||
let thread = Mutex::new(VhostUserFsThread::new(fs, thread_pool_size)?);
|
||||
Ok(VhostUserFsBackend { thread })
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBackend<F> {
|
||||
fn num_queues(&self) -> usize {
|
||||
NUM_QUEUES
|
||||
}
|
||||
|
||||
fn max_queue_size(&self) -> usize {
|
||||
QUEUE_SIZE
|
||||
}
|
||||
|
||||
fn features(&self) -> u64 {
|
||||
1 << VIRTIO_F_VERSION_1
|
||||
| 1 << VIRTIO_RING_F_INDIRECT_DESC
|
||||
| 1 << VIRTIO_RING_F_EVENT_IDX
|
||||
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
|
||||
}
|
||||
|
||||
fn protocol_features(&self) -> VhostUserProtocolFeatures {
|
||||
VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::SLAVE_REQ
|
||||
}
|
||||
|
||||
fn set_event_idx(&mut self, enabled: bool) {
|
||||
self.thread.lock().unwrap().event_idx = enabled;
|
||||
}
|
||||
|
||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
||||
self.thread.lock().unwrap().mem = Some(GuestMemoryAtomic::new(mem));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&self,
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
vrings: &[Arc<RwLock<Vring>>],
|
||||
_thread_id: usize,
|
||||
) -> VhostUserBackendResult<bool> {
|
||||
if evset != epoll::Events::EPOLLIN {
|
||||
return Err(Error::HandleEventNotEpollIn.into());
|
||||
}
|
||||
|
||||
let mut thread = self.thread.lock().unwrap();
|
||||
let mem = match &thread.mem {
|
||||
Some(m) => m.memory(),
|
||||
None => return Err(Error::NoMemoryConfigured.into()),
|
||||
};
|
||||
|
||||
let vring_lock = match device_event {
|
||||
HIPRIO_QUEUE_EVENT => {
|
||||
debug!("HIPRIO_QUEUE_EVENT");
|
||||
vrings[0].clone()
|
||||
}
|
||||
REQ_QUEUE_EVENT => {
|
||||
debug!("QUEUE_EVENT");
|
||||
vrings[1].clone()
|
||||
}
|
||||
_ => return Err(Error::HandleEventUnknownEvent.into()),
|
||||
};
|
||||
|
||||
if thread.event_idx {
|
||||
// vm-virtio's Queue implementation only checks avail_index
|
||||
// once, so to properly support EVENT_IDX we need to keep
|
||||
// calling process_queue() until it stops finding new
|
||||
// requests on the queue.
|
||||
loop {
|
||||
{
|
||||
let mut vring = vring_lock.write().unwrap();
|
||||
vring.mut_queue().update_avail_event(&mem);
|
||||
}
|
||||
if !thread.process_queue(vring_lock.clone())? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Without EVENT_IDX, a single call is enough.
|
||||
thread.process_queue(vring_lock)?;
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, Option<u16>)> {
|
||||
Some((
|
||||
self.thread.lock().unwrap().kill_evt.try_clone().unwrap(),
|
||||
Some(KILL_EVENT),
|
||||
))
|
||||
}
|
||||
|
||||
fn set_slave_req_fd(&mut self, vu_req: SlaveFsCacheReq) {
|
||||
self.thread.lock().unwrap().vu_req = Some(vu_req);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cmd_arguments = App::new("vhost-user-fs backend")
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.about("Launch a vhost-user-fs backend.")
|
||||
.arg(
|
||||
Arg::with_name("shared-dir")
|
||||
.long("shared-dir")
|
||||
.help("Shared directory path")
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("sock")
|
||||
.long("sock")
|
||||
.help("vhost-user socket path (deprecated)")
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("socket")
|
||||
.long("socket")
|
||||
.help("vhost-user socket path")
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("thread-pool-size")
|
||||
.long("thread-pool-size")
|
||||
.help("thread pool size (default 64)")
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("disable-xattr")
|
||||
.long("disable-xattr")
|
||||
.help("Disable support for extended attributes"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("disable-sandbox")
|
||||
.long("disable-sandbox")
|
||||
.help("Don't set up a sandbox for the daemon"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("seccomp")
|
||||
.long("seccomp")
|
||||
.help("Disable/debug seccomp security")
|
||||
.possible_values(&["kill", "log", "trap", "none"])
|
||||
.default_value("kill"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
// Retrieve arguments
|
||||
let shared_dir = cmd_arguments
|
||||
.value_of("shared-dir")
|
||||
.expect("Failed to retrieve shared directory path");
|
||||
let socket = match cmd_arguments.value_of("socket") {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
println!("warning: use of deprecated parameter '--sock': Please use the '--socket' option instead.");
|
||||
cmd_arguments
|
||||
.value_of("sock")
|
||||
.expect("Failed to retrieve vhost-user socket path")
|
||||
}
|
||||
};
|
||||
|
||||
let thread_pool_size: usize = match cmd_arguments.value_of("thread-pool-size") {
|
||||
Some(size) => size.parse().expect("Invalid argument for thread-pool-size"),
|
||||
None => THREAD_POOL_SIZE,
|
||||
};
|
||||
let xattr: bool = !cmd_arguments.is_present("disable-xattr");
|
||||
let create_sandbox: bool = !cmd_arguments.is_present("disable-sandbox");
|
||||
let seccomp_mode: SeccompAction = match cmd_arguments.value_of("seccomp").unwrap() {
|
||||
"none" => SeccompAction::Allow, // i.e. no seccomp
|
||||
"kill" => SeccompAction::Kill,
|
||||
"log" => SeccompAction::Log,
|
||||
"trap" => SeccompAction::Trap,
|
||||
_ => unreachable!(), // We told Arg possible_values
|
||||
};
|
||||
|
||||
let listener = Listener::new(socket, true).unwrap();
|
||||
|
||||
let fs_cfg = if create_sandbox {
|
||||
let mut sandbox = Sandbox::new(shared_dir.to_string());
|
||||
match sandbox.enter().unwrap() {
|
||||
Some(child_pid) => {
|
||||
unsafe { libc::waitpid(child_pid, std::ptr::null_mut(), 0) };
|
||||
return;
|
||||
}
|
||||
None => passthrough::Config {
|
||||
root_dir: "/".to_string(),
|
||||
xattr,
|
||||
proc_sfd_rawfd: sandbox.get_proc_self_fd(),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
} else {
|
||||
passthrough::Config {
|
||||
root_dir: shared_dir.to_string(),
|
||||
xattr,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
|
||||
// Must happen before we start the thread pool
|
||||
if seccomp_mode != SeccompAction::Allow {
|
||||
enable_seccomp(seccomp_mode).unwrap();
|
||||
};
|
||||
|
||||
let fs = PassthroughFs::new(fs_cfg).unwrap();
|
||||
let fs_backend = Arc::new(RwLock::new(
|
||||
VhostUserFsBackend::new(fs, thread_pool_size).unwrap(),
|
||||
));
|
||||
|
||||
let mut daemon =
|
||||
VhostUserDaemon::new(String::from("vhost-user-fs-backend"), fs_backend.clone()).unwrap();
|
||||
|
||||
if let Err(e) = daemon.start(listener) {
|
||||
error!("Failed to start daemon: {:?}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
if let Err(e) = daemon.wait() {
|
||||
error!("Waiting for daemon failed: {:?}", e);
|
||||
}
|
||||
|
||||
let kill_evt = fs_backend
|
||||
.read()
|
||||
.unwrap()
|
||||
.thread
|
||||
.lock()
|
||||
.unwrap()
|
||||
.kill_evt
|
||||
.try_clone()
|
||||
.unwrap();
|
||||
if let Err(e) = kill_evt.write(1) {
|
||||
error!("Error shutting down worker thread: {:?}", e)
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
// Copyright 2019 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// A BTreeMap that supports 2 types of keys per value. All the usual restrictions and warnings for
|
||||
/// `std::collections::BTreeMap` also apply to this struct. Additionally, there is a 1:1
|
||||
/// relationship between the 2 key types. In other words, for each `K1` in the map, there is exactly
|
||||
/// one `K2` in the map and vice versa.
|
||||
#[derive(Default)]
|
||||
pub struct MultikeyBTreeMap<K1, K2, V>
|
||||
where
|
||||
K1: Ord,
|
||||
K2: Ord,
|
||||
{
|
||||
// We need to keep a copy of the second key in the main map so that we can remove entries using
|
||||
// just the main key. Otherwise we would require the caller to provide both keys when calling
|
||||
// `remove`.
|
||||
main: BTreeMap<K1, (K2, V)>,
|
||||
alt: BTreeMap<K2, K1>,
|
||||
}
|
||||
|
||||
impl<K1, K2, V> MultikeyBTreeMap<K1, K2, V>
|
||||
where
|
||||
K1: Clone + Ord,
|
||||
K2: Clone + Ord,
|
||||
{
|
||||
/// Create a new empty MultikeyBTreeMap.
|
||||
pub fn new() -> Self {
|
||||
MultikeyBTreeMap {
|
||||
main: BTreeMap::default(),
|
||||
alt: BTreeMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the value corresponding to the key.
|
||||
///
|
||||
/// The key may be any borrowed form of `K1``, but the ordering on the borrowed form must match
|
||||
/// the ordering on `K1`.
|
||||
pub fn get<Q>(&self, key: &Q) -> Option<&V>
|
||||
where
|
||||
K1: Borrow<Q>,
|
||||
Q: Ord + ?Sized,
|
||||
{
|
||||
self.main.get(key).map(|(_, v)| v)
|
||||
}
|
||||
|
||||
/// Returns a reference to the value corresponding to the alternate key.
|
||||
///
|
||||
/// The key may be any borrowed form of the `K2``, but the ordering on the borrowed form must
|
||||
/// match the ordering on `K2`.
|
||||
///
|
||||
/// Note that this method performs 2 lookups: one to get the main key and another to get the
|
||||
/// value associated with that key. For best performance callers should prefer the `get` method
|
||||
/// over this method whenever possible as `get` only needs to perform one lookup.
|
||||
pub fn get_alt<Q2>(&self, key: &Q2) -> Option<&V>
|
||||
where
|
||||
K2: Borrow<Q2>,
|
||||
Q2: Ord + ?Sized,
|
||||
{
|
||||
if let Some(k) = self.alt.get(key) {
|
||||
self.get(k)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a new entry into the map with the given keys and value.
|
||||
///
|
||||
/// Returns `None` if the map did not have an entry with `k1` or `k2` present. If exactly one
|
||||
/// key was present, then the value associated with that key is updated, the other key is
|
||||
/// removed, and the old value is returned. If **both** keys were present then the value
|
||||
/// associated with the main key is updated, the value associated with the alternate key is
|
||||
/// removed, and the old value associated with the main key is returned.
|
||||
pub fn insert(&mut self, k1: K1, k2: K2, v: V) -> Option<V> {
|
||||
let oldval = if let Some(oldkey) = self.alt.insert(k2.clone(), k1.clone()) {
|
||||
self.main.remove(&oldkey)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.main
|
||||
.insert(k1, (k2.clone(), v))
|
||||
.or(oldval)
|
||||
.map(|(oldk2, v)| {
|
||||
if oldk2 != k2 {
|
||||
self.alt.remove(&oldk2);
|
||||
}
|
||||
v
|
||||
})
|
||||
}
|
||||
|
||||
/// Remove a key from the map, returning the value associated with that key if it was previously
|
||||
/// in the map.
|
||||
///
|
||||
/// The key may be any borrowed form of `K1``, but the ordering on the borrowed form must match
|
||||
/// the ordering on `K1`.
|
||||
pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
|
||||
where
|
||||
K1: Borrow<Q>,
|
||||
Q: Ord + ?Sized,
|
||||
{
|
||||
self.main.remove(key).map(|(k2, v)| {
|
||||
self.alt.remove(&k2);
|
||||
v
|
||||
})
|
||||
}
|
||||
|
||||
/// Clears the map, removing all values.
|
||||
pub fn clear(&mut self) {
|
||||
self.alt.clear();
|
||||
self.main.clear()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get() {
|
||||
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
|
||||
|
||||
let k1 = 0xc6c8_f5e0_b13e_ed40;
|
||||
let k2 = 0x1a04_ce4b_8329_14fe;
|
||||
let val = 0xf4e3_c360;
|
||||
assert!(m.insert(k1, k2, val).is_none());
|
||||
|
||||
assert_eq!(*m.get(&k1).expect("failed to look up main key"), val);
|
||||
assert_eq!(*m.get_alt(&k2).expect("failed to look up alt key"), val);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_main_key() {
|
||||
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
|
||||
|
||||
let k1 = 0xc6c8_f5e0_b13e_ed40;
|
||||
let k2 = 0x1a04_ce4b_8329_14fe;
|
||||
let val = 0xf4e3_c360;
|
||||
assert!(m.insert(k1, k2, val).is_none());
|
||||
|
||||
let new_k1 = 0x3add_f8f8_c7c5_df5e;
|
||||
let val2 = 0x7389_f8a7;
|
||||
assert_eq!(
|
||||
m.insert(new_k1, k2, val2)
|
||||
.expect("failed to update main key"),
|
||||
val
|
||||
);
|
||||
|
||||
assert!(m.get(&k1).is_none());
|
||||
assert_eq!(*m.get(&new_k1).expect("failed to look up main key"), val2);
|
||||
assert_eq!(*m.get_alt(&k2).expect("failed to look up alt key"), val2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_alt_key() {
|
||||
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
|
||||
|
||||
let k1 = 0xc6c8_f5e0_b13e_ed40;
|
||||
let k2 = 0x1a04_ce4b_8329_14fe;
|
||||
let val = 0xf4e3_c360;
|
||||
assert!(m.insert(k1, k2, val).is_none());
|
||||
|
||||
let new_k2 = 0x6825_a60b_61ac_b333;
|
||||
let val2 = 0xbb14_8f2c;
|
||||
assert_eq!(
|
||||
m.insert(k1, new_k2, val2)
|
||||
.expect("failed to update alt key"),
|
||||
val
|
||||
);
|
||||
|
||||
assert!(m.get_alt(&k2).is_none());
|
||||
assert_eq!(*m.get(&k1).expect("failed to look up main key"), val2);
|
||||
assert_eq!(
|
||||
*m.get_alt(&new_k2).expect("failed to look up alt key"),
|
||||
val2
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_value() {
|
||||
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
|
||||
|
||||
let k1 = 0xc6c8_f5e0_b13e_ed40;
|
||||
let k2 = 0x1a04_ce4b_8329_14fe;
|
||||
let val = 0xf4e3_c360;
|
||||
assert!(m.insert(k1, k2, val).is_none());
|
||||
|
||||
let val2 = 0xe42d_79ba;
|
||||
assert_eq!(
|
||||
m.insert(k1, k2, val2).expect("failed to update alt key"),
|
||||
val
|
||||
);
|
||||
|
||||
assert_eq!(*m.get(&k1).expect("failed to look up main key"), val2);
|
||||
assert_eq!(*m.get_alt(&k2).expect("failed to look up alt key"), val2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_both_keys_main() {
|
||||
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
|
||||
|
||||
let k1 = 0xc6c8_f5e0_b13e_ed40;
|
||||
let k2 = 0x1a04_ce4b_8329_14fe;
|
||||
let val = 0xf4e3_c360;
|
||||
assert!(m.insert(k1, k2, val).is_none());
|
||||
|
||||
let new_k1 = 0xc980_587a_24b3_ae30;
|
||||
let new_k2 = 0x2773_c5ee_8239_45a2;
|
||||
let val2 = 0x31f4_33f9;
|
||||
assert!(m.insert(new_k1, new_k2, val2).is_none());
|
||||
|
||||
let val3 = 0x8da1_9cf7;
|
||||
assert_eq!(
|
||||
m.insert(k1, new_k2, val3)
|
||||
.expect("failed to update main key"),
|
||||
val
|
||||
);
|
||||
|
||||
// Both new_k1 and k2 should now be gone from the map.
|
||||
assert!(m.get(&new_k1).is_none());
|
||||
assert!(m.get_alt(&k2).is_none());
|
||||
|
||||
assert_eq!(*m.get(&k1).expect("failed to look up main key"), val3);
|
||||
assert_eq!(
|
||||
*m.get_alt(&new_k2).expect("failed to look up alt key"),
|
||||
val3
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_both_keys_alt() {
|
||||
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
|
||||
|
||||
let k1 = 0xc6c8_f5e0_b13e_ed40;
|
||||
let k2 = 0x1a04_ce4b_8329_14fe;
|
||||
let val = 0xf4e3_c360;
|
||||
assert!(m.insert(k1, k2, val).is_none());
|
||||
|
||||
let new_k1 = 0xc980_587a_24b3_ae30;
|
||||
let new_k2 = 0x2773_c5ee_8239_45a2;
|
||||
let val2 = 0x31f4_33f9;
|
||||
assert!(m.insert(new_k1, new_k2, val2).is_none());
|
||||
|
||||
let val3 = 0x8da1_9cf7;
|
||||
assert_eq!(
|
||||
m.insert(new_k1, k2, val3)
|
||||
.expect("failed to update main key"),
|
||||
val2
|
||||
);
|
||||
|
||||
// Both k1 and new_k2 should now be gone from the map.
|
||||
assert!(m.get(&k1).is_none());
|
||||
assert!(m.get_alt(&new_k2).is_none());
|
||||
|
||||
assert_eq!(*m.get(&new_k1).expect("failed to look up main key"), val3);
|
||||
assert_eq!(*m.get_alt(&k2).expect("failed to look up alt key"), val3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove() {
|
||||
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
|
||||
|
||||
let k1 = 0xc6c8_f5e0_b13e_ed40;
|
||||
let k2 = 0x1a04_ce4b_8329_14fe;
|
||||
let val = 0xf4e3_c360;
|
||||
assert!(m.insert(k1, k2, val).is_none());
|
||||
|
||||
assert_eq!(m.remove(&k1).expect("failed to remove entry"), val);
|
||||
assert!(m.get(&k1).is_none());
|
||||
assert!(m.get_alt(&k2).is_none());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,319 +0,0 @@
|
||||
// Copyright 2020 Red Hat, Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::{fmt, fs, io};
|
||||
|
||||
use tempdir::TempDir;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to bind mount `/proc/self/fd` into a temporary directory.
|
||||
BindMountProcSelfFd(io::Error),
|
||||
/// Failed to bind mount shared directory.
|
||||
BindMountSharedDir(io::Error),
|
||||
/// Failed to change to the old root directory.
|
||||
ChdirOldRoot(io::Error),
|
||||
/// Failed to change to the new root directory.
|
||||
ChdirNewRoot(io::Error),
|
||||
/// Failed to clean the properties of the mount point.
|
||||
CleanMount(io::Error),
|
||||
/// Failed to create a temporary directory.
|
||||
CreateTempDir(io::Error),
|
||||
/// Call to libc::fork returned an error.
|
||||
Fork(io::Error),
|
||||
/// Error bind-mounting a directory.
|
||||
MountBind(io::Error),
|
||||
/// Failed to mount old root.
|
||||
MountOldRoot(io::Error),
|
||||
/// Error mounting proc.
|
||||
MountProc(io::Error),
|
||||
/// Failed to mount new root.
|
||||
MountNewRoot(io::Error),
|
||||
/// Error mounting target directory.
|
||||
MountTarget(io::Error),
|
||||
/// Failed to open new root.
|
||||
OpenNewRoot(io::Error),
|
||||
/// Failed to open old root.
|
||||
OpenOldRoot(io::Error),
|
||||
/// Failed to open `/proc/self/fd`.
|
||||
OpenProcSelfFd(io::Error),
|
||||
/// Error switching root directory.
|
||||
PivotRoot(io::Error),
|
||||
/// Failed to remove temporary directory.
|
||||
RmdirTempDir(io::Error),
|
||||
/// Failed to lazily unmount old root.
|
||||
UmountOldRoot(io::Error),
|
||||
/// Failed to lazily unmount temporary directory.
|
||||
UmountTempDir(io::Error),
|
||||
/// Call to libc::unshare returned an error.
|
||||
Unshare(io::Error),
|
||||
/// Failed to read from procfs.
|
||||
ReadProc(io::Error),
|
||||
/// Failed to parse `/proc/sys/fs/nr_open`.
|
||||
InvalidNrOpen(std::num::ParseIntError),
|
||||
/// Failed to set rlimit.
|
||||
SetRlimit(io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "vhost_user_fs_sandbox_error: {:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper for creating a sandbox for isolating the service.
|
||||
pub struct Sandbox {
|
||||
/// The directory that is going to be shared with the VM. The sandbox will be constructed on top
|
||||
/// of this directory.
|
||||
shared_dir: String,
|
||||
/// A file descriptor for `/proc/self/fd` obtained from the sandboxed context.
|
||||
proc_self_fd: Option<RawFd>,
|
||||
}
|
||||
|
||||
impl Sandbox {
|
||||
pub fn new(shared_dir: String) -> Self {
|
||||
Sandbox {
|
||||
shared_dir,
|
||||
proc_self_fd: None,
|
||||
}
|
||||
}
|
||||
|
||||
// Make `self.shared_dir` our root directory, and get an isolated file descriptor for
|
||||
// `/proc/self/fd`.
|
||||
//
|
||||
// This is based on virtiofsd's setup_namespaces() and setup_mounts(), and it's very similar to
|
||||
// the strategy used in containers. Consists on a careful sequence of mounts and bind-mounts to
|
||||
// ensure it's not possible to escape the sandbox through `self.shared_dir` nor the file
|
||||
// descriptor obtained for `/proc/self/fd`.
|
||||
//
|
||||
// It's ugly, but it's the only way until Linux implements a proper containerization API.
|
||||
fn setup_mounts(&mut self) -> Result<(), Error> {
|
||||
// Ensure our mount changes don't affect the parent mount namespace.
|
||||
let c_root_dir = CString::new("/").unwrap();
|
||||
let ret = unsafe {
|
||||
libc::mount(
|
||||
std::ptr::null(),
|
||||
c_root_dir.as_ptr(),
|
||||
std::ptr::null(),
|
||||
libc::MS_SLAVE | libc::MS_REC,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
if ret != 0 {
|
||||
return Err(Error::CleanMount(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Mount `/proc` in this context.
|
||||
let c_proc_dir = CString::new("/proc").unwrap();
|
||||
let c_proc_fs = CString::new("proc").unwrap();
|
||||
let ret = unsafe {
|
||||
libc::mount(
|
||||
c_proc_fs.as_ptr(),
|
||||
c_proc_dir.as_ptr(),
|
||||
c_proc_fs.as_ptr(),
|
||||
libc::MS_NODEV | libc::MS_NOEXEC | libc::MS_NOSUID | libc::MS_RELATIME,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
if ret != 0 {
|
||||
return Err(Error::MountProc(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Bind-mount `/proc/self/fd` info a temporary directory, preventing access to ancestor
|
||||
// directories.
|
||||
let c_proc_self_fd = CString::new("/proc/self/fd").unwrap();
|
||||
let tmp_dir = TempDir::new("vhostuserfs-")
|
||||
.map_err(|_| Error::CreateTempDir(std::io::Error::last_os_error()))?;
|
||||
let c_tmp_dir = CString::new(tmp_dir.into_path().to_str().unwrap()).unwrap();
|
||||
let ret = unsafe {
|
||||
libc::mount(
|
||||
c_proc_self_fd.as_ptr(),
|
||||
c_tmp_dir.as_ptr(),
|
||||
std::ptr::null(),
|
||||
libc::MS_BIND,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
if ret < 0 {
|
||||
return Err(Error::BindMountProcSelfFd(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Obtain a file descriptor for `/proc/self/fd` through the bind-mounted temporary directory.
|
||||
let proc_self_fd = unsafe { libc::open(c_tmp_dir.as_ptr(), libc::O_PATH) };
|
||||
if proc_self_fd < 0 {
|
||||
return Err(Error::OpenProcSelfFd(std::io::Error::last_os_error()));
|
||||
}
|
||||
self.proc_self_fd = Some(proc_self_fd);
|
||||
|
||||
// Now that we have a file descriptor for `/proc/self/fd`, we no longer need the bind-mount.
|
||||
// Unmount it and remove the temporary directory.
|
||||
let ret = unsafe { libc::umount2(c_tmp_dir.as_ptr(), libc::MNT_DETACH) };
|
||||
if ret < 0 {
|
||||
return Err(Error::UmountTempDir(std::io::Error::last_os_error()));
|
||||
}
|
||||
let ret = unsafe { libc::rmdir(c_tmp_dir.as_ptr()) };
|
||||
if ret < 0 {
|
||||
return Err(Error::RmdirTempDir(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Bind-mount `self.shared_dir` on itself so we can use as new root on `pivot_root` syscall.
|
||||
let c_shared_dir = CString::new(self.shared_dir.clone()).unwrap();
|
||||
let ret = unsafe {
|
||||
libc::mount(
|
||||
c_shared_dir.as_ptr(),
|
||||
c_shared_dir.as_ptr(),
|
||||
std::ptr::null(),
|
||||
libc::MS_BIND | libc::MS_REC,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
if ret < 0 {
|
||||
return Err(Error::BindMountSharedDir(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Get a file descriptor to our old root so we can reference it after switching root.
|
||||
let oldroot_fd = unsafe {
|
||||
libc::open(
|
||||
c_root_dir.as_ptr(),
|
||||
libc::O_DIRECTORY | libc::O_RDONLY | libc::O_CLOEXEC,
|
||||
)
|
||||
};
|
||||
if oldroot_fd < 0 {
|
||||
return Err(Error::OpenOldRoot(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Get a file descriptor to the new root so we can reference it after switching root.
|
||||
let newroot_fd = unsafe {
|
||||
libc::open(
|
||||
c_shared_dir.as_ptr(),
|
||||
libc::O_DIRECTORY | libc::O_RDONLY | libc::O_CLOEXEC,
|
||||
)
|
||||
};
|
||||
if newroot_fd < 0 {
|
||||
return Err(Error::OpenNewRoot(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Change to new root directory to prepare for `pivot_root` syscall.
|
||||
let ret = unsafe { libc::fchdir(newroot_fd) };
|
||||
if ret < 0 {
|
||||
return Err(Error::ChdirNewRoot(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Call to `pivot_root` using `.` as both new and old root.
|
||||
let c_current_dir = CString::new(".").unwrap();
|
||||
let ret = unsafe {
|
||||
libc::syscall(
|
||||
libc::SYS_pivot_root,
|
||||
c_current_dir.as_ptr(),
|
||||
c_current_dir.as_ptr(),
|
||||
)
|
||||
};
|
||||
if ret < 0 {
|
||||
return Err(Error::PivotRoot(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Change to old root directory to prepare for cleaning up and unmounting it.
|
||||
let ret = unsafe { libc::fchdir(oldroot_fd) };
|
||||
if ret < 0 {
|
||||
return Err(Error::ChdirOldRoot(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Clean up old root to avoid mount namespace propagation.
|
||||
let c_empty = CString::new("").unwrap();
|
||||
let ret = unsafe {
|
||||
libc::mount(
|
||||
c_empty.as_ptr(),
|
||||
c_current_dir.as_ptr(),
|
||||
c_empty.as_ptr(),
|
||||
libc::MS_SLAVE | libc::MS_REC,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
if ret != 0 {
|
||||
return Err(Error::CleanMount(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Lazily unmount old root.
|
||||
let ret = unsafe { libc::umount2(c_current_dir.as_ptr(), libc::MNT_DETACH) };
|
||||
if ret < 0 {
|
||||
return Err(Error::UmountOldRoot(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Change to new root.
|
||||
let ret = unsafe { libc::fchdir(newroot_fd) };
|
||||
if ret < 0 {
|
||||
return Err(Error::ChdirNewRoot(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// We no longer need these file descriptors, so close them.
|
||||
unsafe { libc::close(newroot_fd) };
|
||||
unsafe { libc::close(oldroot_fd) };
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the limit of open files to the max possible.
|
||||
fn setup_nofile_rlimit(&self) -> Result<(), Error> {
|
||||
// /proc/sys/fs/nr_open is a sysctl file that shows the maximum number
|
||||
// of file-handles a process can allocate.
|
||||
let path = "/proc/sys/fs/nr_open";
|
||||
let max_str = fs::read_to_string(path).map_err(Error::ReadProc)?;
|
||||
let max = max_str.trim().parse().map_err(Error::InvalidNrOpen)?;
|
||||
|
||||
let limit = libc::rlimit {
|
||||
rlim_cur: max,
|
||||
rlim_max: max,
|
||||
};
|
||||
let ret = unsafe { libc::setrlimit(libc::RLIMIT_NOFILE, &limit) };
|
||||
if ret < 0 {
|
||||
Err(Error::SetRlimit(std::io::Error::last_os_error()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Set up sandbox, fork and jump into it.
|
||||
///
|
||||
/// On success, the returned value will be the PID of the child for the parent and `None` for
|
||||
/// the child itself, with the latter running isolated in `self.shared_dir`.
|
||||
pub fn enter(&mut self) -> Result<Option<i32>, Error> {
|
||||
let uid = unsafe { libc::geteuid() };
|
||||
|
||||
let flags = if uid == 0 {
|
||||
libc::CLONE_NEWPID | libc::CLONE_NEWNS | libc::CLONE_NEWNET
|
||||
} else {
|
||||
// If running as an unprivileged user, rely on user_namespaces(7) for isolation. The
|
||||
// main limitation of this strategy is that only the current uid/gid are mapped into
|
||||
// the new namespace, so most operations on permissions will fail.
|
||||
libc::CLONE_NEWPID | libc::CLONE_NEWNS | libc::CLONE_NEWNET | libc::CLONE_NEWUSER
|
||||
};
|
||||
|
||||
let ret = unsafe { libc::unshare(flags) };
|
||||
if ret != 0 {
|
||||
return Err(Error::Unshare(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
let child = unsafe { libc::fork() };
|
||||
match child {
|
||||
0 => {
|
||||
// This is the child. Request to receive SIGTERM on parent's death.
|
||||
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) };
|
||||
self.setup_nofile_rlimit()?;
|
||||
self.setup_mounts()?;
|
||||
Ok(None)
|
||||
}
|
||||
x if x > 0 => {
|
||||
// This is the parent.
|
||||
Ok(Some(child))
|
||||
}
|
||||
_ => Err(Error::Fork(std::io::Error::last_os_error())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_proc_self_fd(&self) -> Option<RawFd> {
|
||||
self.proc_self_fd
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
// Copyright 2020 Red Hat, Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use seccomp::{allow_syscall, BpfProgram, SeccompAction, SeccompFilter};
|
||||
use std::convert::TryInto;
|
||||
use std::{convert, fmt};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Cannot create seccomp filter
|
||||
CreateSeccompFilter(seccomp::SeccompError),
|
||||
|
||||
/// Cannot apply seccomp filter
|
||||
ApplySeccompFilter(seccomp::Error),
|
||||
}
|
||||
|
||||
impl convert::From<seccomp::Error> for Error {
|
||||
fn from(e: seccomp::Error) -> Self {
|
||||
Error::ApplySeccompFilter(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "vhost_user_fs_seccomp_error: {:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
fn vuf_filter(action: SeccompAction) -> Result<SeccompFilter, Error> {
|
||||
Ok(SeccompFilter::new(
|
||||
vec![
|
||||
allow_syscall(libc::SYS_accept4),
|
||||
allow_syscall(libc::SYS_brk),
|
||||
allow_syscall(libc::SYS_capget), // For CAP_FSETID
|
||||
allow_syscall(libc::SYS_capset),
|
||||
allow_syscall(libc::SYS_clock_gettime),
|
||||
allow_syscall(libc::SYS_clone),
|
||||
allow_syscall(libc::SYS_close),
|
||||
allow_syscall(libc::SYS_copy_file_range),
|
||||
allow_syscall(libc::SYS_dup),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
allow_syscall(libc::SYS_epoll_create),
|
||||
allow_syscall(libc::SYS_epoll_create1),
|
||||
allow_syscall(libc::SYS_epoll_ctl),
|
||||
allow_syscall(libc::SYS_epoll_pwait),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
allow_syscall(libc::SYS_epoll_wait),
|
||||
allow_syscall(libc::SYS_eventfd2),
|
||||
allow_syscall(libc::SYS_exit),
|
||||
allow_syscall(libc::SYS_exit_group),
|
||||
allow_syscall(libc::SYS_fallocate),
|
||||
allow_syscall(libc::SYS_fchdir),
|
||||
allow_syscall(libc::SYS_fchmod),
|
||||
allow_syscall(libc::SYS_fchmodat),
|
||||
allow_syscall(libc::SYS_fchownat),
|
||||
allow_syscall(libc::SYS_fcntl),
|
||||
allow_syscall(libc::SYS_fdatasync),
|
||||
allow_syscall(libc::SYS_fgetxattr),
|
||||
allow_syscall(libc::SYS_flistxattr),
|
||||
allow_syscall(libc::SYS_flock),
|
||||
allow_syscall(libc::SYS_fremovexattr),
|
||||
allow_syscall(libc::SYS_fsetxattr),
|
||||
allow_syscall(libc::SYS_fstat),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
allow_syscall(libc::SYS_fstatfs),
|
||||
allow_syscall(libc::SYS_fsync),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
allow_syscall(libc::SYS_ftruncate),
|
||||
allow_syscall(libc::SYS_futex),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
allow_syscall(libc::SYS_getdents),
|
||||
allow_syscall(libc::SYS_getdents64),
|
||||
allow_syscall(libc::SYS_getegid),
|
||||
allow_syscall(libc::SYS_geteuid),
|
||||
allow_syscall(libc::SYS_getpid),
|
||||
allow_syscall(libc::SYS_gettid),
|
||||
allow_syscall(libc::SYS_gettimeofday),
|
||||
allow_syscall(libc::SYS_getxattr),
|
||||
allow_syscall(libc::SYS_linkat),
|
||||
allow_syscall(libc::SYS_listxattr),
|
||||
allow_syscall(libc::SYS_lseek),
|
||||
allow_syscall(libc::SYS_madvise),
|
||||
allow_syscall(libc::SYS_mkdirat),
|
||||
allow_syscall(libc::SYS_mknodat),
|
||||
allow_syscall(libc::SYS_mmap),
|
||||
allow_syscall(libc::SYS_mprotect),
|
||||
allow_syscall(libc::SYS_mremap),
|
||||
allow_syscall(libc::SYS_munmap),
|
||||
allow_syscall(libc::SYS_newfstatat),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
allow_syscall(libc::SYS_open),
|
||||
allow_syscall(libc::SYS_openat),
|
||||
allow_syscall(libc::SYS_prctl), // TODO restrict to just PR_SET_NAME?
|
||||
allow_syscall(libc::SYS_preadv),
|
||||
allow_syscall(libc::SYS_pread64),
|
||||
allow_syscall(libc::SYS_pwritev),
|
||||
allow_syscall(libc::SYS_pwrite64),
|
||||
allow_syscall(libc::SYS_read),
|
||||
allow_syscall(libc::SYS_readlinkat),
|
||||
allow_syscall(libc::SYS_recvmsg),
|
||||
allow_syscall(libc::SYS_renameat),
|
||||
allow_syscall(libc::SYS_renameat2),
|
||||
allow_syscall(libc::SYS_removexattr),
|
||||
allow_syscall(libc::SYS_rt_sigaction),
|
||||
allow_syscall(libc::SYS_rt_sigprocmask),
|
||||
allow_syscall(libc::SYS_rt_sigreturn),
|
||||
allow_syscall(libc::SYS_sched_getaffinity), // used by thread_pool
|
||||
allow_syscall(libc::SYS_sendmsg),
|
||||
allow_syscall(libc::SYS_setresgid),
|
||||
allow_syscall(libc::SYS_setresuid),
|
||||
//allow_syscall(libc::SYS_setresgid32), Needed on some platforms,
|
||||
//allow_syscall(libc::SYS_setresuid32), Needed on some platforms
|
||||
allow_syscall(libc::SYS_set_robust_list),
|
||||
allow_syscall(libc::SYS_setxattr),
|
||||
allow_syscall(libc::SYS_sigaltstack),
|
||||
allow_syscall(libc::SYS_statx),
|
||||
allow_syscall(libc::SYS_symlinkat),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
allow_syscall(libc::SYS_time), // Rarely needed, except on static builds
|
||||
allow_syscall(libc::SYS_tgkill),
|
||||
allow_syscall(libc::SYS_umask),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
allow_syscall(libc::SYS_unlink),
|
||||
allow_syscall(libc::SYS_unlinkat),
|
||||
allow_syscall(libc::SYS_unshare),
|
||||
allow_syscall(libc::SYS_utimensat),
|
||||
allow_syscall(libc::SYS_write),
|
||||
allow_syscall(libc::SYS_writev),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
action,
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn enable_seccomp(action: SeccompAction) -> Result<(), Error> {
|
||||
let scfilter = vuf_filter(action)?;
|
||||
let bpfprog: BpfProgram = scfilter.try_into()?;
|
||||
SeccompFilter::apply(bpfprog).map_err(Error::ApplySeccompFilter)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,12 +7,12 @@ edition = "2018"
|
||||
[dependencies]
|
||||
clap = { version = "2.33.3", features=["wrap_help"] }
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.80"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
net_util = { path = "../net_util" }
|
||||
option_parser = { path = "../option_parser" }
|
||||
vhost_user_backend = { path = "../vhost_user_backend" }
|
||||
vhost_rs = { git = "https://github.com/rust-vmm/vhost", branch = "master", package = "vhost", features = ["vhost-user-slave"] }
|
||||
virtio-bindings = "0.1.0"
|
||||
vm-memory = "0.3.0"
|
||||
vm-memory = "0.4.0"
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
@@ -10,12 +10,12 @@ io_uring = ["block_util/io_uring"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
arc-swap = ">=0.4.4"
|
||||
arc-swap = ">=1.0.0"
|
||||
block_util = { path = "../block_util" }
|
||||
byteorder = "1.3.4"
|
||||
epoll = ">=4.0.1"
|
||||
io-uring = ">=0.4.0"
|
||||
libc = "0.2.80"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
net_gen = { path = "../net_gen" }
|
||||
net_util = { path = "../net_util" }
|
||||
@@ -30,7 +30,7 @@ vhost_rs = { git = "https://github.com/rust-vmm/vhost", branch = "master", packa
|
||||
virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = { version = "0.3.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-memory = { version = "0.4.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
@@ -98,7 +98,7 @@ struct VirtioBalloonResizeReceiver {
|
||||
|
||||
impl VirtioBalloonResizeReceiver {
|
||||
fn get_size(&self) -> u64 {
|
||||
self.size.load(Ordering::SeqCst)
|
||||
self.size.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
fn send(&self, r: Result<(), Error>) -> Result<(), mpsc::SendError<Result<(), Error>>> {
|
||||
@@ -134,7 +134,7 @@ impl VirtioBalloonResize {
|
||||
}
|
||||
|
||||
pub fn work(&self, size: u64) -> Result<(), Error> {
|
||||
self.size.store(size, Ordering::SeqCst);
|
||||
self.size.store(size, Ordering::Release);
|
||||
self.evt.write(1).map_err(Error::EventFdWriteFail)?;
|
||||
self.rx.recv().map_err(Error::MpscRecvFail)?
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ impl<T: DiskFile> BlockEpollHandler<T> {
|
||||
let len;
|
||||
match Request::parse(&avail_desc, &mem) {
|
||||
Ok(mut request) => {
|
||||
request.set_writeback(self.writeback.load(Ordering::SeqCst));
|
||||
request.set_writeback(self.writeback.load(Ordering::Acquire));
|
||||
|
||||
let mut disk_image_locked = self.disk_image.lock().unwrap();
|
||||
let mut disk_image = disk_image_locked.deref_mut();
|
||||
@@ -387,7 +387,7 @@ impl<T: DiskFile> Block<T> {
|
||||
"writethrough"
|
||||
}
|
||||
);
|
||||
self.writeback.store(writeback, Ordering::SeqCst);
|
||||
self.writeback.store(writeback, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ impl BlockIoUringEpollHandler {
|
||||
|
||||
for avail_desc in queue.iter(&mem) {
|
||||
let mut request = Request::parse(&avail_desc, &mem).map_err(Error::RequestParsing)?;
|
||||
request.set_writeback(self.writeback.load(Ordering::SeqCst));
|
||||
request.set_writeback(self.writeback.load(Ordering::Acquire));
|
||||
if request
|
||||
.execute_io_uring(
|
||||
&mem,
|
||||
@@ -428,7 +428,7 @@ impl BlockIoUring {
|
||||
"writethrough"
|
||||
}
|
||||
);
|
||||
self.writeback.store(writeback, Ordering::SeqCst);
|
||||
self.writeback.store(writeback, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ impl ConsoleInput {
|
||||
pub fn update_console_size(&self, cols: u16, rows: u16) {
|
||||
if self
|
||||
.acked_features
|
||||
.fetch_and(1u64 << VIRTIO_CONSOLE_F_SIZE, Ordering::SeqCst)
|
||||
.fetch_and(1u64 << VIRTIO_CONSOLE_F_SIZE, Ordering::AcqRel)
|
||||
!= 0
|
||||
{
|
||||
self.config.lock().unwrap().update_console_size(cols, rows);
|
||||
|
||||
@@ -284,7 +284,7 @@ impl Resize {
|
||||
|
||||
pub fn work(&self, size: u64) -> Result<(), Error> {
|
||||
if let Some(rx) = &self.rx {
|
||||
self.size.store(size, Ordering::SeqCst);
|
||||
self.size.store(size, Ordering::Release);
|
||||
self.evt.write(1).map_err(Error::EventFdWriteFail)?;
|
||||
rx.recv().map_err(Error::MpscRecvFail)?
|
||||
} else {
|
||||
@@ -293,7 +293,7 @@ impl Resize {
|
||||
}
|
||||
|
||||
fn get_size(&self) -> u64 {
|
||||
self.size.load(Ordering::SeqCst)
|
||||
self.size.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
fn send(&self, r: Result<(), Error>) -> Result<(), mpsc::SendError<Result<(), Error>>> {
|
||||
|
||||
@@ -64,7 +64,7 @@ impl VirtioPciCommonConfig {
|
||||
device_feature_select: self.device_feature_select,
|
||||
driver_feature_select: self.driver_feature_select,
|
||||
queue_select: self.queue_select,
|
||||
msix_config: self.msix_config.load(Ordering::SeqCst),
|
||||
msix_config: self.msix_config.load(Ordering::Acquire),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ impl VirtioPciCommonConfig {
|
||||
self.device_feature_select = state.device_feature_select;
|
||||
self.driver_feature_select = state.driver_feature_select;
|
||||
self.queue_select = state.queue_select;
|
||||
self.msix_config.store(state.msix_config, Ordering::SeqCst);
|
||||
self.msix_config.store(state.msix_config, Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn read(
|
||||
@@ -153,7 +153,7 @@ impl VirtioPciCommonConfig {
|
||||
fn read_common_config_word(&self, offset: u64, queues: &[Queue]) -> u16 {
|
||||
debug!("read_common_config_word: offset 0x{:x}", offset);
|
||||
match offset {
|
||||
0x10 => self.msix_config.load(Ordering::SeqCst),
|
||||
0x10 => self.msix_config.load(Ordering::Acquire),
|
||||
0x12 => queues.len() as u16, // num_queues
|
||||
0x16 => self.queue_select,
|
||||
0x18 => self.with_queue(queues, |q| q.size).unwrap_or(0),
|
||||
@@ -176,7 +176,7 @@ impl VirtioPciCommonConfig {
|
||||
fn write_common_config_word(&mut self, offset: u64, value: u16, queues: &mut Vec<Queue>) {
|
||||
debug!("write_common_config_word: offset 0x{:x}", offset);
|
||||
match offset {
|
||||
0x10 => self.msix_config.store(value, Ordering::SeqCst),
|
||||
0x10 => self.msix_config.store(value, Ordering::Release),
|
||||
0x16 => self.queue_select = value,
|
||||
0x18 => self.with_queue_mut(queues, |q| q.size = value),
|
||||
0x1a => self.with_queue_mut(queues, |q| q.vector = value),
|
||||
|
||||
@@ -448,7 +448,7 @@ impl VirtioPciDevice {
|
||||
fn state(&self) -> VirtioPciDeviceState {
|
||||
VirtioPciDeviceState {
|
||||
device_activated: self.device_activated,
|
||||
interrupt_status: self.interrupt_status.load(Ordering::SeqCst),
|
||||
interrupt_status: self.interrupt_status.load(Ordering::Acquire),
|
||||
queues: self.queues.clone(),
|
||||
}
|
||||
}
|
||||
@@ -456,7 +456,7 @@ impl VirtioPciDevice {
|
||||
fn set_state(&mut self, state: &VirtioPciDeviceState) -> std::result::Result<(), Error> {
|
||||
self.device_activated = state.device_activated;
|
||||
self.interrupt_status
|
||||
.store(state.interrupt_status, Ordering::SeqCst);
|
||||
.store(state.interrupt_status, Ordering::Release);
|
||||
|
||||
// Update virtqueues indexes for both available and used rings.
|
||||
if let Some(mem) = self.memory.as_ref() {
|
||||
@@ -685,7 +685,7 @@ impl VirtioInterrupt for VirtioInterruptMsix {
|
||||
queue: Option<&Queue>,
|
||||
) -> std::result::Result<(), std::io::Error> {
|
||||
let vector = match int_type {
|
||||
VirtioInterruptType::Config => self.config_vector.load(Ordering::SeqCst),
|
||||
VirtioInterruptType::Config => self.config_vector.load(Ordering::Acquire),
|
||||
VirtioInterruptType::Queue => {
|
||||
if let Some(q) = queue {
|
||||
q.vector
|
||||
@@ -717,7 +717,7 @@ impl VirtioInterrupt for VirtioInterruptMsix {
|
||||
|
||||
fn notifier(&self, int_type: &VirtioInterruptType, queue: Option<&Queue>) -> Option<&EventFd> {
|
||||
let vector = match int_type {
|
||||
VirtioInterruptType::Config => self.config_vector.load(Ordering::SeqCst),
|
||||
VirtioInterruptType::Config => self.config_vector.load(Ordering::Acquire),
|
||||
VirtioInterruptType::Queue => {
|
||||
if let Some(q) = queue {
|
||||
q.vector
|
||||
@@ -891,7 +891,7 @@ impl PciDevice for VirtioPciDevice {
|
||||
o if ISR_CONFIG_BAR_OFFSET <= o && o < ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE => {
|
||||
if let Some(v) = data.get_mut(0) {
|
||||
// Reading this register resets it to 0.
|
||||
*v = self.interrupt_status.swap(0, Ordering::SeqCst) as u8;
|
||||
*v = self.interrupt_status.swap(0, Ordering::AcqRel) as u8;
|
||||
}
|
||||
}
|
||||
o if DEVICE_CONFIG_BAR_OFFSET <= o
|
||||
@@ -936,7 +936,7 @@ impl PciDevice for VirtioPciDevice {
|
||||
o if ISR_CONFIG_BAR_OFFSET <= o && o < ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE => {
|
||||
if let Some(v) = data.get(0) {
|
||||
self.interrupt_status
|
||||
.fetch_and(!(*v as usize), Ordering::SeqCst);
|
||||
.fetch_and(!(*v as usize), Ordering::AcqRel);
|
||||
}
|
||||
}
|
||||
o if DEVICE_CONFIG_BAR_OFFSET <= o
|
||||
|
||||
@@ -77,15 +77,15 @@ pub fn setup_vhost_user_vring(
|
||||
queue.desc_table,
|
||||
actual_size * std::mem::size_of::<Descriptor>(),
|
||||
)
|
||||
.ok_or_else(|| Error::DescriptorTableAddress)? as u64,
|
||||
.ok_or(Error::DescriptorTableAddress)? as u64,
|
||||
// The used ring is {flags: u16; idx: u16; virtq_used_elem [{id: u16, len: u16}; actual_size]},
|
||||
// i.e. 4 + (4 + 4) * actual_size.
|
||||
used_ring_addr: get_host_address_range(mem, queue.used_ring, 4 + actual_size * 8)
|
||||
.ok_or_else(|| Error::UsedAddress)? as u64,
|
||||
.ok_or(Error::UsedAddress)? as u64,
|
||||
// The used ring is {flags: u16; idx: u16; elem [u16; actual_size]},
|
||||
// i.e. 4 + (2) * actual_size.
|
||||
avail_ring_addr: get_host_address_range(mem, queue.avail_ring, 4 + actual_size * 2)
|
||||
.ok_or_else(|| Error::AvailAddress)? as u64,
|
||||
.ok_or(Error::AvailAddress)? as u64,
|
||||
log_addr: None,
|
||||
};
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ impl VsockPacket {
|
||||
|
||||
let mut pkt = Self {
|
||||
hdr: get_host_address_range(head.mem, head.addr, VSOCK_PKT_HDR_SIZE)
|
||||
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
|
||||
.ok_or(VsockError::GuestMemory)? as *mut u8,
|
||||
buf: None,
|
||||
buf_size: 0,
|
||||
};
|
||||
@@ -151,7 +151,7 @@ impl VsockPacket {
|
||||
pkt.buf_size = buf_desc.len as usize;
|
||||
pkt.buf = Some(
|
||||
get_host_address_range(buf_desc.mem, buf_desc.addr, pkt.buf_size)
|
||||
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
|
||||
.ok_or(VsockError::GuestMemory)? as *mut u8,
|
||||
);
|
||||
|
||||
Ok(pkt)
|
||||
@@ -183,10 +183,10 @@ impl VsockPacket {
|
||||
|
||||
Ok(Self {
|
||||
hdr: get_host_address_range(head.mem, head.addr, VSOCK_PKT_HDR_SIZE)
|
||||
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
|
||||
.ok_or(VsockError::GuestMemory)? as *mut u8,
|
||||
buf: Some(
|
||||
get_host_address_range(buf_desc.mem, buf_desc.addr, buf_size)
|
||||
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
|
||||
.ok_or(VsockError::GuestMemory)? as *mut u8,
|
||||
),
|
||||
buf_size,
|
||||
})
|
||||
|
||||
@@ -5,6 +5,6 @@ authors = ["The Chromium OS Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.80"
|
||||
vm-memory = "0.3.0"
|
||||
libc = "0.2.81"
|
||||
vm-memory = "0.4.0"
|
||||
arch = { path = "../arch" }
|
||||
|
||||
@@ -10,6 +10,6 @@ thiserror = "1.0"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-memory = { version = "0.3.0", features = ["backend-mmap"] }
|
||||
vm-memory = { version = "0.4.0", features = ["backend-mmap"] }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ impl Bus {
|
||||
.range(..=BusRange { base: addr, len: 1 })
|
||||
.rev()
|
||||
.next()?;
|
||||
Some((*range, dev.upgrade().unwrap().clone()))
|
||||
dev.upgrade().map(|d| (*range, d.clone()))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
@@ -282,10 +282,11 @@ mod tests {
|
||||
assert!(bus.insert(dummy.clone(), 0x0, 0x20).is_err());
|
||||
assert!(bus.insert(dummy.clone(), 0x20, 0x05).is_ok());
|
||||
assert!(bus.insert(dummy.clone(), 0x25, 0x05).is_ok());
|
||||
assert!(bus.insert(dummy.clone(), 0x0, 0x10).is_ok());
|
||||
assert!(bus.insert(dummy, 0x0, 0x10).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::redundant_clone)]
|
||||
fn bus_read_write() {
|
||||
let bus = Bus::new();
|
||||
let dummy = Arc::new(Mutex::new(DummyDevice));
|
||||
@@ -297,12 +298,13 @@ mod tests {
|
||||
assert!(bus.read(0x16, &mut [0, 0, 0, 0]).is_ok());
|
||||
assert!(bus.write(0x16, &[0, 0, 0, 0]).is_ok());
|
||||
assert!(bus.read(0x20, &mut [0, 0, 0, 0]).is_err());
|
||||
assert!(bus.write(0x20, &mut [0, 0, 0, 0]).is_err());
|
||||
assert!(bus.write(0x20, &[0, 0, 0, 0]).is_err());
|
||||
assert!(bus.read(0x06, &mut [0, 0, 0, 0]).is_err());
|
||||
assert!(bus.write(0x06, &mut [0, 0, 0, 0]).is_err());
|
||||
assert!(bus.write(0x06, &[0, 0, 0, 0]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::redundant_clone)]
|
||||
fn bus_read_write_values() {
|
||||
let bus = Bus::new();
|
||||
let dummy = Arc::new(Mutex::new(ConstantDevice));
|
||||
@@ -318,6 +320,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::redundant_clone)]
|
||||
fn busrange_cmp() {
|
||||
let range = BusRange { base: 0x10, len: 2 };
|
||||
assert_eq!(range, BusRange { base: 0x10, len: 3 });
|
||||
@@ -332,7 +335,7 @@ mod tests {
|
||||
let mut data = [1, 2, 3, 4];
|
||||
let device = Arc::new(Mutex::new(DummyDevice));
|
||||
assert!(bus.insert(device.clone(), 0x10, 0x10).is_ok());
|
||||
assert!(bus.write(0x10, &mut data).is_ok());
|
||||
assert!(bus.write(0x10, &data).is_ok());
|
||||
assert!(bus.read(0x10, &mut data).is_ok());
|
||||
assert_eq!(data, [1, 2, 3, 4]);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ extern crate serde_derive;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod protocol;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MigratableError {
|
||||
#[error("Failed to pause migratable component: {0}")]
|
||||
@@ -29,6 +31,9 @@ pub enum MigratableError {
|
||||
|
||||
#[error("Failed to receive migratable component snapshot: {0}")]
|
||||
MigrateReceive(#[source] anyhow::Error),
|
||||
|
||||
#[error("Socket error: {0}")]
|
||||
MigrateSocket(#[source] std::io::Error),
|
||||
}
|
||||
|
||||
/// A Pausable component can be paused and resumed.
|
||||
|
||||
239
vm-migration/src/protocol.rs
Normal file
239
vm-migration/src/protocol.rs
Normal file
@@ -0,0 +1,239 @@
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use crate::MigratableError;
|
||||
|
||||
// Migration protocol
|
||||
// 1: Source establishes communication with destination (file socket or TCP connection.)
|
||||
// (The establishment is out of scope.)
|
||||
// 2: Source -> Dest : send "start command"
|
||||
// 3: Dest -> Source : sends "ok response" when read to accept state data
|
||||
// 4: Source -> Dest : sends "config command" followed by config data, length
|
||||
// in command is length of config data
|
||||
// 5: Dest -> Source : sends "ok response" when ready to accept memory data
|
||||
// 6: Source -> Dest : send "memory command" followed by table of u64 pairs (GPA, size)
|
||||
// followed by the memory described in those pairs.
|
||||
// !! length is size of table i.e. 16 * number of ranges !!
|
||||
// 7: Dest -> Source : sends "ok response" when ready to accept more memory data
|
||||
// 8..(n-4): Repeat steps 6 and 7 until source has no more memory to send
|
||||
// (n-3): Source -> Dest : sends "state command" followed by state data, length
|
||||
// in command is length of config data
|
||||
// (n-2): Dest -> Source : sends "ok response"
|
||||
// (n-1): Source -> Dest : send "complete command"
|
||||
// n: Dest -> Source: sends "ok response"
|
||||
|
||||
// The destination can at any time send an "error response" to cancel
|
||||
// The source can at any time send an "abandon request" to cancel
|
||||
|
||||
use std::io::{Read, Write};
|
||||
|
||||
#[repr(u16)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Command {
|
||||
Invalid,
|
||||
Start,
|
||||
Config,
|
||||
State,
|
||||
Memory,
|
||||
Complete,
|
||||
Abandon,
|
||||
}
|
||||
|
||||
impl Default for Command {
|
||||
fn default() -> Self {
|
||||
Self::Invalid
|
||||
}
|
||||
}
|
||||
|
||||
trait AsBytes {
|
||||
fn as_bytes<T: Sized>(p: &T) -> &[u8] {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts((p as *const T) as *const u8, std::mem::size_of::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
fn as_mut_bytes<T: Sized>(p: &mut T) -> &mut [u8] {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts_mut((p as *mut T) as *mut u8, std::mem::size_of::<T>())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct Request {
|
||||
command: Command,
|
||||
padding: [u8; 6],
|
||||
length: u64, // Length of payload for command excluding the Request struct
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn new(command: Command, length: u64) -> Self {
|
||||
Self {
|
||||
command,
|
||||
length,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start() -> Self {
|
||||
Self::new(Command::Start, 0)
|
||||
}
|
||||
|
||||
pub fn state(length: u64) -> Self {
|
||||
Self::new(Command::State, length)
|
||||
}
|
||||
|
||||
pub fn config(length: u64) -> Self {
|
||||
Self::new(Command::Config, length)
|
||||
}
|
||||
|
||||
pub fn memory(length: u64) -> Self {
|
||||
Self::new(Command::Memory, length)
|
||||
}
|
||||
|
||||
pub fn complete() -> Self {
|
||||
Self::new(Command::Complete, 0)
|
||||
}
|
||||
|
||||
pub fn abandon() -> Self {
|
||||
Self::new(Command::Abandon, 0)
|
||||
}
|
||||
|
||||
pub fn command(&self) -> Command {
|
||||
self.command
|
||||
}
|
||||
|
||||
pub fn length(&self) -> u64 {
|
||||
self.length
|
||||
}
|
||||
|
||||
pub fn read_from(fd: &mut dyn Read) -> Result<Request, MigratableError> {
|
||||
let mut request = Request::default();
|
||||
fd.read_exact(Self::as_mut_bytes(&mut request))
|
||||
.map_err(MigratableError::MigrateSocket)?;
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> {
|
||||
fd.write_all(Self::as_bytes(self))
|
||||
.map_err(MigratableError::MigrateSocket)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsBytes for Request {}
|
||||
|
||||
#[repr(u16)]
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum Status {
|
||||
Invalid,
|
||||
Ok,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl Default for Status {
|
||||
fn default() -> Self {
|
||||
Self::Invalid
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct Response {
|
||||
status: Status,
|
||||
padding: [u8; 6],
|
||||
length: u64, // Length of payload for command excluding the Response struct
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub fn new(status: Status, length: u64) -> Self {
|
||||
Self {
|
||||
status,
|
||||
length,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ok() -> Self {
|
||||
Self::new(Status::Ok, 0)
|
||||
}
|
||||
|
||||
pub fn error() -> Self {
|
||||
Self::new(Status::Error, 0)
|
||||
}
|
||||
|
||||
pub fn status(&self) -> Status {
|
||||
self.status
|
||||
}
|
||||
|
||||
pub fn read_from(fd: &mut dyn Read) -> Result<Response, MigratableError> {
|
||||
let mut response = Response::default();
|
||||
fd.read_exact(Self::as_mut_bytes(&mut response))
|
||||
.map_err(MigratableError::MigrateSocket)?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> {
|
||||
fd.write_all(Self::as_bytes(self))
|
||||
.map_err(MigratableError::MigrateSocket)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsBytes for Response {}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct MemoryRange {
|
||||
pub gpa: u64,
|
||||
pub length: u64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MemoryRangeTable {
|
||||
data: Vec<MemoryRange>,
|
||||
}
|
||||
|
||||
impl MemoryRangeTable {
|
||||
pub fn regions(&self) -> &[MemoryRange] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn push(&mut self, range: MemoryRange) {
|
||||
self.data.push(range)
|
||||
}
|
||||
|
||||
pub fn read_from(fd: &mut dyn Read, length: u64) -> Result<MemoryRangeTable, MigratableError> {
|
||||
assert!(length as usize % std::mem::size_of::<MemoryRange>() == 0);
|
||||
|
||||
let mut data = Vec::with_capacity(length as usize / (std::mem::size_of::<MemoryRange>()));
|
||||
unsafe {
|
||||
data.set_len(length as usize / (std::mem::size_of::<MemoryRange>()));
|
||||
}
|
||||
fd.read_exact(unsafe {
|
||||
std::slice::from_raw_parts_mut(
|
||||
data.as_ptr() as *mut MemoryRange as *mut u8,
|
||||
length as usize,
|
||||
)
|
||||
})
|
||||
.map_err(MigratableError::MigrateSocket)?;
|
||||
|
||||
Ok(Self { data })
|
||||
}
|
||||
|
||||
pub fn length(&self) -> u64 {
|
||||
(std::mem::size_of::<MemoryRange>() * self.data.len()) as u64
|
||||
}
|
||||
|
||||
pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> {
|
||||
fd.write_all(unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
self.data.as_ptr() as *const MemoryRange as *const u8,
|
||||
self.length() as usize,
|
||||
)
|
||||
})
|
||||
.map_err(MigratableError::MigrateSocket)
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,4 @@ serde = ">=1.0.27"
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
|
||||
vm-memory = { version = "0.3.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-memory = { version = "0.4.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
@@ -10,12 +10,13 @@ acpi = ["acpi_tables","devices/acpi", "arch/acpi"]
|
||||
cmos = ["devices/cmos"]
|
||||
fwdebug = ["devices/fwdebug"]
|
||||
kvm = ["hypervisor/kvm"]
|
||||
mshv = ["hypervisor/mshv"]
|
||||
io_uring = ["virtio-devices/io_uring"]
|
||||
|
||||
[dependencies]
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
anyhow = "1.0"
|
||||
arc-swap = ">=0.4.4"
|
||||
arc-swap = ">=1.0.0"
|
||||
arch = { path = "../arch" }
|
||||
bitflags = ">=1.2.1"
|
||||
block_util = { path = "../block_util" }
|
||||
@@ -24,7 +25,7 @@ devices = { path = "../devices" }
|
||||
epoll = ">=4.0.1"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.80"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "master" }
|
||||
net_util = { path = "../net_util" }
|
||||
@@ -35,15 +36,15 @@ seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
signal-hook = "0.1.16"
|
||||
signal-hook = "0.2.1"
|
||||
tempfile = "3.1.0"
|
||||
thiserror = "1.0"
|
||||
url = "2.1.1"
|
||||
url = "2.2.0"
|
||||
vfio-ioctls = { git = "https://github.com/cloud-hypervisor/vfio-ioctls", branch = "ch" }
|
||||
virtio-devices = { path = "../virtio-devices" }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = { version = "0.3.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-memory = { version = "0.4.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = { version = ">=0.5.0", features = ["with-serde"] }
|
||||
|
||||
@@ -100,6 +100,12 @@ pub enum HttpError {
|
||||
|
||||
/// Could not get counters from VM
|
||||
VmCounters(ApiError),
|
||||
|
||||
/// Error setting up migration received
|
||||
VmReceiveMigration(ApiError),
|
||||
|
||||
/// Error setting up migration sender
|
||||
VmSendMigration(ApiError),
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for HttpError {
|
||||
@@ -205,11 +211,13 @@ lazy_static! {
|
||||
r.routes.insert(endpoint!("/vm.info"), Box::new(VmInfo {}));
|
||||
r.routes.insert(endpoint!("/vm.pause"), Box::new(VmActionHandler::new(VmAction::Pause)));
|
||||
r.routes.insert(endpoint!("/vm.reboot"), Box::new(VmActionHandler::new(VmAction::Reboot)));
|
||||
r.routes.insert(endpoint!("/vm.receive-migration"), Box::new(VmActionHandler::new(VmAction::ReceiveMigration(Arc::default()))));
|
||||
r.routes.insert(endpoint!("/vm.remove-device"), Box::new(VmActionHandler::new(VmAction::RemoveDevice(Arc::default()))));
|
||||
r.routes.insert(endpoint!("/vm.resize"), Box::new(VmActionHandler::new(VmAction::Resize(Arc::default()))));
|
||||
r.routes.insert(endpoint!("/vm.resize-zone"), Box::new(VmActionHandler::new(VmAction::ResizeZone(Arc::default()))));
|
||||
r.routes.insert(endpoint!("/vm.restore"), Box::new(VmActionHandler::new(VmAction::Restore(Arc::default()))));
|
||||
r.routes.insert(endpoint!("/vm.resume"), Box::new(VmActionHandler::new(VmAction::Resume)));
|
||||
r.routes.insert(endpoint!("/vm.send-migration"), Box::new(VmActionHandler::new(VmAction::SendMigration(Arc::default()))));
|
||||
r.routes.insert(endpoint!("/vm.shutdown"), Box::new(VmActionHandler::new(VmAction::Shutdown)));
|
||||
r.routes.insert(endpoint!("/vm.snapshot"), Box::new(VmActionHandler::new(VmAction::Snapshot(Arc::default()))));
|
||||
r.routes.insert(endpoint!("/vmm.ping"), Box::new(VmmPing {}));
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
use crate::api::http::{error_response, EndpointHandler, HttpError};
|
||||
use crate::api::{
|
||||
vm_add_device, vm_add_disk, vm_add_fs, vm_add_net, vm_add_pmem, vm_add_vsock, vm_boot,
|
||||
vm_counters, vm_create, vm_delete, vm_info, vm_pause, vm_reboot, vm_remove_device, vm_resize,
|
||||
vm_resize_zone, vm_restore, vm_resume, vm_shutdown, vm_snapshot, vmm_ping, vmm_shutdown,
|
||||
ApiRequest, VmAction, VmConfig,
|
||||
vm_counters, vm_create, vm_delete, vm_info, vm_pause, vm_reboot, vm_receive_migration,
|
||||
vm_remove_device, vm_resize, vm_resize_zone, vm_restore, vm_resume, vm_send_migration,
|
||||
vm_shutdown, vm_snapshot, vmm_ping, vmm_shutdown, ApiRequest, VmAction, VmConfig,
|
||||
};
|
||||
use micro_http::{Body, Method, Request, Response, StatusCode, Version};
|
||||
use std::sync::mpsc::Sender;
|
||||
@@ -153,6 +153,20 @@ impl EndpointHandler for VmActionHandler {
|
||||
)
|
||||
.map_err(HttpError::VmSnapshot),
|
||||
|
||||
ReceiveMigration(_) => vm_receive_migration(
|
||||
api_notifier,
|
||||
api_sender,
|
||||
Arc::new(serde_json::from_slice(body.raw())?),
|
||||
)
|
||||
.map_err(HttpError::VmReceiveMigration),
|
||||
|
||||
SendMigration(_) => vm_send_migration(
|
||||
api_notifier,
|
||||
api_sender,
|
||||
Arc::new(serde_json::from_slice(body.raw())?),
|
||||
)
|
||||
.map_err(HttpError::VmSendMigration),
|
||||
|
||||
_ => Err(HttpError::BadRequest),
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -39,11 +39,13 @@ pub mod http_endpoint;
|
||||
use crate::config::{
|
||||
DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, RestoreConfig, VmConfig, VsockConfig,
|
||||
};
|
||||
use crate::device_tree::DeviceTree;
|
||||
use crate::vm::{Error as VmError, VmState};
|
||||
use micro_http::Body;
|
||||
use std::io;
|
||||
use std::sync::mpsc::{channel, RecvError, SendError, Sender};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_migration::MigratableError;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// API errors are sent back from the VMM API server through the ApiResponse.
|
||||
@@ -138,6 +140,12 @@ pub enum ApiError {
|
||||
|
||||
/// The vsock device could not be added to the VM.
|
||||
VmAddVsock(VmError),
|
||||
|
||||
/// Error starting migration receiever
|
||||
VmReceiveMigration(MigratableError),
|
||||
|
||||
/// Error starting migration sender
|
||||
VmSendMigration(MigratableError),
|
||||
}
|
||||
pub type ApiResult<T> = std::result::Result<T, ApiError>;
|
||||
|
||||
@@ -146,6 +154,7 @@ pub struct VmInfo {
|
||||
pub config: Arc<Mutex<VmConfig>>,
|
||||
pub state: VmState,
|
||||
pub memory_actual_size: u64,
|
||||
pub device_tree: Option<Arc<Mutex<DeviceTree>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
@@ -177,6 +186,18 @@ pub struct VmSnapshotConfig {
|
||||
pub destination_url: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Default)]
|
||||
pub struct VmReceiveMigrationData {
|
||||
/// URL for the reception of migration state
|
||||
pub receiver_url: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Default)]
|
||||
pub struct VmSendMigrationData {
|
||||
/// URL to migrate the VM to
|
||||
pub destination_url: String,
|
||||
}
|
||||
|
||||
pub enum ApiResponsePayload {
|
||||
/// No data is sent on the channel.
|
||||
Empty,
|
||||
@@ -275,6 +296,12 @@ pub enum ApiRequest {
|
||||
|
||||
/// Restore from a VM snapshot
|
||||
VmRestore(Arc<RestoreConfig>, Sender<ApiResponse>),
|
||||
|
||||
/// Incoming migration
|
||||
VmReceiveMigration(Arc<VmReceiveMigrationData>, Sender<ApiResponse>),
|
||||
|
||||
/// Outgoing migration
|
||||
VmSendMigration(Arc<VmSendMigrationData>, Sender<ApiResponse>),
|
||||
}
|
||||
|
||||
pub fn vm_create(
|
||||
@@ -352,6 +379,12 @@ pub enum VmAction {
|
||||
|
||||
/// Snapshot VM
|
||||
Snapshot(Arc<VmSnapshotConfig>),
|
||||
|
||||
/// Incoming migration
|
||||
ReceiveMigration(Arc<VmReceiveMigrationData>),
|
||||
|
||||
/// Outgoing migration
|
||||
SendMigration(Arc<VmSendMigrationData>),
|
||||
}
|
||||
|
||||
fn vm_action(
|
||||
@@ -381,6 +414,8 @@ fn vm_action(
|
||||
ResizeZone(v) => ApiRequest::VmResizeZone(v, response_sender),
|
||||
Restore(v) => ApiRequest::VmRestore(v, response_sender),
|
||||
Snapshot(v) => ApiRequest::VmSnapshot(v, response_sender),
|
||||
ReceiveMigration(v) => ApiRequest::VmReceiveMigration(v, response_sender),
|
||||
SendMigration(v) => ApiRequest::VmSendMigration(v, response_sender),
|
||||
};
|
||||
|
||||
// Send the VM request.
|
||||
@@ -424,6 +459,22 @@ pub fn vm_counters(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResul
|
||||
vm_action(api_evt, api_sender, VmAction::Counters)
|
||||
}
|
||||
|
||||
pub fn vm_receive_migration(
|
||||
api_evt: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
data: Arc<VmReceiveMigrationData>,
|
||||
) -> ApiResult<Option<Body>> {
|
||||
vm_action(api_evt, api_sender, VmAction::ReceiveMigration(data))
|
||||
}
|
||||
|
||||
pub fn vm_send_migration(
|
||||
api_evt: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
data: Arc<VmSendMigrationData>,
|
||||
) -> ApiResult<Option<Body>> {
|
||||
vm_action(api_evt, api_sender, VmAction::SendMigration(data))
|
||||
}
|
||||
|
||||
pub fn vm_snapshot(
|
||||
api_evt: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
|
||||
@@ -363,8 +363,30 @@ components:
|
||||
memory_actual_size:
|
||||
type: integer
|
||||
format: int64
|
||||
device_tree:
|
||||
type: object
|
||||
additionalProperties:
|
||||
$ref: '#/components/schemas/DeviceNode'
|
||||
description: Virtual Machine information
|
||||
|
||||
DeviceNode:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
resources:
|
||||
type: array
|
||||
items:
|
||||
# Rust enum type (with data) which can't be better represented here
|
||||
type: object
|
||||
children:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
pci_bdf:
|
||||
type: integer
|
||||
format: int32
|
||||
|
||||
VmCounters:
|
||||
type: object
|
||||
additionalProperties:
|
||||
@@ -654,8 +676,12 @@ components:
|
||||
|
||||
FsConfig:
|
||||
required:
|
||||
- tag
|
||||
- cache_size
|
||||
- dax
|
||||
- num_queues
|
||||
- queue_size
|
||||
- socket
|
||||
- tag
|
||||
type: object
|
||||
properties:
|
||||
tag:
|
||||
|
||||
@@ -95,6 +95,8 @@ pub enum ValidationError {
|
||||
DiskSocketAndPath,
|
||||
/// Using vhost user requires shared memory
|
||||
VhostUserRequiresSharedMemory,
|
||||
/// No socket provided for vhost_use
|
||||
VhostUserMissingSocket,
|
||||
/// Trying to use IOMMU without PCI
|
||||
IommuUnsupported,
|
||||
/// Trying to use VFIO without PCI
|
||||
@@ -121,6 +123,7 @@ impl fmt::Display for ValidationError {
|
||||
VhostUserRequiresSharedMemory => {
|
||||
write!(f, "Using vhost-user requires using shared memory")
|
||||
}
|
||||
VhostUserMissingSocket => write!(f, "No socket provided when using vhost-user"),
|
||||
IommuUnsupported => write!(f, "Using an IOMMU without PCI support is unsupported"),
|
||||
VfioUnsupported => write!(f, "Using VFIO without PCI support is unsupported"),
|
||||
CpuTopologyZeroPart => write!(f, "No part of the CPU topology can be zero"),
|
||||
@@ -619,6 +622,9 @@ pub struct DiskConfig {
|
||||
pub poll_queue: bool,
|
||||
#[serde(default)]
|
||||
pub id: Option<String>,
|
||||
// For testing use only. Not exposed in API.
|
||||
#[serde(default)]
|
||||
pub disable_io_uring: bool,
|
||||
}
|
||||
|
||||
fn default_diskconfig_num_queues() -> usize {
|
||||
@@ -646,6 +652,7 @@ impl Default for DiskConfig {
|
||||
vhost_socket: None,
|
||||
poll_queue: default_diskconfig_poll_queue(),
|
||||
id: None,
|
||||
disable_io_uring: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -668,7 +675,8 @@ impl DiskConfig {
|
||||
.add("vhost_user")
|
||||
.add("socket")
|
||||
.add("poll_queue")
|
||||
.add("id");
|
||||
.add("id")
|
||||
.add("_disable_io_uring");
|
||||
parser.parse(disk).map_err(Error::ParseDisk)?;
|
||||
|
||||
let path = parser.get("path").map(PathBuf::from);
|
||||
@@ -707,6 +715,11 @@ impl DiskConfig {
|
||||
.unwrap_or_else(|| Toggle(default_diskconfig_poll_queue()))
|
||||
.0;
|
||||
let id = parser.get("id");
|
||||
let disable_io_uring = parser
|
||||
.convert::<Toggle>("_disable_io_uring")
|
||||
.map_err(Error::ParseDisk)?
|
||||
.unwrap_or(Toggle(false))
|
||||
.0;
|
||||
|
||||
if parser.is_set("poll_queue") && !vhost_user {
|
||||
warn!("poll_queue parameter currently only has effect when used vhost_user=true");
|
||||
@@ -723,6 +736,7 @@ impl DiskConfig {
|
||||
vhost_user,
|
||||
poll_queue,
|
||||
id,
|
||||
disable_io_uring,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1453,6 +1467,9 @@ impl VmConfig {
|
||||
if disk.vhost_user && !self.memory.shared {
|
||||
return Err(ValidationError::VhostUserRequiresSharedMemory);
|
||||
}
|
||||
if disk.vhost_user && disk.vhost_socket.is_none() {
|
||||
return Err(ValidationError::VhostUserMissingSocket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1788,9 +1805,9 @@ mod tests {
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
DiskConfig::parse("path=/path/to_file,vhost_user=true")?,
|
||||
DiskConfig::parse("vhost_user=true,socket=/tmp/sock")?,
|
||||
DiskConfig {
|
||||
path: Some(PathBuf::from("/path/to_file")),
|
||||
vhost_socket: Some(String::from("/tmp/sock")),
|
||||
vhost_user: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -2264,9 +2281,18 @@ mod tests {
|
||||
}]);
|
||||
assert!(invalid_config.validate().is_err());
|
||||
|
||||
let mut invalid_config = valid_config.clone();
|
||||
invalid_config.disks = Some(vec![DiskConfig {
|
||||
vhost_user: true,
|
||||
vhost_socket: Some("/path/to/sock".to_owned()),
|
||||
..Default::default()
|
||||
}]);
|
||||
assert!(invalid_config.validate().is_err());
|
||||
|
||||
let mut still_valid_config = valid_config.clone();
|
||||
still_valid_config.disks = Some(vec![DiskConfig {
|
||||
vhost_user: true,
|
||||
vhost_socket: Some("/path/to/sock".to_owned()),
|
||||
..Default::default()
|
||||
}]);
|
||||
still_valid_config.memory.shared = true;
|
||||
@@ -2282,6 +2308,7 @@ mod tests {
|
||||
let mut still_valid_config = valid_config.clone();
|
||||
still_valid_config.net = Some(vec![NetConfig {
|
||||
vhost_user: true,
|
||||
vhost_socket: Some("/path/to/sock".to_owned()),
|
||||
..Default::default()
|
||||
}]);
|
||||
still_valid_config.memory.shared = true;
|
||||
@@ -2293,7 +2320,7 @@ mod tests {
|
||||
}]);
|
||||
assert!(invalid_config.validate().is_err());
|
||||
|
||||
let mut still_valid_config = valid_config.clone();
|
||||
let mut still_valid_config = valid_config;
|
||||
invalid_config.fs = Some(vec![FsConfig {
|
||||
..Default::default()
|
||||
}]);
|
||||
|
||||
131
vmm/src/cpu.rs
131
vmm/src/cpu.rs
@@ -34,10 +34,9 @@ use devices::interrupt_controller::InterruptController;
|
||||
use hypervisor::kvm::kvm_bindings;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use hypervisor::CpuId;
|
||||
use hypervisor::{CpuState, VmExit};
|
||||
use seccomp::{SeccompAction, SeccompFilter};
|
||||
|
||||
use hypervisor::{vm::VmmOps, CpuState, HypervisorCpuError, VmExit};
|
||||
use libc::{c_void, siginfo_t};
|
||||
use seccomp::{SeccompAction, SeccompFilter};
|
||||
use std::os::unix::thread::JoinHandleExt;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
@@ -213,8 +212,6 @@ pub struct Vcpu {
|
||||
// The hypervisor abstracted CPU.
|
||||
vcpu: Arc<dyn hypervisor::Vcpu>,
|
||||
id: u8,
|
||||
#[cfg_attr(target_arch = "aarch64", allow(dead_code))]
|
||||
interrupt_controller: Option<Arc<Mutex<dyn InterruptController>>>,
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mpidr: u64,
|
||||
saved_state: Option<CpuState>,
|
||||
@@ -227,19 +224,19 @@ impl Vcpu {
|
||||
///
|
||||
/// * `id` - Represents the CPU number between [0, max vcpus).
|
||||
/// * `vm` - The virtual machine this vcpu will get attached to.
|
||||
/// * `vmmops` - Optional object for exit handling.
|
||||
pub fn new(
|
||||
id: u8,
|
||||
vm: &Arc<dyn hypervisor::Vm>,
|
||||
interrupt_controller: Option<Arc<Mutex<dyn InterruptController>>>,
|
||||
vmmops: Option<Arc<Box<dyn VmmOps>>>,
|
||||
) -> Result<Arc<Mutex<Self>>> {
|
||||
let vcpu = vm
|
||||
.create_vcpu(id)
|
||||
.create_vcpu(id, vmmops)
|
||||
.map_err(|e| Error::VcpuCreate(e.into()))?;
|
||||
// Initially the cpuid per vCPU is the one supported by this VM.
|
||||
Ok(Arc::new(Mutex::new(Vcpu {
|
||||
vcpu,
|
||||
id,
|
||||
interrupt_controller,
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mpidr: 0,
|
||||
saved_state: None,
|
||||
@@ -323,28 +320,8 @@ impl Vcpu {
|
||||
///
|
||||
/// Note that the state of the VCPU and associated VM must be setup first for this to do
|
||||
/// anything useful.
|
||||
pub fn run(&self) -> Result<bool> {
|
||||
match self.vcpu.run() {
|
||||
Ok(run) => match run {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
VmExit::IoapicEoi(vector) => {
|
||||
if let Some(interrupt_controller) = &self.interrupt_controller {
|
||||
interrupt_controller
|
||||
.lock()
|
||||
.unwrap()
|
||||
.end_of_interrupt(vector);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
VmExit::Ignore => Ok(true),
|
||||
VmExit::Reset => Ok(false),
|
||||
// No need to handle anything from a KVM HyperV exit
|
||||
VmExit::Hyperv => Ok(true),
|
||||
_ => Err(Error::UnexpectedVmExit),
|
||||
},
|
||||
|
||||
Err(e) => Err(Error::VcpuRun(e.into())),
|
||||
}
|
||||
pub fn run(&self) -> std::result::Result<VmExit, HypervisorCpuError> {
|
||||
self.vcpu.run()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,12 +402,14 @@ pub struct CpuManager {
|
||||
vm: Arc<dyn hypervisor::Vm>,
|
||||
vcpus_kill_signalled: Arc<AtomicBool>,
|
||||
vcpus_pause_signalled: Arc<AtomicBool>,
|
||||
exit_evt: EventFd,
|
||||
#[cfg_attr(target_arch = "aarch64", allow(dead_code))]
|
||||
reset_evt: EventFd,
|
||||
vcpu_states: Vec<VcpuState>,
|
||||
selected_cpu: u8,
|
||||
vcpus: Vec<Arc<Mutex<Vcpu>>>,
|
||||
seccomp_action: SeccompAction,
|
||||
vmmops: Arc<Box<dyn VmmOps>>,
|
||||
}
|
||||
|
||||
const CPU_ENABLE_FLAG: usize = 0;
|
||||
@@ -550,14 +529,17 @@ impl VcpuState {
|
||||
|
||||
impl CpuManager {
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
config: &CpusConfig,
|
||||
device_manager: &Arc<Mutex<DeviceManager>>,
|
||||
memory_manager: &Arc<Mutex<MemoryManager>>,
|
||||
vm: Arc<dyn hypervisor::Vm>,
|
||||
exit_evt: EventFd,
|
||||
reset_evt: EventFd,
|
||||
hypervisor: Arc<dyn hypervisor::Hypervisor>,
|
||||
seccomp_action: SeccompAction,
|
||||
vmmops: Arc<Box<dyn VmmOps>>,
|
||||
) -> Result<Arc<Mutex<CpuManager>>> {
|
||||
let guest_memory = memory_manager.lock().unwrap().guest_memory();
|
||||
let mut vcpu_states = Vec::with_capacity(usize::from(config.max_vcpus));
|
||||
@@ -584,10 +566,12 @@ impl CpuManager {
|
||||
vcpus_kill_signalled: Arc::new(AtomicBool::new(false)),
|
||||
vcpus_pause_signalled: Arc::new(AtomicBool::new(false)),
|
||||
vcpu_states,
|
||||
exit_evt,
|
||||
reset_evt,
|
||||
selected_cpu: 0,
|
||||
vcpus: Vec::with_capacity(usize::from(config.max_vcpus)),
|
||||
seccomp_action,
|
||||
vmmops,
|
||||
}));
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -679,13 +663,7 @@ impl CpuManager {
|
||||
) -> Result<Arc<Mutex<Vcpu>>> {
|
||||
info!("Creating vCPU: cpu_id = {}", cpu_id);
|
||||
|
||||
let interrupt_controller = if let Some(interrupt_controller) = &self.interrupt_controller {
|
||||
Some(interrupt_controller.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let vcpu = Vcpu::new(cpu_id, &self.vm, interrupt_controller)?;
|
||||
let vcpu = Vcpu::new(cpu_id, &self.vm, Some(self.vmmops.clone()))?;
|
||||
|
||||
if let Some(snapshot) = snapshot {
|
||||
// AArch64 vCPUs should be initialized after created.
|
||||
@@ -756,6 +734,7 @@ impl CpuManager {
|
||||
) -> Result<()> {
|
||||
let cpu_id = vcpu.lock().unwrap().id;
|
||||
let reset_evt = self.reset_evt.try_clone().unwrap();
|
||||
let exit_evt = self.exit_evt.try_clone().unwrap();
|
||||
let vcpu_kill_signalled = self.vcpus_kill_signalled.clone();
|
||||
let vcpu_pause_signalled = self.vcpus_pause_signalled.clone();
|
||||
|
||||
@@ -770,6 +749,14 @@ impl CpuManager {
|
||||
let vcpu_seccomp_filter = get_seccomp_filter(&self.seccomp_action, Thread::Vcpu)
|
||||
.map_err(Error::CreateSeccompFilter)?;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
let interrupt_controller_clone =
|
||||
if let Some(interrupt_controller) = &self.interrupt_controller {
|
||||
Some(interrupt_controller.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let handle = Some(
|
||||
thread::Builder::new()
|
||||
.name(format!("vcpu{}", cpu_id))
|
||||
@@ -798,6 +785,11 @@ impl CpuManager {
|
||||
// We enter a loop because park() could spuriously
|
||||
// return. We will then park() again unless the
|
||||
// pause boolean has been toggled.
|
||||
|
||||
// Need to use Ordering::SeqCst as we have multiple
|
||||
// loads and stores to different atomics and we need
|
||||
// to see them in a consistent order in all threads
|
||||
|
||||
if vcpu_pause_signalled.load(Ordering::SeqCst) {
|
||||
vcpu_run_interrupted.store(true, Ordering::SeqCst);
|
||||
while vcpu_pause_signalled.load(Ordering::SeqCst) {
|
||||
@@ -816,14 +808,39 @@ impl CpuManager {
|
||||
|
||||
// vcpu.run() returns false on a triple-fault so trigger a reset
|
||||
match vcpu.lock().unwrap().run() {
|
||||
Ok(run) => match run {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
VmExit::IoapicEoi(vector) => {
|
||||
if let Some(interrupt_controller) = &interrupt_controller_clone
|
||||
{
|
||||
interrupt_controller
|
||||
.lock()
|
||||
.unwrap()
|
||||
.end_of_interrupt(vector);
|
||||
}
|
||||
}
|
||||
VmExit::Ignore => {}
|
||||
VmExit::Hyperv => {}
|
||||
VmExit::Reset => {
|
||||
debug!("VmExit::Reset");
|
||||
vcpu_run_interrupted.store(true, Ordering::SeqCst);
|
||||
reset_evt.write(1).unwrap();
|
||||
break;
|
||||
}
|
||||
VmExit::Shutdown => {
|
||||
debug!("VmExit::Shutdown");
|
||||
vcpu_run_interrupted.store(true, Ordering::SeqCst);
|
||||
exit_evt.write(1).unwrap();
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
error!("VCPU generated error: {:?}", Error::UnexpectedVmExit);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
Err(e) => {
|
||||
error!("VCPU generated error: {:?}", e);
|
||||
break;
|
||||
}
|
||||
Ok(true) => {}
|
||||
Ok(false) => {
|
||||
vcpu_run_interrupted.store(true, Ordering::SeqCst);
|
||||
reset_evt.write(1).unwrap();
|
||||
error!("VCPU generated error: {:?}", Error::VcpuRun(e.into()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1343,7 +1360,7 @@ impl Pausable for CpuManager {
|
||||
for vcpu in self.vcpus.iter() {
|
||||
let mut vcpu = vcpu.lock().unwrap();
|
||||
vcpu.pause()?;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
if !self.config.kvm_hyperv {
|
||||
vcpu.vcpu.notify_guest_clock_paused().map_err(|e| {
|
||||
MigratableError::Pause(anyhow!(
|
||||
@@ -1407,7 +1424,7 @@ impl Snapshottable for CpuManager {
|
||||
impl Transportable for CpuManager {}
|
||||
impl Migratable for CpuManager {}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -1424,7 +1441,7 @@ mod tests {
|
||||
assert!(hv.check_capability(hypervisor::kvm::Cap::Irqchip));
|
||||
// Calling get_lapic will fail if there is no irqchip before hand.
|
||||
assert!(vm.create_irq_chip().is_ok());
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
let klapic_before: LapicState = vcpu.get_lapic().unwrap();
|
||||
|
||||
// Compute the value that is expected to represent LVT0 and LVT1.
|
||||
@@ -1447,7 +1464,7 @@ mod tests {
|
||||
fn test_setup_fpu() {
|
||||
let hv = hypervisor::new().unwrap();
|
||||
let vm = hv.create_vm().expect("new VM fd creation failed");
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
setup_fpu(&vcpu).unwrap();
|
||||
|
||||
let expected_fpu: FpuState = FpuState {
|
||||
@@ -1472,7 +1489,7 @@ mod tests {
|
||||
|
||||
let hv = hypervisor::new().unwrap();
|
||||
let vm = hv.create_vm().expect("new VM fd creation failed");
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
setup_msrs(&vcpu).unwrap();
|
||||
|
||||
// This test will check against the last MSR entry configured (the tenth one).
|
||||
@@ -1498,7 +1515,7 @@ mod tests {
|
||||
fn test_setup_regs() {
|
||||
let hv = hypervisor::new().unwrap();
|
||||
let vm = hv.create_vm().expect("new VM fd creation failed");
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
|
||||
let expected_regs: StandardRegisters = StandardRegisters {
|
||||
rflags: 0x0000000000000002u64,
|
||||
@@ -1526,10 +1543,10 @@ mod tests {
|
||||
fn test_setup_sregs() {
|
||||
let hv = hypervisor::new().unwrap();
|
||||
let vm = hv.create_vm().expect("new VM fd creation failed");
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
|
||||
let mut expected_sregs: SpecialRegisters = vcpu.get_sregs().unwrap();
|
||||
let gm = GuestMemoryMmap::from_ranges(&vec![(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
|
||||
configure_segments_and_sregs(&gm, &mut expected_sregs, BootProtocol::LinuxBoot).unwrap();
|
||||
setup_page_tables(&gm, &mut expected_sregs).unwrap();
|
||||
|
||||
@@ -1557,7 +1574,7 @@ mod tests {
|
||||
fn test_setup_regs() {
|
||||
let hv = hypervisor::new().unwrap();
|
||||
let vm = hv.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
let mut regions = Vec::new();
|
||||
regions.push((
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
@@ -1580,7 +1597,7 @@ mod tests {
|
||||
fn test_read_mpidr() {
|
||||
let hv = hypervisor::new().unwrap();
|
||||
let vm = hv.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
let mut kvi: kvm_vcpu_init = kvm_vcpu_init::default();
|
||||
vm.get_preferred_target(&mut kvi).unwrap();
|
||||
|
||||
@@ -1604,7 +1621,7 @@ mod tests {
|
||||
fn test_save_restore_core_regs() {
|
||||
let hv = hypervisor::new().unwrap();
|
||||
let vm = hv.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
let mut kvi: kvm_vcpu_init = kvm_vcpu_init::default();
|
||||
vm.get_preferred_target(&mut kvi).unwrap();
|
||||
|
||||
@@ -1640,7 +1657,7 @@ mod tests {
|
||||
fn test_save_restore_system_regs() {
|
||||
let hv = hypervisor::new().unwrap();
|
||||
let vm = hv.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
let mut kvi: kvm_vcpu_init = kvm_vcpu_init::default();
|
||||
vm.get_preferred_target(&mut kvi).unwrap();
|
||||
|
||||
@@ -1681,7 +1698,7 @@ mod tests {
|
||||
fn test_get_set_mpstate() {
|
||||
let hv = hypervisor::new().unwrap();
|
||||
let vm = hv.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
let mut kvi: kvm_vcpu_init = kvm_vcpu_init::default();
|
||||
vm.get_preferred_target(&mut kvi).unwrap();
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ use crate::config::{DiskConfig, FsConfig, NetConfig, PmemConfig, VmConfig, Vsock
|
||||
use crate::device_tree::{DeviceNode, DeviceTree};
|
||||
#[cfg(feature = "kvm")]
|
||||
use crate::interrupt::kvm::KvmMsiInterruptManager as MsiInterruptManager;
|
||||
#[cfg(feature = "mshv")]
|
||||
use crate::interrupt::mshv::MshvMsiInterruptManager as MsiInterruptManager;
|
||||
use crate::interrupt::LegacyUserspaceInterruptManager;
|
||||
use crate::memory_manager::{Error as MemoryManagerError, MemoryManager};
|
||||
#[cfg(feature = "acpi")]
|
||||
@@ -43,10 +45,12 @@ use devices::{
|
||||
interrupt_controller, interrupt_controller::InterruptController, legacy::Serial,
|
||||
HotPlugNotificationFlags,
|
||||
};
|
||||
use hypervisor::kvm_ioctls;
|
||||
#[cfg(feature = "kvm")]
|
||||
use hypervisor::kvm_ioctls::*;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use hypervisor::CpuState;
|
||||
#[cfg(feature = "mshv")]
|
||||
use hypervisor::IoEventAddress;
|
||||
use libc::TIOCGWINSZ;
|
||||
use libc::{MAP_NORESERVE, MAP_PRIVATE, MAP_SHARED, O_TMPFILE, PROT_READ, PROT_WRITE};
|
||||
use pci::{
|
||||
@@ -63,10 +67,8 @@ use std::num::Wrapping;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
#[cfg(feature = "kvm")]
|
||||
use std::os::unix::io::FromRawFd;
|
||||
use std::path::PathBuf;
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tempfile::NamedTempFile;
|
||||
#[cfg(feature = "kvm")]
|
||||
use vfio_ioctls::{VfioContainer, VfioDevice, VfioDmaMapping};
|
||||
use virtio_devices::transport::VirtioPciDevice;
|
||||
@@ -183,7 +185,7 @@ pub enum DeviceManagerError {
|
||||
AllocateIrq,
|
||||
|
||||
/// Cannot configure the IRQ.
|
||||
Irq(kvm_ioctls::Error),
|
||||
Irq(vmm_sys_util::errno::Error),
|
||||
|
||||
/// Cannot allocate PCI BARs
|
||||
AllocateBars(pci::PciDeviceError),
|
||||
@@ -614,6 +616,7 @@ impl DeviceRelocation for AddressManager {
|
||||
0,
|
||||
shm_regions.host_addr,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
self.vm.set_user_memory_region(mem_region).map_err(|e| {
|
||||
@@ -630,6 +633,7 @@ impl DeviceRelocation for AddressManager {
|
||||
shm_regions.len,
|
||||
shm_regions.host_addr,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
self.vm.set_user_memory_region(mem_region).map_err(|e| {
|
||||
@@ -656,17 +660,6 @@ impl DeviceRelocation for AddressManager {
|
||||
}
|
||||
}
|
||||
|
||||
struct ActivatedBackend {
|
||||
_socket_file: tempfile::NamedTempFile,
|
||||
child: std::process::Child,
|
||||
}
|
||||
|
||||
impl Drop for ActivatedBackend {
|
||||
fn drop(&mut self) {
|
||||
self.child.wait().ok();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct DeviceManagerState {
|
||||
device_tree: DeviceTree,
|
||||
@@ -733,12 +726,6 @@ pub struct DeviceManager {
|
||||
// which prevents cyclic dependencies.
|
||||
bus_devices: Vec<Arc<Mutex<dyn BusDevice>>>,
|
||||
|
||||
// The path to the VMM for self spawning
|
||||
vmm_path: PathBuf,
|
||||
|
||||
// Backends that have been spawned
|
||||
vhost_user_backends: Vec<ActivatedBackend>,
|
||||
|
||||
// Counter to keep track of the consumed device IDs.
|
||||
device_id_cnt: Wrapping<usize>,
|
||||
|
||||
@@ -799,7 +786,6 @@ impl DeviceManager {
|
||||
memory_manager: Arc<Mutex<MemoryManager>>,
|
||||
_exit_evt: &EventFd,
|
||||
reset_evt: &EventFd,
|
||||
vmm_path: PathBuf,
|
||||
seccomp_action: SeccompAction,
|
||||
#[cfg(feature = "acpi")] numa_nodes: NumaNodes,
|
||||
) -> DeviceManagerResult<Arc<Mutex<Self>>> {
|
||||
@@ -839,8 +825,6 @@ impl DeviceManager {
|
||||
memory_manager,
|
||||
virtio_devices: Vec::new(),
|
||||
bus_devices: Vec::new(),
|
||||
vmm_path,
|
||||
vhost_user_backends: Vec::new(),
|
||||
device_id_cnt: Wrapping(0),
|
||||
pci_bus: None,
|
||||
msi_interrupt_manager,
|
||||
@@ -1580,39 +1564,6 @@ impl DeviceManager {
|
||||
Ok(devices)
|
||||
}
|
||||
|
||||
/// Launch block backend
|
||||
fn start_block_backend(&mut self, disk_cfg: &DiskConfig) -> DeviceManagerResult<String> {
|
||||
let _socket_file = NamedTempFile::new().map_err(DeviceManagerError::CreateSocketFile)?;
|
||||
let socket = _socket_file.path().to_str().unwrap().to_owned();
|
||||
|
||||
let child = std::process::Command::new(&self.vmm_path)
|
||||
.args(&[
|
||||
"--block-backend",
|
||||
&format!(
|
||||
"path={},socket={},num_queues={},queue_size={}",
|
||||
disk_cfg
|
||||
.path
|
||||
.as_ref()
|
||||
.ok_or(DeviceManagerError::NoDiskPath)?
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
&socket,
|
||||
disk_cfg.num_queues,
|
||||
disk_cfg.queue_size
|
||||
),
|
||||
])
|
||||
.spawn()
|
||||
.map_err(DeviceManagerError::SpawnBlockBackend)?;
|
||||
|
||||
// The ActivatedBackend::drop() will automatically reap the child
|
||||
self.vhost_user_backends.push(ActivatedBackend {
|
||||
child,
|
||||
_socket_file,
|
||||
});
|
||||
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
fn make_virtio_block_device(
|
||||
&mut self,
|
||||
disk_cfg: &mut DiskConfig,
|
||||
@@ -1626,14 +1577,9 @@ impl DeviceManager {
|
||||
};
|
||||
|
||||
if disk_cfg.vhost_user {
|
||||
let socket = if let Some(socket) = disk_cfg.vhost_socket.clone() {
|
||||
socket
|
||||
} else {
|
||||
warn!("Self-spawning of vhost-user block backend is deprecated and will be removed in a future release.");
|
||||
self.start_block_backend(disk_cfg)?
|
||||
};
|
||||
let socket = disk_cfg.vhost_socket.as_ref().unwrap().clone();
|
||||
let vu_cfg = VhostUserConfig {
|
||||
socket: socket.clone(),
|
||||
socket,
|
||||
num_queues: disk_cfg.num_queues,
|
||||
queue_size: disk_cfg.queue_size,
|
||||
};
|
||||
@@ -1645,11 +1591,6 @@ impl DeviceManager {
|
||||
) {
|
||||
Ok(vub_device) => vub_device,
|
||||
Err(e) => {
|
||||
for vub in self.vhost_user_backends.iter_mut() {
|
||||
if vub._socket_file.path().to_str().unwrap() == socket {
|
||||
let _ = vub.child.kill();
|
||||
}
|
||||
}
|
||||
return Err(DeviceManagerError::CreateVhostUserBlk(e));
|
||||
}
|
||||
},
|
||||
@@ -1694,7 +1635,7 @@ impl DeviceManager {
|
||||
ImageType::Raw => {
|
||||
// Use asynchronous backend relying on io_uring if the
|
||||
// syscalls are supported.
|
||||
if block_io_uring_is_supported() {
|
||||
if block_io_uring_is_supported() && !disk_cfg.disable_io_uring {
|
||||
let dev = Arc::new(Mutex::new(
|
||||
virtio_devices::BlockIoUring::new(
|
||||
id.clone(),
|
||||
@@ -1797,40 +1738,6 @@ impl DeviceManager {
|
||||
Ok(devices)
|
||||
}
|
||||
|
||||
/// Launch network backend
|
||||
fn start_net_backend(&mut self, net_cfg: &NetConfig) -> DeviceManagerResult<String> {
|
||||
let _socket_file = NamedTempFile::new().map_err(DeviceManagerError::CreateSocketFile)?;
|
||||
let socket = _socket_file.path().to_str().unwrap().to_owned();
|
||||
|
||||
let child = std::process::Command::new(&self.vmm_path)
|
||||
.args(&[
|
||||
"--net-backend",
|
||||
&format!(
|
||||
"ip={},mask={},socket={},num_queues={},queue_size={}{}",
|
||||
net_cfg.ip,
|
||||
net_cfg.mask,
|
||||
&socket,
|
||||
net_cfg.num_queues,
|
||||
net_cfg.queue_size,
|
||||
if let Some(mac) = net_cfg.host_mac {
|
||||
format!(",host_mac={:}", mac)
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
),
|
||||
])
|
||||
.spawn()
|
||||
.map_err(DeviceManagerError::SpawnNetBackend)?;
|
||||
|
||||
// The ActivatedBackend::drop() will automatically reap the child
|
||||
self.vhost_user_backends.push(ActivatedBackend {
|
||||
child,
|
||||
_socket_file,
|
||||
});
|
||||
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
fn make_virtio_net_device(
|
||||
&mut self,
|
||||
net_cfg: &mut NetConfig,
|
||||
@@ -1844,14 +1751,9 @@ impl DeviceManager {
|
||||
};
|
||||
|
||||
if net_cfg.vhost_user {
|
||||
let socket = if let Some(socket) = net_cfg.vhost_socket.clone() {
|
||||
socket
|
||||
} else {
|
||||
warn!("Self-spawning of vhost-user net backend is deprecated and will be removed in a future release.");
|
||||
self.start_net_backend(net_cfg)?
|
||||
};
|
||||
let socket = net_cfg.vhost_socket.as_ref().unwrap().clone();
|
||||
let vu_cfg = VhostUserConfig {
|
||||
socket: socket.clone(),
|
||||
socket,
|
||||
num_queues: net_cfg.num_queues,
|
||||
queue_size: net_cfg.queue_size,
|
||||
};
|
||||
@@ -1864,11 +1766,6 @@ impl DeviceManager {
|
||||
) {
|
||||
Ok(vun_device) => vun_device,
|
||||
Err(e) => {
|
||||
for vun in self.vhost_user_backends.iter_mut() {
|
||||
if vun._socket_file.path().to_str().unwrap() == socket {
|
||||
let _ = vun.child.kill();
|
||||
}
|
||||
}
|
||||
return Err(DeviceManagerError::CreateVhostUserNet(e));
|
||||
}
|
||||
},
|
||||
@@ -2086,7 +1983,9 @@ impl DeviceManager {
|
||||
.memory_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.create_userspace_mapping(cache_base, cache_size, host_addr, false, false)
|
||||
.create_userspace_mapping(
|
||||
cache_base, cache_size, host_addr, false, false, false,
|
||||
)
|
||||
.map_err(DeviceManagerError::MemoryManager)?;
|
||||
|
||||
let mut region_list = Vec::new();
|
||||
@@ -2277,6 +2176,7 @@ impl DeviceManager {
|
||||
host_addr,
|
||||
pmem_cfg.mergeable,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.map_err(DeviceManagerError::MemoryManager)?;
|
||||
|
||||
@@ -2643,6 +2543,15 @@ impl DeviceManager {
|
||||
})
|
||||
.map_err(DeviceManagerError::VfioMapRegion)?;
|
||||
|
||||
let mut node = device_node!(vfio_name);
|
||||
|
||||
for region in vfio_pci_device.mmio_regions() {
|
||||
node.resources.push(Resource::MmioAddressRange {
|
||||
base: region.start.0,
|
||||
size: region.length as u64,
|
||||
});
|
||||
}
|
||||
|
||||
let vfio_pci_device = Arc::new(Mutex::new(vfio_pci_device));
|
||||
|
||||
self.add_pci_device(
|
||||
@@ -2653,6 +2562,10 @@ impl DeviceManager {
|
||||
pci_device_bdf,
|
||||
vfio_name.clone(),
|
||||
)?;
|
||||
self.device_tree
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(vfio_name.clone(), node);
|
||||
|
||||
Ok((pci_device_bdf, vfio_name))
|
||||
}
|
||||
@@ -3225,6 +3138,10 @@ impl DeviceManager {
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
pub fn device_tree(&self) -> Arc<Mutex<DeviceTree>> {
|
||||
self.device_tree.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
|
||||
@@ -39,7 +39,7 @@ impl InterruptRoute {
|
||||
}
|
||||
|
||||
pub fn enable(&self, vm: &Arc<dyn hypervisor::Vm>) -> Result<()> {
|
||||
if !self.registered.load(Ordering::SeqCst) {
|
||||
if !self.registered.load(Ordering::Acquire) {
|
||||
vm.register_irqfd(&self.irq_fd, self.gsi).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
@@ -48,14 +48,14 @@ impl InterruptRoute {
|
||||
})?;
|
||||
|
||||
// Update internals to track the irq_fd as "registered".
|
||||
self.registered.store(true, Ordering::SeqCst);
|
||||
self.registered.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disable(&self, vm: &Arc<dyn hypervisor::Vm>) -> Result<()> {
|
||||
if self.registered.load(Ordering::SeqCst) {
|
||||
if self.registered.load(Ordering::Acquire) {
|
||||
vm.unregister_irqfd(&self.irq_fd, self.gsi).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
@@ -64,7 +64,7 @@ impl InterruptRoute {
|
||||
})?;
|
||||
|
||||
// Update internals to track the irq_fd as "unregistered".
|
||||
self.registered.store(false, Ordering::SeqCst);
|
||||
self.registered.store(false, Ordering::Release);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -325,6 +325,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "kvm")]
|
||||
pub mod kvm {
|
||||
use super::*;
|
||||
use hypervisor::kvm::KVM_MSI_VALID_DEVID;
|
||||
@@ -395,6 +396,69 @@ pub mod kvm {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mshv")]
|
||||
pub mod mshv {
|
||||
use super::*;
|
||||
use hypervisor::mshv::*;
|
||||
|
||||
type MshvMsiInterruptGroup = MsiInterruptGroup<MshvIrqRoutingEntry>;
|
||||
type MshvRoutingEntry = RoutingEntry<MshvIrqRoutingEntry>;
|
||||
pub type MshvMsiInterruptManager = MsiInterruptManager<MshvIrqRoutingEntry>;
|
||||
|
||||
impl RoutingEntryExt for MshvRoutingEntry {
|
||||
fn make_entry(
|
||||
_vm: &Arc<dyn hypervisor::Vm>,
|
||||
gsi: u32,
|
||||
config: &InterruptSourceConfig,
|
||||
) -> Result<Box<Self>> {
|
||||
if let InterruptSourceConfig::MsiIrq(cfg) = &config {
|
||||
let route = MshvIrqRoutingEntry {
|
||||
gsi,
|
||||
route: MshvIrqRouting::Msi(MshvIrqRoutingMsi {
|
||||
address_lo: cfg.low_addr,
|
||||
address_hi: cfg.high_addr,
|
||||
data: cfg.data,
|
||||
}),
|
||||
};
|
||||
let entry = MshvRoutingEntry {
|
||||
route,
|
||||
masked: false,
|
||||
};
|
||||
|
||||
return Ok(Box::new(entry));
|
||||
}
|
||||
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Interrupt config type not supported",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl MsiInterruptGroupOps<MshvIrqRoutingEntry> for MshvMsiInterruptGroup {
|
||||
fn set_gsi_routes(
|
||||
&self,
|
||||
routes: &HashMap<u32, RoutingEntry<MshvIrqRoutingEntry>>,
|
||||
) -> Result<()> {
|
||||
let mut entry_vec: Vec<MshvIrqRoutingEntry> = Vec::new();
|
||||
for (_, entry) in routes.iter() {
|
||||
if entry.masked {
|
||||
continue;
|
||||
}
|
||||
|
||||
entry_vec.push(entry.route);
|
||||
}
|
||||
|
||||
self.vm.set_gsi_routing(&entry_vec).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Failed setting GSI routing: {}", e),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -415,7 +479,7 @@ mod tests {
|
||||
fn test_get_set_dist_regs() {
|
||||
let hv = hypervisor::new().unwrap();
|
||||
let vm = hv.create_vm().unwrap();
|
||||
let _ = vm.create_vcpu(0).unwrap();
|
||||
let _ = vm.create_vcpu(0, None).unwrap();
|
||||
let gic = create_gic(&vm, 1).expect("Cannot create gic");
|
||||
|
||||
let res = get_dist_regs(gic.device());
|
||||
@@ -431,7 +495,7 @@ mod tests {
|
||||
fn test_get_set_redist_regs() {
|
||||
let hv = hypervisor::new().unwrap();
|
||||
let vm = hv.create_vm().unwrap();
|
||||
let _ = vm.create_vcpu(0).unwrap();
|
||||
let _ = vm.create_vcpu(0, None).unwrap();
|
||||
let gic = create_gic(&vm, 1).expect("Cannot create gic");
|
||||
|
||||
let mut gicr_typer = Vec::new();
|
||||
@@ -449,7 +513,7 @@ mod tests {
|
||||
fn test_get_set_icc_regs() {
|
||||
let hv = hypervisor::new().unwrap();
|
||||
let vm = hv.create_vm().unwrap();
|
||||
let _ = vm.create_vcpu(0).unwrap();
|
||||
let _ = vm.create_vcpu(0, None).unwrap();
|
||||
let gic = create_gic(&vm, 1).expect("Cannot create gic");
|
||||
|
||||
let mut gicr_typer = Vec::new();
|
||||
@@ -467,7 +531,7 @@ mod tests {
|
||||
fn test_save_pending_tables() {
|
||||
let hv = hypervisor::new().unwrap();
|
||||
let vm = hv.create_vm().unwrap();
|
||||
let _ = vm.create_vcpu(0).unwrap();
|
||||
let _ = vm.create_vcpu(0, None).unwrap();
|
||||
let gic = create_gic(&vm, 1).expect("Cannot create gic");
|
||||
|
||||
assert!(save_pending_tables(gic.device()).is_ok());
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user