mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
350 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96be82296f | ||
|
|
5115ad6e56 | ||
|
|
d5bfa2dfc8 | ||
|
|
2f0bc06bec | ||
|
|
aaba6e777f | ||
|
|
9ab4bb1ae2 | ||
|
|
06487131f9 | ||
|
|
eeb7e10d1f | ||
|
|
9d84ef5073 | ||
|
|
14350f5de4 | ||
|
|
556871570e | ||
|
|
052eff1ca7 | ||
|
|
354c2a4b3d | ||
|
|
46e0b3ff75 | ||
|
|
bb7fa71fcb | ||
|
|
ec5ff395cf | ||
|
|
9b53044aae | ||
|
|
1592a9292f | ||
|
|
2e91b73881 | ||
|
|
9eb7413fab | ||
|
|
be946caf4b | ||
|
|
ff9c8b847f | ||
|
|
8183141399 | ||
|
|
e4386c8bb7 | ||
|
|
75ddd2a244 | ||
|
|
eac350c454 | ||
|
|
6802ef5406 | ||
|
|
d71d52e9b0 | ||
|
|
b08fde5928 | ||
|
|
8031ac33c3 | ||
|
|
50134969b9 | ||
|
|
ce794f7858 | ||
|
|
33b0e15804 | ||
|
|
ad9374bd68 | ||
|
|
8cef35745b | ||
|
|
ce7678f29f | ||
|
|
12758d7fad | ||
|
|
86fcd19b8a | ||
|
|
a5de49558e | ||
|
|
9ed880d74e | ||
|
|
7e0ab6b56d | ||
|
|
3012975c17 | ||
|
|
6c2bca5f1b | ||
|
|
8de7448d44 | ||
|
|
bf09a1e695 | ||
|
|
a76cf0865f | ||
|
|
99422324a7 | ||
|
|
1d61c476a1 | ||
|
|
f8501a3bd3 | ||
|
|
6e049e0da1 | ||
|
|
10348f73e4 | ||
|
|
9d1f95a3cc | ||
|
|
30e2e51588 | ||
|
|
dd9d0d0440 | ||
|
|
cdc8493a05 | ||
|
|
f5debc4bc0 | ||
|
|
37dfb4cbb3 | ||
|
|
e1a07ce3c4 | ||
|
|
1df38daf74 | ||
|
|
7481e4d959 | ||
|
|
2ac6971a8b | ||
|
|
3f42f86d81 | ||
|
|
d6aa717913 | ||
|
|
3eaeba4b55 | ||
|
|
df14a68e87 | ||
|
|
e685854f16 | ||
|
|
c790bba905 | ||
|
|
e525af7a61 | ||
|
|
3e8a6ba0c0 | ||
|
|
9ebf052538 | ||
|
|
f6b150a34d | ||
|
|
9f08f537e0 | ||
|
|
9c7215d620 | ||
|
|
35744375e0 | ||
|
|
4fc75cf2b0 | ||
|
|
d41ce909a2 | ||
|
|
f626bd60c5 | ||
|
|
5a380a6918 | ||
|
|
f8ee89a514 | ||
|
|
49322c5ebe | ||
|
|
24c2b67aa4 | ||
|
|
03dd24978e | ||
|
|
cf707da1a8 | ||
|
|
c22fd39170 | ||
|
|
0a97c25464 | ||
|
|
b2de1cd523 | ||
|
|
fbcf3a7a7a | ||
|
|
b0353992d6 | ||
|
|
3fb0a02fa2 | ||
|
|
8b823e5e34 | ||
|
|
c23b48888d | ||
|
|
f68b08bfdb | ||
|
|
18f7789a81 | ||
|
|
c2abadc293 | ||
|
|
bb2139a408 | ||
|
|
d35e775ed9 | ||
|
|
49cc73a4ca | ||
|
|
187b1eec8b | ||
|
|
a484aa7be6 | ||
|
|
ac7178ef2a | ||
|
|
b6fdbf7a44 | ||
|
|
12fec55064 | ||
|
|
fd45e94510 | ||
|
|
b7faf4fdc1 | ||
|
|
0870028fde | ||
|
|
592cfbafb3 | ||
|
|
621ea837fa | ||
|
|
a2830da7c0 | ||
|
|
926a414b90 | ||
|
|
747f31d82c | ||
|
|
5eb903a509 | ||
|
|
97e2d5d266 | ||
|
|
b8cfdab8b6 | ||
|
|
9bd5ec8967 | ||
|
|
e7e0e8ac38 | ||
|
|
82d0cdff4f | ||
|
|
a517be4eac | ||
|
|
13c8283fbe | ||
|
|
a31f5f8106 | ||
|
|
e78e34b36a | ||
|
|
808586ece7 | ||
|
|
ea82632c70 | ||
|
|
6389418fa6 | ||
|
|
f9a0445c3d | ||
|
|
444e5c2a04 | ||
|
|
5bab9c3894 | ||
|
|
514491a051 | ||
|
|
2fa652aa4c | ||
|
|
476e4ce24f | ||
|
|
b38470df4b | ||
|
|
1beb62ed2d | ||
|
|
a8ec8f3326 | ||
|
|
6cc8248a37 | ||
|
|
8ff3633782 | ||
|
|
56207a0328 | ||
|
|
a216c2ebd3 | ||
|
|
72fdfff15d | ||
|
|
70ecd6bab4 | ||
|
|
0c6706a510 | ||
|
|
6565e478e6 | ||
|
|
1a0a2c0182 | ||
|
|
cfffb7edb0 | ||
|
|
b927dceed8 | ||
|
|
cd2b03f6ed | ||
|
|
d9eec0de14 | ||
|
|
40e4dc6339 | ||
|
|
8f434df1fb | ||
|
|
b1554642e4 | ||
|
|
886c0f9093 | ||
|
|
6c164c761b | ||
|
|
0071ac8c30 | ||
|
|
2b7fbcb99a | ||
|
|
d1155c7c7f | ||
|
|
28abfa9de5 | ||
|
|
c260640fd5 | ||
|
|
4617aefd60 | ||
|
|
7134f3129f | ||
|
|
0fc3936448 | ||
|
|
b9f193703a | ||
|
|
2d3f518c72 | ||
|
|
eeb7e2529d | ||
|
|
12edb24678 | ||
|
|
31928fb103 | ||
|
|
11dd609fa5 | ||
|
|
aaf382eee2 | ||
|
|
3b0da2d895 | ||
|
|
99b2ada4d0 | ||
|
|
0ea706faf5 | ||
|
|
8d9d22436a | ||
|
|
a517ca23a0 | ||
|
|
6712958f23 | ||
|
|
b2cdee80b6 | ||
|
|
d771223b2f | ||
|
|
ee5a041a0f | ||
|
|
be4e1e8712 | ||
|
|
f0ab002ef1 | ||
|
|
b9f9f01fcc | ||
|
|
6eb721301c | ||
|
|
53613319cc | ||
|
|
2cd0bc0a2c | ||
|
|
b55b83c6e8 | ||
|
|
1ed357cf34 | ||
|
|
20ba271b6c | ||
|
|
e606112cef | ||
|
|
50b3f008d1 | ||
|
|
f787c409c4 | ||
|
|
722f9b6628 | ||
|
|
13756490b5 | ||
|
|
a0d5dbce6c | ||
|
|
93d3abfd6e | ||
|
|
12b036a824 | ||
|
|
183529d024 | ||
|
|
22958261aa | ||
|
|
c7dfbd8a84 | ||
|
|
d8119fda13 | ||
|
|
1a10f16ad0 | ||
|
|
f449486b9b | ||
|
|
a4e0ce58c7 | ||
|
|
c731a943d4 | ||
|
|
37264cf21b | ||
|
|
8665898ff3 | ||
|
|
a85e2fa735 | ||
|
|
bed282b801 | ||
|
|
2ae3392d32 | ||
|
|
143d63c88e | ||
|
|
5ab58e743a | ||
|
|
233ad78b3a | ||
|
|
13dc637350 | ||
|
|
7a071c28db | ||
|
|
e4cd3072d4 | ||
|
|
708dbb973a | ||
|
|
057e71d266 | ||
|
|
218c780f67 | ||
|
|
a5747a843e | ||
|
|
8754720e2d | ||
|
|
224e3ddef4 | ||
|
|
9e10244716 | ||
|
|
e40ae6274b | ||
|
|
be32065aa4 | ||
|
|
f01bd7d56d | ||
|
|
746138039d | ||
|
|
929142bc2e | ||
|
|
68203ea414 | ||
|
|
9e6a2825ba | ||
|
|
9e7231cd69 | ||
|
|
1e20b5727d | ||
|
|
baf4850052 | ||
|
|
00230905ff | ||
|
|
447af8e702 | ||
|
|
c73c9b112c | ||
|
|
0646a90626 | ||
|
|
b584ec3fb3 | ||
|
|
ef2b11ee6c | ||
|
|
622f3f8fb6 | ||
|
|
164e810069 | ||
|
|
1a2c1f9751 | ||
|
|
3eb11069d0 | ||
|
|
c3a3490331 | ||
|
|
7a50646c02 | ||
|
|
8ba37a98a7 | ||
|
|
b3e4111e1d | ||
|
|
98741573e7 | ||
|
|
3ef1c00cfb | ||
|
|
dc97b67dac | ||
|
|
859a96181f | ||
|
|
35c0ea6c25 | ||
|
|
fe2d884605 | ||
|
|
8f300bed83 | ||
|
|
92c73c3b78 | ||
|
|
39d4f817f0 | ||
|
|
cf8f8ce93a | ||
|
|
452475c280 | ||
|
|
1b1a2175ca | ||
|
|
2d17f4384a | ||
|
|
bf135a29f7 | ||
|
|
da4aaee778 | ||
|
|
6a934c32ee | ||
|
|
3b891cd304 | ||
|
|
d5199ad91d | ||
|
|
6a0b4d7a7c | ||
|
|
31bbe0e509 | ||
|
|
7f098168ac | ||
|
|
00a1eced55 | ||
|
|
f24b74246d | ||
|
|
e4ea8b0bef | ||
|
|
33be24bd5a | ||
|
|
78b5cbc63a | ||
|
|
0c29c2ec49 | ||
|
|
9e18177654 | ||
|
|
cc67131ecc | ||
|
|
e4a034aef9 | ||
|
|
8fc7bf2953 | ||
|
|
785812d976 | ||
|
|
5157ba10d1 | ||
|
|
3b470d4f4b | ||
|
|
6cce7b9560 | ||
|
|
1f9bc68c54 | ||
|
|
4db252b418 | ||
|
|
0ce7de3ef5 | ||
|
|
4b110d5b73 | ||
|
|
f3f4d07595 | ||
|
|
c73c31b66e | ||
|
|
6244beb9d5 | ||
|
|
57c3fa4b1e | ||
|
|
f664cddec9 | ||
|
|
8f323e61d8 | ||
|
|
42a9896fe4 | ||
|
|
9df601a1df | ||
|
|
41d7b3a387 | ||
|
|
15d9ec0149 | ||
|
|
abbd69abee | ||
|
|
4a7a2cff8c | ||
|
|
e63f98182a | ||
|
|
e6b934a56a | ||
|
|
51d102c708 | ||
|
|
8d05fb86e3 | ||
|
|
0b0510108d | ||
|
|
44aef8f438 | ||
|
|
75878dd90a | ||
|
|
f6f4c68fb4 | ||
|
|
15de30f141 | ||
|
|
f7def621dd | ||
|
|
8c3ea8cd76 | ||
|
|
a7296bbb52 | ||
|
|
61e34331c2 | ||
|
|
4c9d15d44c | ||
|
|
96d4f1e39e | ||
|
|
05ce2dc820 | ||
|
|
eec1a32d95 | ||
|
|
82cad99c0b | ||
|
|
f2151b2734 | ||
|
|
164ec2b8e6 | ||
|
|
b3082c1984 | ||
|
|
2be703ca92 | ||
|
|
66da29d8dd | ||
|
|
ede288789a | ||
|
|
c1e6d0022b | ||
|
|
890582b5a2 | ||
|
|
4de258477e | ||
|
|
f2682463a4 | ||
|
|
5a5b3cf23b | ||
|
|
dd8debf201 | ||
|
|
0c9c72c583 | ||
|
|
c95851f48f | ||
|
|
68293fc0e8 | ||
|
|
d75e7456fc | ||
|
|
7ff82af4b2 | ||
|
|
e54f8ec8a5 | ||
|
|
bc874a9b6f | ||
|
|
93becca82a | ||
|
|
feb8d7ae90 | ||
|
|
5120c275a2 | ||
|
|
f1a23d712f | ||
|
|
db62cb3f4d | ||
|
|
cb98d90097 | ||
|
|
708f02dc26 | ||
|
|
18fbd303ab | ||
|
|
bbc385c3f3 | ||
|
|
2fc86ffea6 | ||
|
|
4b462a54ee | ||
|
|
8acc15a63c | ||
|
|
38ed560d3a | ||
|
|
9f67de4735 | ||
|
|
ebab8090ee | ||
|
|
c67e407620 | ||
|
|
bdcfe1efbd | ||
|
|
70986022d8 | ||
|
|
f7197e8415 | ||
|
|
d11a67b0fe | ||
|
|
7257e890ef |
53
.github/workflows/build.yaml
vendored
53
.github/workflows/build.yaml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Cloud Hypervisor Actions
|
||||
name: Cloud Hypervisor Build
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
@@ -12,6 +12,9 @@ jobs:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
@@ -19,52 +22,8 @@ jobs:
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release
|
||||
|
||||
release:
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Rust toolchain (stable)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Build
|
||||
run: cargo build --release
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
draft: true
|
||||
prerelease: true
|
||||
- name: Upload cloud-hypervisor
|
||||
id: upload-release-cloud-hypervisor
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/release/cloud-hypervisor
|
||||
asset_name: cloud-hypervisor
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload ch-remote
|
||||
id: upload-release-ch-remote
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/release/ch-remote
|
||||
asset_name: ch-remote
|
||||
asset_content_type: application/octet-stream
|
||||
run: cargo build --release --target=${{ matrix.target }}
|
||||
|
||||
65
.github/workflows/release.yaml
vendored
Normal file
65
.github/workflows/release.yaml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: Cloud Hypervisor Release
|
||||
on: [create]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- name: Install Rust toolchain (x86_64-unknown-linux-musl)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: x86_64-unknown-linux-musl
|
||||
- name: Build
|
||||
run: cargo build --release --target=x86_64-unknown-linux-gnu
|
||||
- name: Static Build
|
||||
run: cargo build --release --target=x86_64-unknown-linux-musl
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
draft: true
|
||||
prerelease: true
|
||||
- name: Upload cloud-hypervisor
|
||||
id: upload-release-cloud-hypervisor
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/x86_64-unknown-linux-gnu/release/cloud-hypervisor
|
||||
asset_name: cloud-hypervisor
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload static cloud-hypervisor
|
||||
id: upload-release-static-cloud-hypervisor
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/x86_64-unknown-linux-musl/release/cloud-hypervisor
|
||||
asset_name: cloud-hypervisor-static
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload ch-remote
|
||||
id: upload-release-ch-remote
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/x86_64-unknown-linux-gnu/release/ch-remote
|
||||
asset_name: ch-remote
|
||||
asset_content_type: application/octet-stream
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/build
|
||||
/target
|
||||
**/*.rs.bk
|
||||
**/Cargo.lock
|
||||
|
||||
1237
Cargo.lock
generated
1237
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
13
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cloud-hypervisor"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
default-run = "cloud-hypervisor"
|
||||
@@ -12,8 +12,9 @@ clap = { version = "2.33.0", features=["wrap_help"] }
|
||||
epoll = ">=4.0.1"
|
||||
futures = { version = "0.3.4", features = ["thread-pool"] }
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.68"
|
||||
libc = "0.2.69"
|
||||
log = { version = "0.4.8", features = ["std"] }
|
||||
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.21.1" }
|
||||
serde_json = ">=1.0.9"
|
||||
vhost_user_backend = { path = "vhost_user_backend"}
|
||||
vhost_user_block = { path = "vhost_user_block"}
|
||||
@@ -22,8 +23,8 @@ vhost_user_net = { path = "vhost_user_net"}
|
||||
virtio-bindings = "0.1.0"
|
||||
vmm = { path = "vmm" }
|
||||
vm-device = { path = "vm-device" }
|
||||
vm-memory = "0.1.0"
|
||||
vmm-sys-util = "0.4.0"
|
||||
vm-memory = "0.2.0"
|
||||
vmm-sys-util = "0.5.0"
|
||||
vm-virtio = { path = "vm-virtio" }
|
||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
||||
|
||||
@@ -33,6 +34,8 @@ dirs = "2.0.2"
|
||||
credibility = "0.1.3"
|
||||
tempdir= "0.3.7"
|
||||
lazy_static= "1.4.0"
|
||||
tempfile = "3.1.0"
|
||||
serde_json = ">=1.0.9"
|
||||
|
||||
[features]
|
||||
default = ["acpi", "pci", "cmos"]
|
||||
@@ -40,6 +43,7 @@ acpi = ["vmm/acpi"]
|
||||
pci = ["vmm/pci_support"]
|
||||
mmio = ["vmm/mmio_support"]
|
||||
cmos = ["vmm/cmos"]
|
||||
fwdebug = ["vmm/fwdebug"]
|
||||
|
||||
# Integration tests require a special environment to run in
|
||||
integration_tests = []
|
||||
@@ -53,6 +57,7 @@ members = [
|
||||
"vmm",
|
||||
"vm-virtio",
|
||||
"vm-device",
|
||||
"vm-migration",
|
||||
"vhost_user_block",
|
||||
"vhost_user_backend",
|
||||
"vhost_user_fs",
|
||||
|
||||
12
Jenkinsfile
vendored
12
Jenkinsfile
vendored
@@ -65,6 +65,18 @@ pipeline{
|
||||
sh "scripts/dev_cli.sh tests --integration"
|
||||
}
|
||||
}
|
||||
stage ('Run unit tests for musl') {
|
||||
when { branch 'master' }
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --unit --libc musl"
|
||||
}
|
||||
}
|
||||
stage ('Run integration tests for musl') {
|
||||
when { branch 'master' }
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration --libc musl"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,10 +229,10 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
|
||||
`cloud-hypervisor` is in a very early, pre-alpha stage. Use at your own risk!
|
||||
|
||||
As of 2019-12-12, the following cloud images are supported:
|
||||
As of 2020-04-23, the following cloud images are supported:
|
||||
* [Clear Linux](https://download.clearlinux.org/current/) (cloudguest and kvm)
|
||||
* [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (cloudimg)
|
||||
* [Ubuntu Eoan](https://cloud-images.ubuntu.com/eoan/current/) (cloudimg)
|
||||
* [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (cloudimg)
|
||||
|
||||
Direct kernel boot to userspace should work with most rootfs.
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vm-memory = "0.1.0"
|
||||
vm-memory = "0.2.0"
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ default = []
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3.4"
|
||||
kvm-bindings = "0.2.0"
|
||||
kvm-ioctls = "0.5.0"
|
||||
libc = "0.2.68"
|
||||
vm-memory = { version = "0.1.0", features = ["backend-mmap"] }
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch" }
|
||||
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
|
||||
libc = "0.2.69"
|
||||
vm-memory = { version = "0.2.0", features = ["backend-mmap"] }
|
||||
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
arch_gen = { path = "../arch_gen" }
|
||||
|
||||
@@ -41,6 +41,10 @@ pub enum Error {
|
||||
StartInfoPastRamEnd,
|
||||
/// Error writing hvm_start_info to guest memory.
|
||||
StartInfoSetup,
|
||||
/// Failed to compute initramfs address.
|
||||
InitramfsAddress,
|
||||
/// Error writing module entry to guest memory.
|
||||
ModlistSetup(vm_memory::GuestMemoryError),
|
||||
}
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
@@ -76,6 +80,21 @@ pub mod x86_64;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::{
|
||||
arch_memory_regions, configure_system, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START,
|
||||
BootProtocol, EntryPoint,
|
||||
arch_memory_regions, configure_system, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
||||
layout::CMDLINE_START, regs, BootProtocol, EntryPoint,
|
||||
};
|
||||
|
||||
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
||||
#[inline(always)]
|
||||
fn pagesize() -> usize {
|
||||
// Trivially safe
|
||||
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
|
||||
}
|
||||
|
||||
/// Type for passing information about the initramfs in the guest memory.
|
||||
pub struct InitramfsConfig {
|
||||
/// Load address of initramfs in guest memory
|
||||
pub address: vm_memory::GuestAddress,
|
||||
/// Size of initramfs in guest memory
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ pub const BOOT_IDT_START: GuestAddress = GuestAddress(0x520);
|
||||
/// Address for the hvm_start_info struct used in PVH boot
|
||||
pub const PVH_INFO_START: GuestAddress = GuestAddress(0x6000);
|
||||
|
||||
/// Starting address of array of modules of hvm_modlist_entry type.
|
||||
/// Used to enable initrd support using the PVH boot ABI.
|
||||
pub const MODLIST_START: GuestAddress = GuestAddress(0x6040);
|
||||
|
||||
/// Address of memory map table used in PVH boot. Can overlap
|
||||
/// with the zero page address since they are mutually exclusive.
|
||||
pub const MEMMAP_START: GuestAddress = GuestAddress(0x7000);
|
||||
@@ -74,18 +78,18 @@ 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: GuestUsize = 1024 << 20;
|
||||
|
||||
// == Fixed constants within the "32-bit reserved" range ==
|
||||
|
||||
// Sub range: 32-bit PCI devices (start: 3GiB, length: 640Mib)
|
||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = MEM_32BIT_RESERVED_START;
|
||||
pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = (640 << 20);
|
||||
pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = 640 << 20;
|
||||
|
||||
// PCI MMCONFIG space (start: after the device space, length: 256MiB)
|
||||
pub const PCI_MMCONFIG_START: GuestAddress =
|
||||
GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE);
|
||||
pub const PCI_MMCONFIG_SIZE: GuestUsize = (256 << 20);
|
||||
pub const PCI_MMCONFIG_SIZE: GuestUsize = 256 << 20;
|
||||
|
||||
// IOAPIC
|
||||
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
|
||||
|
||||
@@ -13,12 +13,16 @@ pub mod layout;
|
||||
mod mptable;
|
||||
pub mod regs;
|
||||
|
||||
use crate::InitramfsConfig;
|
||||
use crate::RegionType;
|
||||
use linux_loader::loader::bootparam::{boot_params, setup_header};
|
||||
use linux_loader::loader::start_info::{hvm_memmap_table_entry, hvm_start_info};
|
||||
use linux_loader::loader::elf::start_info::{
|
||||
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
|
||||
};
|
||||
use std::mem;
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion,
|
||||
GuestUsize,
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@@ -66,6 +70,11 @@ struct MemmapTableEntryWrapper(hvm_memmap_table_entry);
|
||||
|
||||
unsafe impl ByteValued for MemmapTableEntryWrapper {}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct ModlistEntryWrapper(hvm_modlist_entry);
|
||||
|
||||
unsafe impl ByteValued for ModlistEntryWrapper {}
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `DataInit`) where:
|
||||
// * the type that is implementing the trait is foreign or
|
||||
@@ -151,6 +160,7 @@ pub fn configure_system(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
cmdline_size: usize,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
num_cpus: u8,
|
||||
setup_hdr: Option<setup_header>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
@@ -161,10 +171,17 @@ pub fn configure_system(
|
||||
|
||||
match boot_prot {
|
||||
BootProtocol::PvhBoot => {
|
||||
configure_pvh(guest_mem, cmdline_addr, rsdp_addr)?;
|
||||
configure_pvh(guest_mem, cmdline_addr, initramfs, rsdp_addr)?;
|
||||
}
|
||||
BootProtocol::LinuxBoot => {
|
||||
configure_64bit_boot(guest_mem, cmdline_addr, cmdline_size, setup_hdr, rsdp_addr)?;
|
||||
configure_64bit_boot(
|
||||
guest_mem,
|
||||
cmdline_addr,
|
||||
cmdline_size,
|
||||
initramfs,
|
||||
setup_hdr,
|
||||
rsdp_addr,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +191,7 @@ pub fn configure_system(
|
||||
fn configure_pvh(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
) -> super::Result<()> {
|
||||
const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578;
|
||||
@@ -190,6 +208,24 @@ fn configure_pvh(
|
||||
start_info.0.rsdp_paddr = rsdp_addr.0;
|
||||
}
|
||||
|
||||
if let Some(initramfs_config) = initramfs {
|
||||
// The initramfs has been written to guest memory already, here we just need to
|
||||
// create the module structure that describes it.
|
||||
let ramdisk_mod: ModlistEntryWrapper = ModlistEntryWrapper(hvm_modlist_entry {
|
||||
paddr: initramfs_config.address.raw_value(),
|
||||
size: initramfs_config.size as u64,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
start_info.0.nr_modules += 1;
|
||||
start_info.0.modlist_paddr = layout::MODLIST_START.raw_value();
|
||||
|
||||
// Write the modlist struct to guest memory.
|
||||
guest_mem
|
||||
.write_obj(ramdisk_mod, layout::MODLIST_START)
|
||||
.map_err(super::Error::ModlistSetup)?;
|
||||
}
|
||||
|
||||
// Vector to hold the memory maps which needs to be written to guest memory
|
||||
// at MEMMAP_START after all of the mappings are recorded.
|
||||
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
||||
@@ -293,6 +329,7 @@ fn configure_64bit_boot(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
cmdline_size: usize,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
setup_hdr: Option<setup_header>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
) -> super::Result<()> {
|
||||
@@ -319,6 +356,11 @@ fn configure_64bit_boot(
|
||||
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||
params.0.hdr.cmdline_size = cmdline_size as u32;
|
||||
|
||||
if let Some(initramfs_config) = initramfs {
|
||||
params.0.hdr.ramdisk_image = initramfs_config.address.raw_value() as u32;
|
||||
params.0.hdr.ramdisk_size = initramfs_config.size as u32;
|
||||
}
|
||||
|
||||
add_e820_entry(&mut params.0, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
|
||||
|
||||
let mem_end = guest_mem.last_addr();
|
||||
@@ -388,6 +430,25 @@ fn add_e820_entry(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the memory address where the initramfs could be loaded.
|
||||
pub fn initramfs_load_addr(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
initramfs_size: usize,
|
||||
) -> super::Result<u64> {
|
||||
let first_region = guest_mem
|
||||
.find_region(GuestAddress::new(0))
|
||||
.ok_or(super::Error::InitramfsAddress)?;
|
||||
// It's safe to cast to usize because the size of a region can't be greater than usize.
|
||||
let lowmem_size = first_region.len() as usize;
|
||||
|
||||
if lowmem_size < initramfs_size {
|
||||
return Err(super::Error::InitramfsAddress);
|
||||
}
|
||||
|
||||
let aligned_addr: u64 = ((lowmem_size - initramfs_size) & !(crate::pagesize() - 1)) as u64;
|
||||
Ok(aligned_addr)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -417,6 +478,7 @@ mod tests {
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
1,
|
||||
None,
|
||||
None,
|
||||
@@ -437,6 +499,7 @@ mod tests {
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
@@ -448,6 +511,7 @@ mod tests {
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
@@ -468,6 +532,7 @@ mod tests {
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
@@ -479,6 +544,7 @@ mod tests {
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
@@ -499,6 +565,7 @@ mod tests {
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
@@ -510,6 +577,7 @@ mod tests {
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -70,7 +70,7 @@ pub fn setup_fpu(vcpu: &VcpuFd) -> Result<()> {
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
|
||||
vcpu.set_msrs(&create_msr_entries())
|
||||
vcpu.set_msrs(&boot_msr_entries())
|
||||
.map_err(Error::SetModelSpecificRegisters)?;
|
||||
|
||||
Ok(())
|
||||
@@ -242,68 +242,43 @@ fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_msr_entries() -> Msrs {
|
||||
let mut entries = Vec::<kvm_msr_entry>::new();
|
||||
macro_rules! kvm_msr {
|
||||
($msr:expr) => {
|
||||
kvm_msr_entry {
|
||||
index: $msr,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_SYSENTER_CS,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_SYSENTER_ESP,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_SYSENTER_EIP,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
// x86_64 specific msrs, we only run on x86_64 not x86.
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_STAR,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_CSTAR,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_KERNEL_GS_BASE,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_SYSCALL_MASK,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_LSTAR,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
// end of x86_64 specific code
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_TSC,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_MISC_ENABLE,
|
||||
data: msr_index::MSR_IA32_MISC_ENABLE_FAST_STRING as u64,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_MTRRdefType,
|
||||
data: MTRR_ENABLE | MTRR_MEM_TYPE_WB,
|
||||
..Default::default()
|
||||
});
|
||||
macro_rules! kvm_msr_data {
|
||||
($msr:expr, $data:expr) => {
|
||||
kvm_msr_entry {
|
||||
index: $msr,
|
||||
data: $data,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Msrs::from_entries(&entries)
|
||||
pub fn boot_msr_entries() -> Msrs {
|
||||
Msrs::from_entries(&[
|
||||
kvm_msr!(msr_index::MSR_IA32_SYSENTER_CS),
|
||||
kvm_msr!(msr_index::MSR_IA32_SYSENTER_ESP),
|
||||
kvm_msr!(msr_index::MSR_IA32_SYSENTER_EIP),
|
||||
kvm_msr!(msr_index::MSR_STAR),
|
||||
kvm_msr!(msr_index::MSR_CSTAR),
|
||||
kvm_msr!(msr_index::MSR_LSTAR),
|
||||
kvm_msr!(msr_index::MSR_KERNEL_GS_BASE),
|
||||
kvm_msr!(msr_index::MSR_SYSCALL_MASK),
|
||||
kvm_msr!(msr_index::MSR_IA32_TSC),
|
||||
kvm_msr_data!(
|
||||
msr_index::MSR_IA32_MISC_ENABLE,
|
||||
msr_index::MSR_IA32_MISC_ENABLE_FAST_STRING as u64
|
||||
),
|
||||
kvm_msr_data!(msr_index::MSR_MTRRdefType, MTRR_ENABLE | MTRR_MEM_TYPE_WB),
|
||||
])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -451,7 +426,7 @@ mod tests {
|
||||
// Official entries that were setup when we did setup_msrs. We need to assert that the
|
||||
// tenth one (i.e the one with index msr_index::MSR_IA32_MISC_ENABLE has the data we
|
||||
// expect.
|
||||
let entry_vec = create_msr_entries();
|
||||
let entry_vec = boot_msr_entries();
|
||||
assert_eq!(entry_vec.as_slice()[9], msrs.as_slice()[0]);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,19 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bitflags = ">=1.2.1"
|
||||
byteorder = "1.3.4"
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.68"
|
||||
libc = "0.2.69"
|
||||
log = "0.4.8"
|
||||
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.1.0"
|
||||
vm-memory = "0.2.0"
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -21,3 +26,4 @@ tempfile = "3.1.0"
|
||||
default = []
|
||||
acpi = ["acpi_tables"]
|
||||
cmos = []
|
||||
fwdebug = []
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
|
||||
|
||||
use crate::BusDevice;
|
||||
use anyhow::anyhow;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::io;
|
||||
use std::result;
|
||||
@@ -19,6 +20,14 @@ use vm_device::interrupt::{
|
||||
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||
};
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "GuestAddress")]
|
||||
pub struct GuestAddressDef(pub u64);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -99,25 +108,6 @@ fn set_remote_irr(entry: &mut RedirectionTableEntry, val: u8) {
|
||||
*entry |= u64::from(val & 0x1) << 14;
|
||||
}
|
||||
|
||||
pub struct MsiMessage {
|
||||
// Message Address Register
|
||||
// 31-20: Base address. Fixed value (0x0FEE)
|
||||
// 19-12: Destination ID
|
||||
// 11-4: Reserved
|
||||
// 3: Redirection Hint indication
|
||||
// 2: Destination Mode
|
||||
// 1-0: Reserved
|
||||
pub addr: u32,
|
||||
// Message Data Register
|
||||
// 32-16: Reserved
|
||||
// 15: Trigger Mode. 0 = Edge, 1 = Level
|
||||
// 14: Level. 0 = Deassert, 1 = Assert
|
||||
// 13-11: Reserved
|
||||
// 10-8: Delivery Mode
|
||||
// 7-0: Vector
|
||||
pub data: u32,
|
||||
}
|
||||
|
||||
pub const NUM_IOAPIC_PINS: usize = 24;
|
||||
const IOAPIC_VERSION_ID: u32 = 0x0017_0011;
|
||||
|
||||
@@ -166,13 +156,25 @@ fn decode_irq_from_selector(selector: u8) -> (usize, bool) {
|
||||
}
|
||||
|
||||
pub struct Ioapic {
|
||||
id: u32,
|
||||
id: String,
|
||||
id_reg: u32,
|
||||
reg_sel: u32,
|
||||
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
|
||||
used_entries: [bool; NUM_IOAPIC_PINS],
|
||||
apic_address: GuestAddress,
|
||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct IoapicState {
|
||||
id_reg: u32,
|
||||
reg_sel: u32,
|
||||
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
|
||||
used_entries: [bool; NUM_IOAPIC_PINS],
|
||||
#[serde(with = "GuestAddressDef")]
|
||||
apic_address: GuestAddress,
|
||||
}
|
||||
|
||||
impl BusDevice for Ioapic {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
assert!(data.len() == 4);
|
||||
@@ -210,6 +212,7 @@ impl BusDevice for Ioapic {
|
||||
|
||||
impl Ioapic {
|
||||
pub fn new(
|
||||
id: String,
|
||||
apic_address: GuestAddress,
|
||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
) -> Result<Ioapic> {
|
||||
@@ -225,9 +228,11 @@ impl Ioapic {
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
|
||||
Ok(Ioapic {
|
||||
id: 0,
|
||||
id,
|
||||
id_reg: 0,
|
||||
reg_sel: 0,
|
||||
reg_entries: [0; NUM_IOAPIC_PINS],
|
||||
used_entries: [false; NUM_IOAPIC_PINS],
|
||||
apic_address,
|
||||
interrupt_source_group,
|
||||
})
|
||||
@@ -342,7 +347,7 @@ impl Ioapic {
|
||||
debug!("IOAPIC_W reg 0x{:x}, val 0x{:x}", self.reg_sel, val);
|
||||
|
||||
match self.reg_sel as u8 {
|
||||
IOAPIC_REG_ID => self.id = (val >> 24) & 0xf,
|
||||
IOAPIC_REG_ID => self.id_reg = (val >> 24) & 0xf,
|
||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||
if is_high_bits {
|
||||
@@ -360,6 +365,8 @@ impl Ioapic {
|
||||
if let Err(e) = self.update_entry(index) {
|
||||
error!("Failed updating IOAPIC entry: {:?}", e);
|
||||
}
|
||||
// Store the information this IRQ is now being used.
|
||||
self.used_entries[index] = true;
|
||||
}
|
||||
_ => error!("IOAPIC: invalid write to register offset"),
|
||||
}
|
||||
@@ -370,7 +377,7 @@ impl Ioapic {
|
||||
|
||||
match self.reg_sel as u8 {
|
||||
IOAPIC_REG_VERSION => IOAPIC_VERSION_ID,
|
||||
IOAPIC_REG_ID | IOAPIC_REG_ARBITRATION_ID => (self.id & 0xf) << 24,
|
||||
IOAPIC_REG_ID | IOAPIC_REG_ARBITRATION_ID => (self.id_reg & 0xf) << 24,
|
||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||
if is_high_bits {
|
||||
@@ -385,4 +392,74 @@ impl Ioapic {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> IoapicState {
|
||||
IoapicState {
|
||||
id_reg: self.id_reg,
|
||||
reg_sel: self.reg_sel,
|
||||
reg_entries: self.reg_entries,
|
||||
used_entries: self.used_entries,
|
||||
apic_address: self.apic_address,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &IoapicState) -> Result<()> {
|
||||
self.id_reg = state.id_reg;
|
||||
self.reg_sel = state.reg_sel;
|
||||
self.reg_entries = state.reg_entries;
|
||||
self.used_entries = state.used_entries;
|
||||
self.apic_address = state.apic_address;
|
||||
for (irq, entry) in self.used_entries.iter().enumerate() {
|
||||
if *entry {
|
||||
self.update_entry(irq)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for Ioapic {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut ioapic_snapshot = Snapshot::new(self.id.as_str());
|
||||
ioapic_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(ioapic_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(ioapic_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||
let ioapic_state = match serde_json::from_slice(&ioapic_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize IOAPIC {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
return self.set_state(&ioapic_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not restore IOAPIC state {:?}", e))
|
||||
});
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find IOAPIC snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Ioapic {}
|
||||
impl Transportable for Ioapic {}
|
||||
impl Migratable for Ioapic {}
|
||||
|
||||
40
devices/src/legacy/fwdebug.rs
Normal file
40
devices/src/legacy/fwdebug.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
//
|
||||
|
||||
use BusDevice;
|
||||
|
||||
/// Provides firmware debug output via I/O port controls
|
||||
#[derive(Default)]
|
||||
pub struct FwDebugDevice {}
|
||||
|
||||
impl FwDebugDevice {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
/// FwDebugDevice sits on the I/O bus as 0x402 and receives ASCII characters
|
||||
impl BusDevice for FwDebugDevice {
|
||||
/// Upon read return the magic value to indicate that there is a debug port
|
||||
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||
if data.len() == 1 {
|
||||
data[0] = 0xe9
|
||||
} else {
|
||||
error!("Invalid read size on debug port: {}", data.len())
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) {
|
||||
if data.len() == 1 {
|
||||
print!("{}", data[0] as char);
|
||||
} else {
|
||||
error!("Invalid write size on debug port: {}", data.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,14 @@
|
||||
|
||||
#[cfg(feature = "cmos")]
|
||||
mod cmos;
|
||||
#[cfg(feature = "fwdebug")]
|
||||
mod fwdebug;
|
||||
mod i8042;
|
||||
mod serial;
|
||||
|
||||
#[cfg(feature = "cmos")]
|
||||
pub use self::cmos::Cmos;
|
||||
#[cfg(feature = "fwdebug")]
|
||||
pub use self::fwdebug::FwDebugDevice;
|
||||
pub use self::i8042::I8042Device;
|
||||
pub use self::serial::Serial;
|
||||
|
||||
@@ -6,10 +6,15 @@
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use crate::BusDevice;
|
||||
use anyhow::anyhow;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use std::{io, result};
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
use vmm_sys_util::errno::Result;
|
||||
|
||||
const LOOP_SIZE: usize = 0x40;
|
||||
@@ -55,6 +60,7 @@ const DEFAULT_BAUD_DIVISOR: u16 = 12; // 9600 bps
|
||||
/// This can optionally write the guest's output to a Write trait object. To send input to the
|
||||
/// guest, use `queue_input_bytes`.
|
||||
pub struct Serial {
|
||||
id: String,
|
||||
interrupt_enable: u8,
|
||||
interrupt_identification: u8,
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
@@ -68,12 +74,27 @@ pub struct Serial {
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SerialState {
|
||||
interrupt_enable: u8,
|
||||
interrupt_identification: u8,
|
||||
line_control: u8,
|
||||
line_status: u8,
|
||||
modem_control: u8,
|
||||
modem_status: u8,
|
||||
scratch: u8,
|
||||
baud_divisor: u16,
|
||||
in_buffer: VecDeque<u8>,
|
||||
}
|
||||
|
||||
impl Serial {
|
||||
pub fn new(
|
||||
id: String,
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
) -> Serial {
|
||||
Serial {
|
||||
id,
|
||||
interrupt_enable: 0,
|
||||
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
|
||||
interrupt,
|
||||
@@ -90,15 +111,16 @@ impl Serial {
|
||||
|
||||
/// Constructs a Serial port ready for output.
|
||||
pub fn new_out(
|
||||
id: String,
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
out: Box<dyn io::Write + Send>,
|
||||
) -> Serial {
|
||||
Self::new(interrupt, Some(out))
|
||||
Self::new(id, interrupt, Some(out))
|
||||
}
|
||||
|
||||
/// Constructs a Serial port with no connected output.
|
||||
pub fn new_sink(interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Serial {
|
||||
Self::new(interrupt, None)
|
||||
pub fn new_sink(id: String, interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Serial {
|
||||
Self::new(id, interrupt, None)
|
||||
}
|
||||
|
||||
/// Queues raw bytes for the guest to read and signals the interrupt if the line status would
|
||||
@@ -194,6 +216,32 @@ impl Serial {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn state(&self) -> SerialState {
|
||||
SerialState {
|
||||
interrupt_enable: self.interrupt_enable,
|
||||
interrupt_identification: self.interrupt_identification,
|
||||
line_control: self.line_control,
|
||||
line_status: self.line_status,
|
||||
modem_control: self.modem_control,
|
||||
modem_status: self.modem_status,
|
||||
scratch: self.scratch,
|
||||
baud_divisor: self.baud_divisor,
|
||||
in_buffer: self.in_buffer.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &SerialState) {
|
||||
self.interrupt_enable = state.interrupt_enable;
|
||||
self.interrupt_identification = state.interrupt_identification;
|
||||
self.line_control = state.line_control;
|
||||
self.line_status = state.line_status;
|
||||
self.modem_control = state.modem_control;
|
||||
self.modem_status = state.modem_status;
|
||||
self.scratch = state.scratch;
|
||||
self.baud_divisor = state.baud_divisor;
|
||||
self.in_buffer = state.in_buffer.clone();
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for Serial {
|
||||
@@ -236,6 +284,51 @@ impl BusDevice for Serial {
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for Serial {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut serial_snapshot = Snapshot::new(self.id.as_str());
|
||||
serial_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(serial_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(serial_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||
let serial_state = match serde_json::from_slice(&serial_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize SERIAL {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
self.set_state(&serial_state);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find the serial snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Serial {}
|
||||
impl Transportable for Serial {}
|
||||
impl Migratable for Serial {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -244,6 +337,8 @@ mod tests {
|
||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const SERIAL_NAME: &str = "serial";
|
||||
|
||||
struct TestInterrupt {
|
||||
event_fd: EventFd,
|
||||
}
|
||||
@@ -294,6 +389,7 @@ mod tests {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let serial_out = SharedBuffer::new();
|
||||
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()),
|
||||
);
|
||||
@@ -313,6 +409,7 @@ mod tests {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let serial_out = SharedBuffer::new();
|
||||
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()),
|
||||
);
|
||||
@@ -350,9 +447,10 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_thr() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
let mut serial = Serial::new_sink(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
|
||||
// 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)
|
||||
@@ -371,9 +469,10 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_dlab() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
let mut serial = Serial::new_sink(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
|
||||
serial.write(0, LCR as u64, &[LCR_DLAB_BIT as u8]);
|
||||
serial.write(0, DLAB_LOW as u64, &[0x12 as u8]);
|
||||
@@ -391,9 +490,10 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_modem() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
let mut serial = Serial::new_sink(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
|
||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT as u8]);
|
||||
serial.write(0, DATA as u64, &['a' as u8]);
|
||||
@@ -416,9 +516,10 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_scratch() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
let mut serial = Serial::new_sink(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
|
||||
serial.write(0, SCR as u64, &[0x12 as u8]);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
//! Emulates virtual and hardware devices.
|
||||
extern crate anyhow;
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
extern crate byteorder;
|
||||
@@ -15,9 +16,14 @@ extern crate libc;
|
||||
extern crate log;
|
||||
#[cfg(feature = "acpi")]
|
||||
extern crate acpi_tables;
|
||||
extern crate serde;
|
||||
extern crate vm_device;
|
||||
extern crate vm_memory;
|
||||
extern crate vm_migration;
|
||||
extern crate vmm_sys_util;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
|
||||
@@ -92,6 +92,11 @@ Remove memory from the VM | `/vm.resize` | `/schemas/VmResize`
|
||||
Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created
|
||||
Add VFIO PCI device to the VM | `/vm.add-device` | `/schemas/VmAddDevice` | N/A | The VM is booted
|
||||
Remove VFIO PCI device from the VM | `/vm.remove-device` | `/schemas/VmRemoveDevice` | N/A | The VM is booted
|
||||
Add disk device to the VM | `/vm.add-disk` | `/schemas/DiskConfig` | N/A | The VM is booted
|
||||
Add fs device to the VM | `/vm.add-fs` | `/schemas/FsConfig` | N/A | The VM is booted
|
||||
Add pmem device to the VM | `/vm.add-pmem` | `/schemas/PmemConfig` | N/A | The VM is booted
|
||||
Add network device to the VM | `/vm.add-net` | `/schemas/NetConfig` | N/A | The VM is booted
|
||||
Add vsock device to the VM | `/vm.add-vsock` | `/schemas/VsockConfig` | N/A | The VM is booted
|
||||
|
||||
### REST API Examples
|
||||
|
||||
|
||||
104
docs/vhost-user-blk-testing.md
Normal file
104
docs/vhost-user-blk-testing.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# How to test vhost-user-blk with SPDK
|
||||
|
||||
The purpose of this document is to illustrate how to test vhost-user-blk in Cloud Hypervisor with SPDK as the backend.
|
||||
|
||||
## Framework
|
||||
|
||||
It's a simple test to validate the block read/write between VM and block backend.
|
||||
```
|
||||
+----+----------+ +-------------+-----------+
|
||||
| | | | | |
|
||||
| |vhost-user|----------| vhost-user | dpdk |
|
||||
| |blk device| | port 1 | |
|
||||
| | | | | |
|
||||
| +----------+ +-------------+-----------+
|
||||
| | | |
|
||||
| vm | | spdk |
|
||||
| | | |
|
||||
+--+----------------------------------------------------+--+
|
||||
| | hugepages | |
|
||||
| +----------------------------------------------------+ |
|
||||
| |
|
||||
| host |
|
||||
| |
|
||||
+----------------------------------------------------------+
|
||||
```
|
||||
## Prerequisites
|
||||
|
||||
Prior to running the test, the following steps need to be performed.
|
||||
- Enable hugepages
|
||||
- Install SPDK
|
||||
|
||||
Here are some good references for detailing them.
|
||||
- spdk
|
||||
* https://spdk.io/doc/
|
||||
|
||||
## Test environment
|
||||
|
||||
The below test environment is based on ubuntu release(16.04.1 LTS), as for other system, please check related document.
|
||||
The test runs with multiple queue (MQ) support enabled, using 4 queues defined for both SPDK and the virtual machine.
|
||||
Here are the details on how the test can be run.
|
||||
|
||||
### The hugepages settings in host linux
|
||||
Add "default_hugepagesz=1G hugepagesz=1G hugepages=2" into host linux cmdline.
|
||||
As for how to change Ubuntu linux cmdline in grub file, please ref below link:
|
||||
https://www.ostechnix.com/configure-grub-2-boot-loader-settings-ubuntu-16-04/
|
||||
reboot Ubuntu
|
||||
sudo mount -t hugetlbfs -o pagesize=1G none /dev/hugepages
|
||||
|
||||
### Download the SPDK code
|
||||
git clone https://github.com/spdk/spdk
|
||||
cd spdk
|
||||
git submodule update --init
|
||||
|
||||
### Create the build dep
|
||||
./scripts/pkgdep.sh
|
||||
|
||||
### Build spdk
|
||||
./configure
|
||||
make
|
||||
|
||||
### Set the SPDk environment
|
||||
sudo HUGEMEM=2048 scripts/setup.sh
|
||||
sudo ./app/vhost/vhost -S /var/tmp -s 1024 -m 0x3 &
|
||||
|
||||
### Create 512M block device
|
||||
sudo scripts/rpc.py bdev_malloc_create 512 512 -b Malloc0
|
||||
sudo scripts/rpc.py vhost_create_blk_controller --cpumask 0x1 vhost.1 Malloc0
|
||||
|
||||
_Launch the VM_
|
||||
|
||||
VMs run in client mode. They connect to the socket created by the `dpdkvhostuser` in the SPDK backend.
|
||||
```bash
|
||||
# From the test terminal. We need to create one vhost-user-blk device for the --disk.
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M,file=/dev/hugepages \
|
||||
--kernel linux/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3 iommu=off" \
|
||||
--disk "path=images/clear-kvm.img" "num_queues=4,queue_size=128,vhost_user=true,socket=/var/tmp/vhost.1,wce=true" \
|
||||
--console off \
|
||||
--serial tty \
|
||||
--rng
|
||||
```
|
||||
|
||||
```bash
|
||||
# How to test the vhost-user-blk device with SPDK backend
|
||||
login in guest
|
||||
|
||||
# Use lsblk command to find out vhost-user-blk device
|
||||
lsblk
|
||||
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
|
||||
vda 253:0 0 8.5G 0 disk
|
||||
├─vda1 253:1 0 511M 0 part
|
||||
├─vda2 253:2 0 32M 0 part [SWAP]
|
||||
└─vda3 253:3 0 8G 0 part /
|
||||
vdb 253:16 0 512M 0 disk
|
||||
|
||||
The vhost-user-blk device is /dev/vdb
|
||||
|
||||
# How to do simple read/write test
|
||||
dd if=/dev/vdb of=/dev/null bs=2M iflag=direct
|
||||
dd of=/dev/vdb if=/dev/zero bs=2M oflag=direct count=256
|
||||
|
||||
If you want to do fio test, please install fio binary into guest. The detailed info is not listed here.
|
||||
@@ -4,9 +4,9 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.68"
|
||||
libc = "0.2.69"
|
||||
rand = "0.7.3"
|
||||
serde = "1.0.105"
|
||||
serde = "1.0.106"
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
net_gen = { path = "../net_gen" }
|
||||
@@ -14,4 +14,4 @@ net_gen = { path = "../net_gen" }
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.3.0"
|
||||
pnet = "0.25.0"
|
||||
serde_json = "1.0.48"
|
||||
serde_json = "1.0.52"
|
||||
|
||||
@@ -9,6 +9,7 @@ use rand::Rng;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::result::Result;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::de::{Deserialize, Deserializer, Error};
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
@@ -120,6 +121,18 @@ impl<'de> Deserialize<'de> for MacAddr {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum MacAddrParseError {
|
||||
InvalidValue(String),
|
||||
}
|
||||
|
||||
impl FromStr for MacAddr {
|
||||
type Err = MacAddrParseError;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
MacAddr::parse_str(s).map_err(|_| MacAddrParseError::InvalidValue(s.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate serde_json;
|
||||
|
||||
@@ -8,7 +8,7 @@ edition = "2018"
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
byteorder = "1.3.4"
|
||||
devices = { path = "../devices" }
|
||||
libc = "0.2.68"
|
||||
libc = "0.2.69"
|
||||
log = "0.4.8"
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = "0.1.0"
|
||||
vm-memory = "0.2.0"
|
||||
|
||||
@@ -53,6 +53,7 @@ impl PciRoot {
|
||||
config: PciConfiguration::new(
|
||||
VENDOR_ID_INTEL,
|
||||
DEVICE_ID_INTEL_VIRT_PCIE_HOST,
|
||||
0,
|
||||
PciClassCode::BridgeDevice,
|
||||
&PciBridgeSubclass::HostBridge,
|
||||
None,
|
||||
@@ -239,7 +240,10 @@ impl PciConfigIo {
|
||||
device.deref_mut(),
|
||||
params.region_type,
|
||||
) {
|
||||
error!("Failed moving device BAR: {}", e);
|
||||
error!(
|
||||
"Failed moving device BAR: {}: 0x{:x}->0x{:x}(0x{:x})",
|
||||
e, params.old_base, params.new_base, params.len
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,7 +359,10 @@ impl PciConfigMmio {
|
||||
device.deref_mut(),
|
||||
params.region_type,
|
||||
) {
|
||||
error!("Failed moving device BAR: {}", e);
|
||||
error!(
|
||||
"Failed moving device BAR: {}: 0x{:x}->0x{:x}(0x{:x})",
|
||||
e, params.old_base, params.new_base, params.len
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -244,6 +244,38 @@ pub trait PciCapability {
|
||||
fn id(&self) -> PciCapabilityID;
|
||||
}
|
||||
|
||||
fn encode_32_bits_bar_size(bar_size: u32) -> Option<u32> {
|
||||
if bar_size > 0 {
|
||||
return Some(!(bar_size - 1));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn decode_32_bits_bar_size(bar_size: u32) -> Option<u32> {
|
||||
if bar_size > 0 {
|
||||
return Some(!bar_size + 1);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn encode_64_bits_bar_size(bar_size: u64) -> Option<(u32, u32)> {
|
||||
if bar_size > 0 {
|
||||
let result = !(bar_size - 1);
|
||||
let result_hi = (result >> 32) as u32;
|
||||
let result_lo = (result & 0xffff_ffff) as u32;
|
||||
return Some((result_hi, result_lo));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn decode_64_bits_bar_size(bar_size_hi: u32, bar_size_lo: u32) -> Option<u64> {
|
||||
let bar_size: u64 = ((bar_size_hi as u64) << 32) | (bar_size_lo as u64);
|
||||
if bar_size > 0 {
|
||||
return Some(!bar_size + 1);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Contains the configuration space of a PCI node.
|
||||
/// See the [specification](https://en.wikipedia.org/wiki/PCI_configuration_space).
|
||||
/// The configuration space is accessed with DWORD reads and writes from the guest.
|
||||
@@ -297,6 +329,10 @@ pub enum Error {
|
||||
CapabilityEmpty,
|
||||
CapabilityLengthInvalid(usize),
|
||||
CapabilitySpaceFull(usize),
|
||||
Decode32BarSize,
|
||||
Decode64BarSize,
|
||||
Encode32BarSize,
|
||||
Encode64BarSize,
|
||||
RomBarAddressInvalid(u64, u64),
|
||||
RomBarInUse(usize),
|
||||
RomBarInvalid(usize),
|
||||
@@ -324,6 +360,10 @@ impl Display for Error {
|
||||
CapabilityEmpty => write!(f, "empty capabilities are invalid"),
|
||||
CapabilityLengthInvalid(l) => write!(f, "Invalid capability length {}", l),
|
||||
CapabilitySpaceFull(s) => write!(f, "capability of size {} doesn't fit", s),
|
||||
Decode32BarSize => write!(f, "failed to decode 32 bits BAR size"),
|
||||
Decode64BarSize => write!(f, "failed to decode 64 bits BAR size"),
|
||||
Encode32BarSize => write!(f, "failed to encode 32 bits BAR size"),
|
||||
Encode64BarSize => write!(f, "failed to encode 64 bits BAR size"),
|
||||
RomBarAddressInvalid(a, s) => write!(f, "address {} size {} too big", a, s),
|
||||
RomBarInUse(b) => write!(f, "rom bar {} already used", b),
|
||||
RomBarInvalid(b) => write!(f, "rom bar {} invalid, max {}", b, NUM_BAR_REGS - 1),
|
||||
@@ -337,6 +377,7 @@ impl PciConfiguration {
|
||||
pub fn new(
|
||||
vendor_id: u16,
|
||||
device_id: u16,
|
||||
revision_id: u8,
|
||||
class_code: PciClassCode,
|
||||
subclass: &dyn PciSubclass,
|
||||
programming_interface: Option<&dyn PciProgrammingInterface>,
|
||||
@@ -359,7 +400,8 @@ impl PciConfiguration {
|
||||
};
|
||||
registers[2] = u32::from(class_code.get_register_value()) << 24
|
||||
| u32::from(subclass.get_register_value()) << 16
|
||||
| u32::from(pi) << 8;
|
||||
| u32::from(pi) << 8
|
||||
| u32::from(revision_id);
|
||||
writable_bits[3] = 0x0000_00ff; // Cacheline size (r/w)
|
||||
match header_type {
|
||||
PciHeaderType::Device => {
|
||||
@@ -403,14 +445,14 @@ impl PciConfiguration {
|
||||
// Handle very specific case where the BAR is being written with
|
||||
// all 1's to retrieve the BAR size during next BAR reading.
|
||||
if value == 0xffff_ffff {
|
||||
mask = self.bar_size[reg_idx - 4];
|
||||
mask &= self.bar_size[reg_idx - 4];
|
||||
}
|
||||
} else if reg_idx == ROM_BAR_REG {
|
||||
// Handle very specific case where the BAR is being written with
|
||||
// all 1's on bits 31-11 to retrieve the BAR size during next BAR
|
||||
// reading.
|
||||
if value & ROM_BAR_ADDR_MASK == ROM_BAR_ADDR_MASK {
|
||||
mask = self.rom_bar_size;
|
||||
mask &= self.rom_bar_size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,6 +536,11 @@ impl PciConfiguration {
|
||||
if end_addr > u64::from(u32::max_value()) {
|
||||
return Err(Error::BarAddressInvalid(config.addr, config.size));
|
||||
}
|
||||
|
||||
// Encode the BAR size as expected by the software running in
|
||||
// the guest.
|
||||
self.bar_size[config.reg_idx] =
|
||||
encode_32_bits_bar_size(config.size as u32).ok_or(Error::Encode32BarSize)?;
|
||||
}
|
||||
PciBarRegionType::Memory64BitRegion => {
|
||||
if config.reg_idx + 1 >= NUM_BAR_REGS {
|
||||
@@ -508,10 +555,16 @@ impl PciConfiguration {
|
||||
return Err(Error::BarInUse64(config.reg_idx));
|
||||
}
|
||||
|
||||
// Encode the BAR size as expected by the software running in
|
||||
// the guest.
|
||||
let (bar_size_hi, bar_size_lo) =
|
||||
encode_64_bits_bar_size(config.size).ok_or(Error::Encode64BarSize)?;
|
||||
|
||||
self.registers[bar_idx + 1] = (config.addr >> 32) as u32;
|
||||
self.writable_bits[bar_idx + 1] = 0xffff_ffff;
|
||||
self.bar_addr[config.reg_idx + 1] = self.registers[bar_idx + 1];
|
||||
self.bar_size[config.reg_idx + 1] = (config.size >> 32) as u32;
|
||||
self.bar_size[config.reg_idx] = bar_size_lo;
|
||||
self.bar_size[config.reg_idx + 1] = bar_size_hi;
|
||||
self.bar_used[config.reg_idx + 1] = true;
|
||||
}
|
||||
}
|
||||
@@ -527,7 +580,6 @@ impl PciConfiguration {
|
||||
self.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits;
|
||||
self.writable_bits[bar_idx] = mask;
|
||||
self.bar_addr[config.reg_idx] = self.registers[bar_idx];
|
||||
self.bar_size[config.reg_idx] = config.size as u32;
|
||||
self.bar_used[config.reg_idx] = true;
|
||||
self.bar_type[config.reg_idx] = Some(config.region_type);
|
||||
Ok(config.reg_idx)
|
||||
@@ -559,7 +611,8 @@ impl PciConfiguration {
|
||||
self.registers[config.reg_idx] = (config.addr as u32) | active;
|
||||
self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK;
|
||||
self.rom_bar_addr = self.registers[config.reg_idx];
|
||||
self.rom_bar_size = config.size as u32;
|
||||
self.rom_bar_size =
|
||||
encode_32_bits_bar_size(config.size as u32).ok_or(Error::Encode32BarSize)?;
|
||||
self.rom_bar_used = true;
|
||||
Ok(config.reg_idx)
|
||||
}
|
||||
@@ -697,7 +750,11 @@ impl PciConfiguration {
|
||||
);
|
||||
let old_base = u64::from(self.bar_addr[bar_idx] & mask);
|
||||
let new_base = u64::from(value & mask);
|
||||
let len = u64::from(self.bar_size[bar_idx]);
|
||||
let len = u64::from(
|
||||
decode_32_bits_bar_size(self.bar_size[bar_idx])
|
||||
.ok_or(Error::Decode32BarSize)
|
||||
.unwrap(),
|
||||
);
|
||||
let region_type = bar_type;
|
||||
|
||||
self.bar_addr[bar_idx] = value;
|
||||
@@ -733,8 +790,10 @@ impl PciConfiguration {
|
||||
| u64::from(self.bar_addr[bar_idx - 1] & self.writable_bits[reg_idx - 1]);
|
||||
let new_base = u64::from(value & mask) << 32
|
||||
| u64::from(self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1]);
|
||||
let len = u64::from(self.bar_size[bar_idx]) << 32
|
||||
| u64::from(self.bar_size[bar_idx - 1]);
|
||||
let len =
|
||||
decode_64_bits_bar_size(self.bar_size[bar_idx], self.bar_size[bar_idx - 1])
|
||||
.ok_or(Error::Decode64BarSize)
|
||||
.unwrap();
|
||||
let region_type = PciBarRegionType::Memory64BitRegion;
|
||||
|
||||
self.bar_addr[bar_idx] = value;
|
||||
@@ -760,7 +819,11 @@ impl PciConfiguration {
|
||||
);
|
||||
let old_base = u64::from(self.rom_bar_addr & mask);
|
||||
let new_base = u64::from(value & mask);
|
||||
let len = u64::from(self.rom_bar_size);
|
||||
let len = u64::from(
|
||||
decode_32_bits_bar_size(self.rom_bar_size)
|
||||
.ok_or(Error::Decode32BarSize)
|
||||
.unwrap(),
|
||||
);
|
||||
let region_type = PciBarRegionType::Memory32BitRegion;
|
||||
|
||||
self.rom_bar_addr = value;
|
||||
@@ -862,6 +925,7 @@ mod tests {
|
||||
let mut cfg = PciConfiguration::new(
|
||||
0x1234,
|
||||
0x5678,
|
||||
0x1,
|
||||
PciClassCode::MultimediaController,
|
||||
&PciMultimediaSubclass::AudioController,
|
||||
None,
|
||||
@@ -917,6 +981,7 @@ mod tests {
|
||||
let cfg = PciConfiguration::new(
|
||||
0x1234,
|
||||
0x5678,
|
||||
0x1,
|
||||
PciClassCode::MultimediaController,
|
||||
&PciMultimediaSubclass::AudioController,
|
||||
Some(&TestPI::Test),
|
||||
|
||||
@@ -10,7 +10,7 @@ path = "src/qcow.rs"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3.4"
|
||||
libc = "0.2.68"
|
||||
libc = "0.2.69"
|
||||
log = "0.4.8"
|
||||
remain = "0.2.1"
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
115
release-notes.md
115
release-notes.md
@@ -1,10 +1,20 @@
|
||||
- [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)
|
||||
- [Multithreaded Multi Queued `vhost-user` Backends](#multithreaded-multi-queued-vhost-user-backends)
|
||||
- [Initial RamFS Support](#initial-ramfs-support)
|
||||
- [Alternative Memory Hotplug: `virtio-mem`](#alternative-memory-hotplug-virtio-mem)
|
||||
- [`Seccomp` Sandboxing](#seccomp-sandboxing)
|
||||
- [Updated Distribution Support](#updated-distribution-support)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes)
|
||||
- [Contributors](#contributors)
|
||||
- [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)
|
||||
- [Contributors](#contributors-1)
|
||||
- [v0.5.1](#v051)
|
||||
- [v0.5.0](#v050)
|
||||
- [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing)
|
||||
@@ -12,7 +22,7 @@
|
||||
- [New Interrupt Management Framework](#new-interrupt-management-framework)
|
||||
- [Development Tools](#development-tools)
|
||||
- [Kata Containers Integration](#kata-containers-integration)
|
||||
- [Contributors](#contributors)
|
||||
- [Contributors](#contributors-2)
|
||||
- [v0.4.0](#v040)
|
||||
- [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition)
|
||||
- [Programmatic firmware tables generation](#programmatic-firmware-tables-generation)
|
||||
@@ -21,7 +31,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-1)
|
||||
- [Contributors](#contributors-3)
|
||||
- [v0.3.0](#v030)
|
||||
- [Block device offloading](#block-device-offloading)
|
||||
- [Network device backend](#network-device-backend)
|
||||
@@ -48,6 +58,105 @@
|
||||
- [Unit testing](#unit-testing)
|
||||
- [Integration tests parallelization](#integration-tests-parallelization)
|
||||
|
||||
# v0.7.0
|
||||
|
||||
This release has been tracked through the [0.7.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/7).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.7.0 include:
|
||||
|
||||
### Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug
|
||||
|
||||
Further to our effort to support modifying a running guest we now support
|
||||
hotplug and unplug of the following virtio backed devices: block, network,
|
||||
pmem, virtio-fs and vsock. This functionality is available on the (default) PCI
|
||||
based tranport and is exposed through the HTTP API. The `ch-remote` utility
|
||||
provides a CLI for adding or removing these device types after the VM has
|
||||
booted. User can use the `id` parameter on the devices to choose names for
|
||||
devices to ease their removal.
|
||||
|
||||
### Alternative `libc` Support
|
||||
|
||||
Cloud Hypervisor can now be compiled with the `musl` C library and this release
|
||||
contains a static binary compiled using that toolchain.
|
||||
|
||||
### Multithreaded Multi Queued `vhost-user` Backends
|
||||
|
||||
The `vhost-user` backends for network and block support that are shipped by
|
||||
Cloud Hypervisor have been enhanced to support multiple threads and queues to
|
||||
improve throughput. These backends are used automatically if `vhost_user=true`
|
||||
is passed when the devices are created.
|
||||
|
||||
### Initial RamFS Support
|
||||
|
||||
By passing the `--initramfs` command line option the user can specify a file to
|
||||
be loaded into the guest memory to be used as the kernel initial filesystem.
|
||||
This is usually used to allow the loading of drivers needed to be able to
|
||||
access the real root filesystem but it can also be used standalone for a very
|
||||
minimal image.
|
||||
|
||||
### Alternative Memory Hotplug: `virtio-mem`
|
||||
|
||||
As well as supporting ACPI based hotplug Cloud Hypervisor now supports using
|
||||
the `virtio-mem` hotplug alternative. This can be controlled by the
|
||||
`hotplug_method` parameter on the `--memory` command line option. It currently
|
||||
requires kernel patches to be able to support it.
|
||||
|
||||
### `Seccomp` Sandboxing
|
||||
|
||||
Cloud Hypervisor now has support for restricting the system calls that the
|
||||
process can use via the `seccomp` security API. This on by default and is
|
||||
controlled by the `--seccomp` command line option.
|
||||
|
||||
### Updated Distribution Support
|
||||
|
||||
With the release of Ubuntu 20.04 we have added that to the list of supported
|
||||
distributions and is part of our regular testing programme.
|
||||
|
||||
### Command Line and API Changes
|
||||
|
||||
This is non exhaustive list of HTTP API and command line changes
|
||||
|
||||
* New `id` fields added for devices to allow them to be named to ease removal.
|
||||
If no name is specified the VMM chooses one.
|
||||
* Use `--memory`'s `shared` and `hugepages` controls for determining backing
|
||||
memory instead of providing a path.
|
||||
* The `--vsock` parameter only takes one device as the Linux kernel only
|
||||
supports a single Vsock device. The REST API has removed the vector for this
|
||||
option and replaced it with a single optional field.
|
||||
* There is enhanced validation of the command line and API provided
|
||||
configurations to ensure that the provided options are compatible e.g. that
|
||||
shared memory is in use if any attempt is made to used a `vhost-user` backed
|
||||
device.
|
||||
* `ch-remote` has added `add-disk`, `add-fs`, `add-net`, `add-pmem` and
|
||||
`add-vsock` subcommands. For removal `remove-device` is used. The REST API
|
||||
has appropriate new HTTP endpoints too.
|
||||
* Specifying a `size` with `--pmem` is no longer required and instead the size
|
||||
will be obtained from the file. A `discard_writes` option has also been added
|
||||
to provide the equivalent of a read-only file.
|
||||
* The parameters to `--block-backend` have been changed to more closely align
|
||||
with those used by `--disk`.
|
||||
|
||||
### Contributors
|
||||
|
||||
Many thanks to everyone who has contributed to our 0.7.0 release including some new faces.
|
||||
|
||||
* Alejandro Jimenez <alejandro.j.jimenez@oracle.com>
|
||||
* Bo Chen <chen.bo@intel.com>
|
||||
* Cathy Zhang <cathy.zhang@intel.com>
|
||||
* Damjan Georgievski <gdamjan@gmail.com>
|
||||
* Dean Sheather <dean@coder.com>
|
||||
* Eryu Guan <eguan@linux.alibaba.com>
|
||||
* Hui Zhu <teawater@antfin.com>
|
||||
* Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
|
||||
* Martin Xu <martin.xu@intel.com>
|
||||
* Muminul Islam <muislam@microsoft.com>
|
||||
* Rob Bradford <robert.bradford@intel.com>
|
||||
* Samuel Ortiz <sameo@linux.intel.com>
|
||||
* Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
* Sergio Lopez <slp@redhat.com>
|
||||
* Yang Zhong <yang.zhong@intel.com>
|
||||
* Yi Sun <yi.y.sun@linux.intel.com>
|
||||
|
||||
# v0.6.0
|
||||
|
||||
This release has been tracked through the [0.6.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/7).
|
||||
|
||||
@@ -21,6 +21,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
||||
wget \
|
||||
sudo \
|
||||
mtools \
|
||||
musl-tools \
|
||||
libssl-dev \
|
||||
pkg-config \
|
||||
flex \
|
||||
@@ -34,11 +35,22 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
||||
libcap-ng-dev \
|
||||
socat \
|
||||
dosfstools \
|
||||
cpio \
|
||||
bsdtar \
|
||||
gcc-multilib \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Fix the libssl-dev install
|
||||
RUN cp /usr/include/x86_64-linux-gnu/openssl/opensslconf.h /usr/include/openssl/
|
||||
ENV OPENSSL_DIR=/usr/lib/x86_64-linux-gnu/
|
||||
ENV OPENSSL_LIB_DIR=/usr/lib/x86_64-linux-gnu/
|
||||
ENV OPENSSL_INCLUDE_DIR=/usr/include/
|
||||
|
||||
# Install the rust toolchain
|
||||
RUN nohup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN" \
|
||||
&& rustup target add x86_64-unknown-linux-musl --toolchain "$RUST_TOOLCHAIN" \
|
||||
&& rustup toolchain add $RUST_TOOLCHAIN-x86_64-unknown-linux-musl \
|
||||
&& rustup component add rustfmt \
|
||||
&& rustup component add clippy \
|
||||
&& cargo install cargo-audit \
|
||||
|
||||
@@ -178,7 +178,14 @@ CONFIG_NET_NS=y
|
||||
CONFIG_SCHED_AUTOGROUP=y
|
||||
# CONFIG_SYSFS_DEPRECATED is not set
|
||||
CONFIG_RELAY=y
|
||||
# CONFIG_BLK_DEV_INITRD is not set
|
||||
CONFIG_BLK_DEV_INITRD=y
|
||||
CONFIG_INITRAMFS_SOURCE=""
|
||||
CONFIG_RD_GZIP=y
|
||||
CONFIG_RD_BZIP2=y
|
||||
CONFIG_RD_LZMA=y
|
||||
CONFIG_RD_XZ=y
|
||||
CONFIG_RD_LZO=y
|
||||
CONFIG_RD_LZ4=y
|
||||
# CONFIG_BOOT_CONFIG is not set
|
||||
CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y
|
||||
# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set
|
||||
@@ -485,6 +492,7 @@ CONFIG_ACPI_HOTPLUG_CPU=y
|
||||
# CONFIG_ACPI_PROCESSOR_AGGREGATOR is not set
|
||||
CONFIG_ACPI_THERMAL=y
|
||||
CONFIG_ARCH_HAS_ACPI_TABLE_UPGRADE=y
|
||||
CONFIG_ACPI_TABLE_UPGRADE=y
|
||||
# CONFIG_ACPI_DEBUG is not set
|
||||
# CONFIG_ACPI_PCI_SLOT is not set
|
||||
CONFIG_ACPI_CONTAINER=y
|
||||
@@ -2418,7 +2426,22 @@ CONFIG_ZLIB_INFLATE=y
|
||||
CONFIG_ZLIB_DEFLATE=y
|
||||
CONFIG_LZO_COMPRESS=y
|
||||
CONFIG_LZO_DECOMPRESS=y
|
||||
# CONFIG_XZ_DEC is not set
|
||||
CONFIG_LZ4_DECOMPRESS=y
|
||||
CONFIG_XZ_DEC=y
|
||||
CONFIG_XZ_DEC_X86=y
|
||||
CONFIG_XZ_DEC_POWERPC=y
|
||||
CONFIG_XZ_DEC_IA64=y
|
||||
CONFIG_XZ_DEC_ARM=y
|
||||
CONFIG_XZ_DEC_ARMTHUMB=y
|
||||
CONFIG_XZ_DEC_SPARC=y
|
||||
CONFIG_XZ_DEC_BCJ=y
|
||||
# CONFIG_XZ_DEC_TEST is not set
|
||||
CONFIG_DECOMPRESS_GZIP=y
|
||||
CONFIG_DECOMPRESS_BZIP2=y
|
||||
CONFIG_DECOMPRESS_LZMA=y
|
||||
CONFIG_DECOMPRESS_XZ=y
|
||||
CONFIG_DECOMPRESS_LZO=y
|
||||
CONFIG_DECOMPRESS_LZ4=y
|
||||
CONFIG_INTERVAL_TREE=y
|
||||
CONFIG_XARRAY_MULTI=y
|
||||
CONFIG_ASSOCIATIVE_ARRAY=y
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
CLI_NAME="Cloud Hypervisor"
|
||||
|
||||
CTR_IMAGE_TAG="cloudhypervisor/dev"
|
||||
CTR_IMAGE_VERSION="v1"
|
||||
CTR_IMAGE_VERSION="v2"
|
||||
CTR_IMAGE="${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}"
|
||||
|
||||
DOCKER_RUNTIME="docker"
|
||||
@@ -113,6 +113,13 @@ ensure_build_dir() {
|
||||
done
|
||||
}
|
||||
|
||||
# Make sure we're using the latest dev container, by just pulling it.
|
||||
ensure_latest_ctr() {
|
||||
$DOCKER_RUNTIME pull "$CTR_IMAGE"
|
||||
|
||||
ok_or_die "Error pulling container image. Aborting."
|
||||
}
|
||||
|
||||
# Fix main directory permissions after a container ran as root.
|
||||
# Since the container ran as root, any files it creates will be owned by root.
|
||||
# This fixes that by recursively changing the ownership of /cloud-hypervisor to the
|
||||
@@ -139,16 +146,18 @@ cmd_help() {
|
||||
echo ""
|
||||
echo "Available commands:"
|
||||
echo ""
|
||||
echo " build [--debug|--release] [-- [<cargo args>]]"
|
||||
echo " build [--debug|--release] [--libc musl|gnu] [-- [<cargo args>]]"
|
||||
echo " Build the Cloud Hypervisor binaries."
|
||||
echo " --debug Build the debug binaries. This is the default."
|
||||
echo " --release Build the release binaries."
|
||||
echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu"
|
||||
echo ""
|
||||
echo " tests [--unit|--cargo|--all] [-- [<cargo test args>]]"
|
||||
echo " tests [--unit|--cargo|--all] [--libc musl|gnu] [-- [<cargo test args>]]"
|
||||
echo " Run the Cloud Hypervisor tests."
|
||||
echo " --unit Run the unit tests."
|
||||
echo " --cargo Run the cargo tests."
|
||||
echo " --integration Run the integration tests."
|
||||
echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu"
|
||||
echo " --all Run all tests."
|
||||
echo ""
|
||||
echo " build-container [--type]"
|
||||
@@ -165,12 +174,19 @@ cmd_help() {
|
||||
|
||||
cmd_build() {
|
||||
build="debug"
|
||||
libc="gnu"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--debug") { build="debug"; } ;;
|
||||
"--release") { build="release"; } ;;
|
||||
"--libc")
|
||||
shift
|
||||
[[ "$1" =~ ^(musl|gnu)$ ]] || \
|
||||
die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"."
|
||||
libc="$1"
|
||||
;;
|
||||
"--") { shift; break; } ;;
|
||||
*)
|
||||
die "Unknown build argument: $1. Please use --help for help."
|
||||
@@ -179,8 +195,11 @@ cmd_build() {
|
||||
shift
|
||||
done
|
||||
|
||||
target="$(uname -m)-unknown-linux-${libc}"
|
||||
|
||||
cargo_args=("$@")
|
||||
[ $build = "release" ] && cargo_args+=("--release")
|
||||
cargo_args+=(--target "$target")
|
||||
|
||||
$DOCKER_RUNTIME run \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
@@ -191,7 +210,7 @@ cmd_build() {
|
||||
"$CTR_IMAGE" \
|
||||
cargo build \
|
||||
--target-dir "$CTR_CLH_CARGO_TARGET" \
|
||||
"${cargo_args[@]}" && say "Binaries placed under $CLH_CARGO_TARGET/$build"
|
||||
"${cargo_args[@]}" && say "Binaries placed under $CLH_CARGO_TARGET/$target/$build"
|
||||
}
|
||||
|
||||
cmd_clean() {
|
||||
@@ -212,15 +231,22 @@ cmd_tests() {
|
||||
unit=false
|
||||
cargo=false
|
||||
integration=false
|
||||
libc="gnu"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--unit") { unit=true; } ;;
|
||||
"--cargo") { cargo=true; } ;;
|
||||
"--integration") { integration=true; } ;;
|
||||
"--all") { cargo=true; unit=true; integration=true; } ;;
|
||||
"--") { shift; break; } ;;
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--unit") { unit=true; } ;;
|
||||
"--cargo") { cargo=true; } ;;
|
||||
"--integration") { integration=true; } ;;
|
||||
"--libc")
|
||||
shift
|
||||
[[ "$1" =~ ^(musl|gnu)$ ]] || \
|
||||
die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"."
|
||||
libc="$1"
|
||||
;;
|
||||
"--all") { cargo=true; unit=true; integration=true; } ;;
|
||||
"--") { shift; break; } ;;
|
||||
*)
|
||||
die "Unknown tests argument: $1. Please use --help for help."
|
||||
;;
|
||||
@@ -228,8 +254,16 @@ cmd_tests() {
|
||||
shift
|
||||
done
|
||||
|
||||
target="$(uname -m)-unknown-linux-${libc}"
|
||||
cflags=""
|
||||
target_cc=""
|
||||
if [[ "$target" == "x86_64-unknown-linux-musl" ]]; then
|
||||
target_cc="musl-gcc"
|
||||
cflags="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
|
||||
fi
|
||||
|
||||
if [ "$unit" = true ] ; then
|
||||
say "Running unit tests..."
|
||||
say "Running unit tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
@@ -237,6 +271,9 @@ cmd_tests() {
|
||||
--device /dev/net/tun \
|
||||
--cap-add net_admin \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||
--env BUILD_TARGET="$target" \
|
||||
--env CFLAGS="$cflags" \
|
||||
--env TARGET_CC="$target_cc" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_unit_tests.sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
@@ -252,7 +289,7 @@ cmd_tests() {
|
||||
fi
|
||||
|
||||
if [ "$integration" = true ] ; then
|
||||
say "Running integration tests..."
|
||||
say "Running integration tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
@@ -265,6 +302,9 @@ cmd_tests() {
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--env BUILD_TARGET="$target" \
|
||||
--env CFLAGS="$cflags" \
|
||||
--env TARGET_CC="$target_cc" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_integration_tests.sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
@@ -325,5 +365,6 @@ cmd=cmd_$1
|
||||
shift
|
||||
|
||||
ensure_build_dir
|
||||
ensure_latest_ctr
|
||||
|
||||
$cmd "$@"
|
||||
|
||||
@@ -3,6 +3,8 @@ set -x
|
||||
|
||||
source $HOME/.cargo/env
|
||||
|
||||
export BUILD_TARGET=${BUILD_TARGET-x86_64-unknown-linux-gnu}
|
||||
|
||||
WORKLOADS_DIR="$HOME/workloads"
|
||||
mkdir -p "$WORKLOADS_DIR"
|
||||
|
||||
@@ -51,20 +53,46 @@ if [ ! -f "$BIONIC_OS_RAW_IMAGE" ]; then
|
||||
fi
|
||||
|
||||
|
||||
EOAN_OS_IMAGE_NAME="eoan-server-cloudimg-amd64.img"
|
||||
EOAN_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$EOAN_OS_IMAGE_NAME"
|
||||
EOAN_OS_IMAGE="$WORKLOADS_DIR/$EOAN_OS_IMAGE_NAME"
|
||||
if [ ! -f "$EOAN_OS_IMAGE" ]; then
|
||||
FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64.img"
|
||||
FOCAL_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$FOCAL_OS_IMAGE_NAME"
|
||||
FOCAL_OS_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_IMAGE_NAME"
|
||||
if [ ! -f "$FOCAL_OS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
time wget --quiet $EOAN_OS_IMAGE_URL || exit 1
|
||||
time wget --quiet $FOCAL_OS_IMAGE_URL || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
EOAN_OS_RAW_IMAGE_NAME="eoan-server-cloudimg-amd64-raw.img"
|
||||
EOAN_OS_RAW_IMAGE="$WORKLOADS_DIR/$EOAN_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$EOAN_OS_RAW_IMAGE" ]; then
|
||||
FOCAL_OS_RAW_IMAGE_NAME="focal-server-cloudimg-amd64-raw.img"
|
||||
FOCAL_OS_RAW_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
time qemu-img convert -p -f qcow2 -O raw $EOAN_OS_IMAGE_NAME $EOAN_OS_RAW_IMAGE_NAME || exit 1
|
||||
time qemu-img convert -p -f qcow2 -O raw $FOCAL_OS_IMAGE_NAME $FOCAL_OS_RAW_IMAGE_NAME || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
ALPINE_MINIROOTFS_URL="http://dl-cdn.alpinelinux.org/alpine/v3.11/releases/x86_64/alpine-minirootfs-3.11.3-x86_64.tar.gz"
|
||||
ALPINE_MINIROOTFS_TARBALL="$WORKLOADS_DIR/alpine-minirootfs-x86_64.tar.gz"
|
||||
if [ ! -f "$ALPINE_MINIROOTFS_TARBALL" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
time wget --quiet $ALPINE_MINIROOTFS_URL -O $ALPINE_MINIROOTFS_TARBALL || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
ALPINE_INITRAMFS_IMAGE="$WORKLOADS_DIR/alpine_initramfs.img"
|
||||
if [ ! -f "$ALPINE_INITRAMFS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
mkdir alpine-minirootfs
|
||||
tar xf "$ALPINE_MINIROOTFS_TARBALL" -C alpine-minirootfs
|
||||
cat > alpine-minirootfs/init <<-EOF
|
||||
#! /bin/sh
|
||||
mount -t devtmpfs dev /dev
|
||||
echo \$TEST_STRING > /dev/console
|
||||
poweroff -f
|
||||
EOF
|
||||
chmod +x alpine-minirootfs/init
|
||||
cd alpine-minirootfs
|
||||
find . -print0 |
|
||||
cpio --null --create --verbose --owner root:root --format=newc > "$ALPINE_INITRAMFS_IMAGE"
|
||||
popd
|
||||
fi
|
||||
|
||||
@@ -182,13 +210,13 @@ sudo ip tuntap add name vunet-tap0 mode tap
|
||||
# Create tap interface with multipe queues support for vhost_user_net test.
|
||||
sudo ip tuntap add name vunet-tap1 mode tap multi_queue
|
||||
|
||||
cargo build --release
|
||||
sudo setcap cap_net_admin+ep target/release/cloud-hypervisor
|
||||
sudo setcap cap_net_admin+ep target/release/vhost_user_net
|
||||
cargo build --release --target $BUILD_TARGET
|
||||
sudo setcap cap_net_admin+ep target/$BUILD_TARGET/release/cloud-hypervisor
|
||||
sudo setcap cap_net_admin+ep target/$BUILD_TARGET/release/vhost_user_net
|
||||
|
||||
# We always copy a fresh version of our binary for our L2 guest.
|
||||
cp target/release/cloud-hypervisor $VFIO_DIR
|
||||
cp target/release/ch-remote $VFIO_DIR
|
||||
cp target/$BUILD_TARGET/release/cloud-hypervisor $VFIO_DIR
|
||||
cp target/$BUILD_TARGET/release/ch-remote $VFIO_DIR
|
||||
|
||||
# Enable KSM with some reasonable parameters so that it won't take too long
|
||||
# for the memory to be merged between two processes.
|
||||
@@ -213,8 +241,12 @@ RES=$?
|
||||
|
||||
if [ $RES -eq 0 ]; then
|
||||
# virtio-mmio based testing
|
||||
cargo build --release --no-default-features --features "mmio"
|
||||
sudo setcap cap_net_admin+ep target/release/cloud-hypervisor
|
||||
cargo build --release --target $BUILD_TARGET --no-default-features --features "mmio"
|
||||
sudo setcap cap_net_admin+ep target/$BUILD_TARGET/release/cloud-hypervisor
|
||||
|
||||
# Ensure test binary has the same caps as the cloud-hypervisor one
|
||||
time cargo test --no-run --features "integration_tests,mmio" -- --nocapture || exit 1
|
||||
ls target/debug/deps/cloud_hypervisor-* | xargs -n 1 sudo setcap cap_net_admin+ep
|
||||
|
||||
newgrp kvm << EOF
|
||||
export RUST_BACKTRACE=1
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
|
||||
source $HOME/.cargo/env
|
||||
|
||||
cargo test --workspace --no-run
|
||||
pushd target/debug
|
||||
BUILD_TARGET=${BUILD_TARGET-x86_64-unknown-linux-gnu}
|
||||
|
||||
cargo test --target $BUILD_TARGET --workspace --no-run
|
||||
pushd target/$BUILD_TARGET/debug
|
||||
ls | grep net_util | grep -v "\.d" | xargs -n 1 sudo setcap cap_net_admin,cap_net_raw+ep
|
||||
popd
|
||||
|
||||
sudo adduser $USER kvm
|
||||
newgrp kvm << EOF || exit 1
|
||||
export RUST_BACKTRACE=1
|
||||
cargo test --workspace "$@" || exit 1;
|
||||
cargo test --target $BUILD_TARGET --workspace "$@" || exit 1;
|
||||
EOF
|
||||
|
||||
@@ -2,5 +2,6 @@ cf7cfa783082fc4d6b4d1c0a53e4402648c14b82 clear-31311-cloudguest.img
|
||||
142a410546b592ff9536b46bb410faf8ac11edee clear-31311-cloudguest-raw.img
|
||||
27f3b17962ace69b51f0ddc2012095e3109e6ed8 bionic-server-cloudimg-amd64.img
|
||||
8db9cc58b01452ce2d06c313177e6e74d8582d93 bionic-server-cloudimg-amd64-raw.img
|
||||
4a452cdcf781f95d31a1668ecb92a937c176709a eoan-server-cloudimg-amd64.img
|
||||
91fdfb21df8920fd55915edf7669282cda2505f6 eoan-server-cloudimg-amd64-raw.img
|
||||
d4a44acc6014d5f83dea1c625c43d677a95fa75f alpine-minirootfs-x86_64.tar.gz
|
||||
3cab64475048383e70a38550734e98961cfe0fd6 focal-server-cloudimg-amd64-raw.img
|
||||
c74a95665b189f93cb16a2c1770820c6a6a43fb0 focal-server-cloudimg-amd64.img
|
||||
|
||||
@@ -23,6 +23,12 @@ enum Error {
|
||||
InvalidCPUCount(std::num::ParseIntError),
|
||||
InvalidMemorySize(std::num::ParseIntError),
|
||||
AddDeviceConfig(vmm::config::Error),
|
||||
AddDiskConfig(vmm::config::Error),
|
||||
AddFsConfig(vmm::config::Error),
|
||||
AddPmemConfig(vmm::config::Error),
|
||||
AddNetConfig(vmm::config::Error),
|
||||
AddVsockConfig(vmm::config::Error),
|
||||
Restore(vmm::config::Error),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -213,6 +219,85 @@ fn remove_device_api_command(socket: &mut UnixStream, id: &str) -> Result<(), Er
|
||||
)
|
||||
}
|
||||
|
||||
fn add_disk_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
|
||||
let disk_config = vmm::config::DiskConfig::parse(config).map_err(Error::AddDiskConfig)?;
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"add-disk",
|
||||
Some(&serde_json::to_string(&disk_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn add_fs_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
|
||||
let fs_config = vmm::config::FsConfig::parse(config).map_err(Error::AddFsConfig)?;
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"add-fs",
|
||||
Some(&serde_json::to_string(&fs_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn add_pmem_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
|
||||
let pmem_config = vmm::config::PmemConfig::parse(config).map_err(Error::AddPmemConfig)?;
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"add-pmem",
|
||||
Some(&serde_json::to_string(&pmem_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn add_net_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
|
||||
let net_config = vmm::config::NetConfig::parse(config).map_err(Error::AddNetConfig)?;
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"add-net",
|
||||
Some(&serde_json::to_string(&net_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn add_vsock_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
|
||||
let vsock_config = vmm::config::VsockConfig::parse(config).map_err(Error::AddVsockConfig)?;
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"add-vsock",
|
||||
Some(&serde_json::to_string(&vsock_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn snapshot_api_command(socket: &mut UnixStream, url: &str) -> Result<(), Error> {
|
||||
let snapshot_config = vmm::api::VmSnapshotConfig {
|
||||
destination_url: String::from(url),
|
||||
};
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"snapshot",
|
||||
Some(&serde_json::to_string(&snapshot_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn restore_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
|
||||
let restore_config = vmm::config::RestoreConfig::parse(config).map_err(Error::Restore)?;
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"restore",
|
||||
Some(&serde_json::to_string(&restore_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn do_command(matches: &ArgMatches) -> Result<(), Error> {
|
||||
let mut socket =
|
||||
UnixStream::connect(matches.value_of("api-socket").unwrap()).map_err(Error::Socket)?;
|
||||
@@ -246,6 +331,62 @@ fn do_command(matches: &ArgMatches) -> Result<(), Error> {
|
||||
.value_of("id")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("add-disk") => add_disk_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("add-disk")
|
||||
.unwrap()
|
||||
.value_of("disk_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("add-fs") => add_fs_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("add-fs")
|
||||
.unwrap()
|
||||
.value_of("fs_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("add-pmem") => add_pmem_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("add-pmem")
|
||||
.unwrap()
|
||||
.value_of("pmem_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("add-net") => add_net_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("add-net")
|
||||
.unwrap()
|
||||
.value_of("net_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("add-vsock") => add_vsock_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("add-vsock")
|
||||
.unwrap()
|
||||
.value_of("vsock_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("snapshot") => snapshot_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("snapshot")
|
||||
.unwrap()
|
||||
.value_of("snapshot_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("restore") => restore_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("restore")
|
||||
.unwrap()
|
||||
.value_of("restore_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some(c) => simple_api_command(&mut socket, "PUT", c, None),
|
||||
None => unreachable!(),
|
||||
}
|
||||
@@ -273,6 +414,51 @@ fn main() {
|
||||
.help(vmm::config::DeviceConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("add-disk")
|
||||
.about("Add block device")
|
||||
.arg(
|
||||
Arg::with_name("disk_config")
|
||||
.index(1)
|
||||
.help(vmm::config::DiskConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("add-fs")
|
||||
.about("Add virtio-fs backed fs device")
|
||||
.arg(
|
||||
Arg::with_name("fs_config")
|
||||
.index(1)
|
||||
.help(vmm::config::FsConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("add-pmem")
|
||||
.about("Add persistent memory device")
|
||||
.arg(
|
||||
Arg::with_name("pmem_config")
|
||||
.index(1)
|
||||
.help(vmm::config::PmemConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("add-net")
|
||||
.about("Add network device")
|
||||
.arg(
|
||||
Arg::with_name("net_config")
|
||||
.index(1)
|
||||
.help(vmm::config::NetConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("add-vsock")
|
||||
.about("Add vsock device")
|
||||
.arg(
|
||||
Arg::with_name("vsock_config")
|
||||
.index(1)
|
||||
.help(vmm::config::VsockConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("remove-device")
|
||||
.about("Remove VFIO device")
|
||||
@@ -300,7 +486,25 @@ fn main() {
|
||||
),
|
||||
)
|
||||
.subcommand(SubCommand::with_name("resume").about("Resume the VM"))
|
||||
.subcommand(SubCommand::with_name("shutdown").about("Shutdown the VM"));
|
||||
.subcommand(SubCommand::with_name("shutdown").about("Shutdown the VM"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("snapshot")
|
||||
.about("Create a snapshot from VM")
|
||||
.arg(
|
||||
Arg::with_name("snapshot_config")
|
||||
.index(1)
|
||||
.help("<destination_url>"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("restore")
|
||||
.about("Restore VM from a snapshot")
|
||||
.arg(
|
||||
Arg::with_name("restore_config")
|
||||
.index(1)
|
||||
.help(vmm::config::RestoreConfig::SYNTAX),
|
||||
),
|
||||
);
|
||||
|
||||
let matches = app.get_matches();
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ fn main() {
|
||||
.long("block-backend")
|
||||
.help(
|
||||
"vhost-user-block backend parameters \
|
||||
\"image=<image_path>,sock=<socket_path>,num_queues=<number_of_queues>,\
|
||||
\"path=<image_path>,socket=<socket_path>,num_queues=<number_of_queues>,\
|
||||
readonly=true|false,direct=true|false,poll_queue=true|false\"",
|
||||
)
|
||||
.takes_value(true)
|
||||
|
||||
@@ -15,7 +15,7 @@ use futures::executor::{ThreadPool, ThreadPoolBuilder};
|
||||
use libc::EFD_NONBLOCK;
|
||||
use log::*;
|
||||
use std::num::Wrapping;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::{convert, error, fmt, io, process};
|
||||
|
||||
use vhost_rs::vhost_user::message::*;
|
||||
@@ -83,7 +83,7 @@ impl convert::From<Error> for io::Error {
|
||||
}
|
||||
}
|
||||
|
||||
struct VhostUserFsBackend<F: FileSystem + Send + Sync + 'static> {
|
||||
struct VhostUserFsThread<F: FileSystem + Send + Sync + 'static> {
|
||||
mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
kill_evt: EventFd,
|
||||
server: Arc<Server<F>>,
|
||||
@@ -93,9 +93,9 @@ struct VhostUserFsBackend<F: FileSystem + Send + Sync + 'static> {
|
||||
pool: ThreadPool,
|
||||
}
|
||||
|
||||
impl<F: FileSystem + Send + Sync + 'static> Clone for VhostUserFsBackend<F> {
|
||||
impl<F: FileSystem + Send + Sync + 'static> Clone for VhostUserFsThread<F> {
|
||||
fn clone(&self) -> Self {
|
||||
VhostUserFsBackend {
|
||||
VhostUserFsThread {
|
||||
mem: self.mem.clone(),
|
||||
kill_evt: self.kill_evt.try_clone().unwrap(),
|
||||
server: self.server.clone(),
|
||||
@@ -106,9 +106,9 @@ impl<F: FileSystem + Send + Sync + 'static> Clone for VhostUserFsBackend<F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> {
|
||||
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsThread<F> {
|
||||
fn new(fs: F, thread_pool_size: usize) -> Result<Self> {
|
||||
Ok(VhostUserFsBackend {
|
||||
Ok(VhostUserFsThread {
|
||||
mem: None,
|
||||
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
|
||||
server: Arc::new(Server::new(fs)),
|
||||
@@ -176,6 +176,17 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -197,25 +208,27 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBack
|
||||
}
|
||||
|
||||
fn set_event_idx(&mut self, enabled: bool) {
|
||||
self.event_idx = enabled;
|
||||
self.thread.lock().unwrap().event_idx = enabled;
|
||||
}
|
||||
|
||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
||||
self.mem = Some(GuestMemoryAtomic::new(mem));
|
||||
self.thread.lock().unwrap().mem = Some(GuestMemoryAtomic::new(mem));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
&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 mem = match &self.mem {
|
||||
let mut thread = self.thread.lock().unwrap();
|
||||
let mem = match &thread.mem {
|
||||
Some(m) => m.memory(),
|
||||
None => return Err(Error::NoMemoryConfigured.into()),
|
||||
};
|
||||
@@ -232,7 +245,7 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBack
|
||||
_ => return Err(Error::HandleEventUnknownEvent.into()),
|
||||
};
|
||||
|
||||
if self.event_idx {
|
||||
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
|
||||
@@ -242,24 +255,27 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBack
|
||||
let mut vring = vring_lock.write().unwrap();
|
||||
vring.mut_queue().update_avail_event(&mem);
|
||||
}
|
||||
if !self.process_queue(vring_lock.clone())? {
|
||||
if !thread.process_queue(vring_lock.clone())? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Without EVENT_IDX, a single call is enough.
|
||||
self.process_queue(vring_lock)?;
|
||||
thread.process_queue(vring_lock)?;
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> {
|
||||
Some((self.kill_evt.try_clone().unwrap(), Some(KILL_EVENT)))
|
||||
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.vu_req = Some(vu_req);
|
||||
self.thread.lock().unwrap().vu_req = Some(vu_req);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,7 +354,15 @@ fn main() {
|
||||
error!("Waiting for daemon failed: {:?}", e);
|
||||
}
|
||||
|
||||
let kill_evt = &fs_backend.read().unwrap().kill_evt;
|
||||
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)
|
||||
}
|
||||
|
||||
459
src/main.rs
459
src/main.rs
File diff suppressed because it is too large
Load Diff
@@ -74,7 +74,7 @@ write_files:
|
||||
bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id"
|
||||
bash -c "echo 0000:00:06.0 > /sys/bus/pci/devices/0000\:00\:06.0/driver/unbind"
|
||||
bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id"
|
||||
# 512M ram requires 256 pages
|
||||
echo 256 | sudo tee /proc/sys/vm/nr_hugepages
|
||||
# 1G ram requires 512 pages
|
||||
echo 512 | sudo tee /proc/sys/vm/nr_hugepages
|
||||
sudo chmod a+rwX /dev/hugepages
|
||||
/mnt/cloud-hypervisor --kernel /mnt/vmlinux --cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=PARTUUID=6fb4d1a8-6c8c-4dd7-9f7c-1fe0b9f2574c VFIOTAG" --disk path=/mnt/clear-31311-cloudguest.img path=/mnt/cloudinit.img --cpus boot=1 --memory size=512M,file=/dev/hugepages --device path=/sys/bus/pci/devices/0000:00:05.0/ path=/sys/bus/pci/devices/0000:00:06.0/ --api-socket /tmp/ch_api.sock
|
||||
/mnt/cloud-hypervisor --kernel /mnt/vmlinux --cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=PARTUUID=6fb4d1a8-6c8c-4dd7-9f7c-1fe0b9f2574c VFIOTAG" --disk path=/mnt/clear-31311-cloudguest.img path=/mnt/cloudinit.img --cpus boot=1 --memory size=512M,hotplug_size=1G,file=/dev/hugepages --device path=/sys/bus/pci/devices/0000:00:05.0/ path=/sys/bus/pci/devices/0000:00:06.0/ --api-socket /tmp/ch_api.sock
|
||||
|
||||
1616
tests/integration.rs
1616
tests/integration.rs
File diff suppressed because it is too large
Load Diff
@@ -7,14 +7,14 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
arc-swap = ">=0.4.4"
|
||||
byteorder = "1.3.4"
|
||||
devices = { path = "../devices" }
|
||||
kvm-bindings = "0.2.0"
|
||||
kvm-ioctls = "0.5.0"
|
||||
libc = "0.2.68"
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch" }
|
||||
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
|
||||
libc = "0.2.69"
|
||||
log = "0.4.8"
|
||||
pci = { path = "../pci" }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = { version = "0.1.0", features = ["backend-mmap"] }
|
||||
vm-memory = { version = "0.2.0", features = ["backend-mmap"] }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
[dependencies.vfio-bindings]
|
||||
|
||||
@@ -25,7 +25,7 @@ use vfio_ioctls::*;
|
||||
use vm_device::{get_host_address_range, ExternalDmaMapping};
|
||||
use vm_memory::{
|
||||
Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestMemoryMmap,
|
||||
GuestMemoryRegion,
|
||||
GuestMemoryRegion, GuestRegionMmap,
|
||||
};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::fam::FamStruct;
|
||||
@@ -873,6 +873,18 @@ impl VfioDevice {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn extend_dma_map(&self, new_region: &Arc<GuestRegionMmap>) -> Result<()> {
|
||||
if !self.iommu_attached {
|
||||
self.vfio_dma_map(
|
||||
new_region.start_addr().raw_value(),
|
||||
new_region.len() as u64,
|
||||
new_region.as_ptr() as u64,
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the maximum numner of interrupts a VFIO device can request.
|
||||
/// This is used for pre-allocating the VFIO PCI routes.
|
||||
pub fn max_interrupts(&self) -> u32 {
|
||||
|
||||
@@ -27,7 +27,7 @@ use vm_allocator::SystemAllocator;
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
|
||||
};
|
||||
use vm_memory::{Address, GuestAddress, GuestUsize};
|
||||
use vm_memory::{Address, GuestAddress, GuestRegionMmap, GuestUsize};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -41,6 +41,7 @@ pub enum VfioPciError {
|
||||
SetGsiRouting(kvm_ioctls::Error),
|
||||
MsiNotConfigured,
|
||||
MsixNotConfigured,
|
||||
UpdateMemory(crate::VfioError),
|
||||
UpdateMsiEventFd,
|
||||
UpdateMsixEventFd,
|
||||
}
|
||||
@@ -62,6 +63,7 @@ impl fmt::Display for VfioPciError {
|
||||
VfioPciError::SetGsiRouting(e) => write!(f, "failed to set GSI routes for KVM: {}", e),
|
||||
VfioPciError::MsiNotConfigured => write!(f, "MSI interrupt not yet configured"),
|
||||
VfioPciError::MsixNotConfigured => write!(f, "MSI-X interrupt not yet configured"),
|
||||
VfioPciError::UpdateMemory(e) => write!(f, "failed to update memory: {}", e),
|
||||
VfioPciError::UpdateMsiEventFd => write!(f, "failed to update MSI eventfd"),
|
||||
VfioPciError::UpdateMsixEventFd => write!(f, "failed to update MSI-X eventfd"),
|
||||
}
|
||||
@@ -295,6 +297,7 @@ impl VfioPciDevice {
|
||||
device.reset();
|
||||
|
||||
let configuration = PciConfiguration::new(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
PciClassCode::Other,
|
||||
@@ -610,6 +613,12 @@ impl VfioPciDevice {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_memory(&self, new_region: &Arc<GuestRegionMmap>) -> Result<()> {
|
||||
self.device
|
||||
.extend_dma_map(new_region)
|
||||
.map_err(VfioPciError::UpdateMemory)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VfioPciDevice {
|
||||
|
||||
@@ -11,10 +11,10 @@ mmio_support = ["vm-virtio/mmio_support"]
|
||||
|
||||
[dependencies]
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.68"
|
||||
libc = "0.2.69"
|
||||
log = "0.4.8"
|
||||
virtio-bindings = "0.1.0"
|
||||
vm-memory = "0.1.0"
|
||||
vm-memory = "0.2.0"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
||||
|
||||
@@ -81,10 +81,11 @@ pub trait VhostUserBackend: Send + Sync + 'static {
|
||||
/// virtqueues on its own, but does not know what to do with events
|
||||
/// happening on custom listeners.
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
&self,
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
vrings: &[Arc<RwLock<Vring>>],
|
||||
thread_id: usize,
|
||||
) -> result::Result<bool, io::Error>;
|
||||
|
||||
/// Get virtio device configuration.
|
||||
@@ -105,7 +106,7 @@ pub trait VhostUserBackend: Send + Sync + 'static {
|
||||
/// When this EventFd is written to the worker thread will exit. An optional id may
|
||||
/// also be provided, if it not provided then the exit event will be first event id
|
||||
/// after the last queue
|
||||
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> {
|
||||
fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, Option<u16>)> {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -113,6 +114,10 @@ pub trait VhostUserBackend: Send + Sync + 'static {
|
||||
/// A default implementation is provided as we cannot expect all backends
|
||||
/// to implement this function.
|
||||
fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {}
|
||||
|
||||
fn queues_per_thread(&self) -> Vec<u64> {
|
||||
vec![0xffff_ffff]
|
||||
}
|
||||
}
|
||||
|
||||
/// This structure is the public API the backend is allowed to interact with
|
||||
@@ -187,8 +192,8 @@ impl<S: VhostUserBackend> VhostUserDaemon<S> {
|
||||
/// Retrieve the vring worker. This is necessary to perform further
|
||||
/// actions like registering and unregistering some extra event file
|
||||
/// descriptors.
|
||||
pub fn get_vring_worker(&self) -> Arc<VringWorker> {
|
||||
self.handler.lock().unwrap().get_vring_worker()
|
||||
pub fn get_vring_workers(&self) -> Vec<Arc<VringWorker>> {
|
||||
self.handler.lock().unwrap().get_vring_workers()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,6 +288,7 @@ struct VringEpollHandler<S: VhostUserBackend> {
|
||||
backend: Arc<RwLock<S>>,
|
||||
vrings: Vec<Arc<RwLock<Vring>>>,
|
||||
exit_event_id: Option<u16>,
|
||||
thread_id: usize,
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VringEpollHandler<S> {
|
||||
@@ -310,9 +316,9 @@ impl<S: VhostUserBackend> VringEpollHandler<S> {
|
||||
}
|
||||
|
||||
self.backend
|
||||
.write()
|
||||
.read()
|
||||
.unwrap()
|
||||
.handle_event(device_event, evset, &self.vrings)
|
||||
.handle_event(device_event, evset, &self.vrings, self.thread_id)
|
||||
.map_err(VringEpollHandlerError::HandleEventBackendHandling)
|
||||
}
|
||||
}
|
||||
@@ -453,22 +459,24 @@ type VhostUserHandlerResult<T> = std::result::Result<T, VhostUserHandlerError>;
|
||||
|
||||
struct VhostUserHandler<S: VhostUserBackend> {
|
||||
backend: Arc<RwLock<S>>,
|
||||
worker: Arc<VringWorker>,
|
||||
workers: Vec<Arc<VringWorker>>,
|
||||
owned: bool,
|
||||
features_acked: bool,
|
||||
acked_features: u64,
|
||||
acked_protocol_features: u64,
|
||||
num_queues: usize,
|
||||
max_queue_size: usize,
|
||||
queues_per_thread: Vec<u64>,
|
||||
memory: Option<Memory>,
|
||||
vrings: Vec<Arc<RwLock<Vring>>>,
|
||||
worker_thread: Option<thread::JoinHandle<VringWorkerResult<()>>>,
|
||||
worker_threads: Vec<thread::JoinHandle<VringWorkerResult<()>>>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VhostUserHandler<S> {
|
||||
fn new(backend: Arc<RwLock<S>>) -> VhostUserHandlerResult<Self> {
|
||||
let num_queues = backend.read().unwrap().num_queues();
|
||||
let max_queue_size = backend.read().unwrap().max_queue_size();
|
||||
let queues_per_thread = backend.read().unwrap().queues_per_thread();
|
||||
|
||||
let mut vrings: Vec<Arc<RwLock<Vring>>> = Vec::new();
|
||||
for _ in 0..num_queues {
|
||||
@@ -476,14 +484,18 @@ impl<S: VhostUserBackend> VhostUserHandler<S> {
|
||||
vrings.push(vring);
|
||||
}
|
||||
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?;
|
||||
let mut workers = Vec::new();
|
||||
let mut worker_threads = Vec::new();
|
||||
for (thread_id, queues_mask) in queues_per_thread.iter().enumerate() {
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?;
|
||||
|
||||
let vring_worker = Arc::new(VringWorker { epoll_fd });
|
||||
let worker = vring_worker.clone();
|
||||
let vring_worker = Arc::new(VringWorker { epoll_fd });
|
||||
let worker = vring_worker.clone();
|
||||
|
||||
let exit_event_id =
|
||||
if let Some((exit_event_fd, exit_event_id)) = backend.read().unwrap().exit_event() {
|
||||
let exit_event_id = if let Some((exit_event_fd, exit_event_id)) =
|
||||
backend.read().unwrap().exit_event(thread_id)
|
||||
{
|
||||
let exit_event_id = exit_event_id.unwrap_or(num_queues as u16);
|
||||
worker
|
||||
.register_listener(
|
||||
@@ -497,36 +509,47 @@ impl<S: VhostUserBackend> VhostUserHandler<S> {
|
||||
None
|
||||
};
|
||||
|
||||
let vring_handler = VringEpollHandler {
|
||||
backend: backend.clone(),
|
||||
vrings: vrings.clone(),
|
||||
exit_event_id,
|
||||
};
|
||||
let mut thread_vrings: Vec<Arc<RwLock<Vring>>> = Vec::new();
|
||||
for (index, vring) in vrings.iter().enumerate() {
|
||||
if (queues_mask >> index) & 1u64 == 1u64 {
|
||||
thread_vrings.push(vring.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let worker_thread = Some(
|
||||
thread::Builder::new()
|
||||
let vring_handler = VringEpollHandler {
|
||||
backend: backend.clone(),
|
||||
vrings: thread_vrings,
|
||||
exit_event_id,
|
||||
thread_id,
|
||||
};
|
||||
|
||||
let worker_thread = thread::Builder::new()
|
||||
.name("vring_worker".to_string())
|
||||
.spawn(move || vring_worker.run(vring_handler))
|
||||
.map_err(VhostUserHandlerError::SpawnVringWorker)?,
|
||||
);
|
||||
.map_err(VhostUserHandlerError::SpawnVringWorker)?;
|
||||
|
||||
workers.push(worker);
|
||||
worker_threads.push(worker_thread);
|
||||
}
|
||||
|
||||
Ok(VhostUserHandler {
|
||||
backend,
|
||||
worker,
|
||||
workers,
|
||||
owned: false,
|
||||
features_acked: false,
|
||||
acked_features: 0,
|
||||
acked_protocol_features: 0,
|
||||
num_queues,
|
||||
max_queue_size,
|
||||
queues_per_thread,
|
||||
memory: None,
|
||||
vrings,
|
||||
worker_thread,
|
||||
worker_threads,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_vring_worker(&self) -> Arc<VringWorker> {
|
||||
self.worker.clone()
|
||||
fn get_vring_workers(&self) -> Vec<Arc<VringWorker>> {
|
||||
self.workers.clone()
|
||||
}
|
||||
|
||||
fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult<u64> {
|
||||
@@ -718,9 +741,20 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
||||
// VHOST_USER_GET_VRING_BASE.
|
||||
self.vrings[index as usize].write().unwrap().queue.ready = false;
|
||||
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
|
||||
self.worker
|
||||
.unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index))
|
||||
.map_err(VhostUserError::ReqHandlerError)?;
|
||||
for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() {
|
||||
let shifted_queues_mask = queues_mask >> index;
|
||||
if shifted_queues_mask & 1u64 == 1u64 {
|
||||
let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones();
|
||||
self.workers[thread_index]
|
||||
.unregister_listener(
|
||||
fd.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(evt_idx),
|
||||
)
|
||||
.map_err(VhostUserError::ReqHandlerError)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let next_avail = self.vrings[index as usize]
|
||||
@@ -752,9 +786,20 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
||||
// VHOST_USER_GET_VRING_BASE.
|
||||
self.vrings[index as usize].write().unwrap().queue.ready = true;
|
||||
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
|
||||
self.worker
|
||||
.register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index))
|
||||
.map_err(VhostUserError::ReqHandlerError)?;
|
||||
for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() {
|
||||
let shifted_queues_mask = queues_mask >> index;
|
||||
if shifted_queues_mask & 1u64 == 1u64 {
|
||||
let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones();
|
||||
self.workers[thread_index]
|
||||
.register_listener(
|
||||
fd.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(evt_idx),
|
||||
)
|
||||
.map_err(VhostUserError::ReqHandlerError)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -837,7 +882,7 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
||||
|
||||
impl<S: VhostUserBackend> Drop for VhostUserHandler<S> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(thread) = self.worker_thread.take() {
|
||||
for thread in self.worker_threads.drain(..) {
|
||||
if let Err(e) = thread.join() {
|
||||
error!("Error in vring worker: {:?}", e);
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ edition = "2018"
|
||||
[dependencies]
|
||||
bitflags = "1.1.0"
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.68"
|
||||
libc = "0.2.69"
|
||||
log = "0.4.8"
|
||||
qcow = { path = "../qcow" }
|
||||
vhost_user_backend = { path = "../vhost_user_backend" }
|
||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
||||
virtio-bindings = "0.1.0"
|
||||
vm-memory = "0.1.0"
|
||||
vm-memory = "0.2.0"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
@@ -23,19 +23,20 @@ use std::io::Read;
|
||||
use std::io::{Seek, SeekFrom, Write};
|
||||
use std::mem;
|
||||
use std::num::Wrapping;
|
||||
use std::ops::DerefMut;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::slice;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::time::Instant;
|
||||
use std::vec::Vec;
|
||||
use std::{convert, error, fmt, io};
|
||||
use vhost_rs::vhost_user::message::*;
|
||||
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
|
||||
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring};
|
||||
use virtio_bindings::bindings::virtio_blk::*;
|
||||
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
||||
use vm_memory::{Bytes, GuestMemoryError, GuestMemoryMmap};
|
||||
use vm_memory::{Bytes, GuestMemoryMmap};
|
||||
use vm_virtio::block::{build_disk_image_id, Request};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
@@ -51,17 +52,11 @@ const POLL_QUEUE_US: u128 = 50;
|
||||
trait DiskFile: Read + Seek + Write + Send + Sync {}
|
||||
impl<D: Read + Seek + Write + Send + Sync> DiskFile for D {}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to detect image type.
|
||||
DetectImageType,
|
||||
/// Bad memory address.
|
||||
GuestMemory(GuestMemoryError),
|
||||
/// Can't open image file.
|
||||
OpenImage,
|
||||
enum Error {
|
||||
/// Failed to parse direct parameter.
|
||||
ParseDirectParam,
|
||||
/// Failed to parse image parameter.
|
||||
@@ -96,70 +91,32 @@ impl convert::From<Error> for io::Error {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VhostUserBlkBackend {
|
||||
struct VhostUserBlkThread {
|
||||
mem: Option<GuestMemoryMmap>,
|
||||
vring_worker: Option<Arc<VringWorker>>,
|
||||
disk_image: Box<dyn DiskFile>,
|
||||
disk_image: Arc<Mutex<dyn DiskFile>>,
|
||||
disk_image_id: Vec<u8>,
|
||||
disk_nsectors: u64,
|
||||
config: virtio_blk_config,
|
||||
rdonly: bool,
|
||||
poll_queue: bool,
|
||||
event_idx: bool,
|
||||
kill_evt: EventFd,
|
||||
}
|
||||
|
||||
impl VhostUserBlkBackend {
|
||||
pub fn new(
|
||||
image_path: String,
|
||||
num_queues: usize,
|
||||
rdonly: bool,
|
||||
direct: bool,
|
||||
poll_queue: bool,
|
||||
impl VhostUserBlkThread {
|
||||
fn new(
|
||||
disk_image: Arc<Mutex<dyn DiskFile>>,
|
||||
disk_image_id: Vec<u8>,
|
||||
disk_nsectors: u64,
|
||||
) -> Result<Self> {
|
||||
let mut options = OpenOptions::new();
|
||||
options.read(true);
|
||||
options.write(!rdonly);
|
||||
if direct {
|
||||
options.custom_flags(libc::O_DIRECT);
|
||||
}
|
||||
let image: File = options.open(&image_path).unwrap();
|
||||
let mut raw_img: vm_virtio::RawFile = vm_virtio::RawFile::new(image, direct);
|
||||
|
||||
let image_id = build_disk_image_id(&PathBuf::from(&image_path));
|
||||
let image_type = qcow::detect_image_type(&mut raw_img).unwrap();
|
||||
let mut image = match image_type {
|
||||
ImageType::Raw => Box::new(raw_img) as Box<dyn DiskFile>,
|
||||
ImageType::Qcow2 => Box::new(QcowFile::from(raw_img).unwrap()) as Box<dyn DiskFile>,
|
||||
};
|
||||
|
||||
let nsectors = (image.seek(SeekFrom::End(0)).unwrap() as u64) / SECTOR_SIZE;
|
||||
let mut config = virtio_blk_config::default();
|
||||
|
||||
config.capacity = nsectors;
|
||||
config.blk_size = BLK_SIZE;
|
||||
config.size_max = 65535;
|
||||
config.seg_max = 128 - 2;
|
||||
config.min_io_size = 1;
|
||||
config.opt_io_size = 1;
|
||||
config.num_queues = num_queues as u16;
|
||||
config.wce = 1;
|
||||
|
||||
Ok(VhostUserBlkBackend {
|
||||
Ok(VhostUserBlkThread {
|
||||
mem: None,
|
||||
vring_worker: None,
|
||||
disk_image: image,
|
||||
disk_image_id: image_id,
|
||||
disk_nsectors: nsectors,
|
||||
config,
|
||||
rdonly,
|
||||
poll_queue,
|
||||
disk_image,
|
||||
disk_image_id,
|
||||
disk_nsectors,
|
||||
event_idx: false,
|
||||
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn process_queue(&mut self, vring: &mut Vring) -> bool {
|
||||
fn process_queue(&mut self, vring: &mut Vring) -> bool {
|
||||
let mut used_any = false;
|
||||
let mem = match self.mem.as_ref() {
|
||||
Some(m) => m,
|
||||
@@ -173,7 +130,7 @@ impl VhostUserBlkBackend {
|
||||
Ok(request) => {
|
||||
debug!("element is a valid request");
|
||||
let status = match request.execute(
|
||||
&mut self.disk_image,
|
||||
&mut self.disk_image.lock().unwrap().deref_mut(),
|
||||
self.disk_nsectors,
|
||||
mem,
|
||||
&self.disk_image_id,
|
||||
@@ -215,9 +172,73 @@ impl VhostUserBlkBackend {
|
||||
|
||||
used_any
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_vring_worker(&mut self, vring_worker: Option<Arc<VringWorker>>) {
|
||||
self.vring_worker = vring_worker;
|
||||
struct VhostUserBlkBackend {
|
||||
threads: Vec<Mutex<VhostUserBlkThread>>,
|
||||
config: virtio_blk_config,
|
||||
rdonly: bool,
|
||||
poll_queue: bool,
|
||||
queues_per_thread: Vec<u64>,
|
||||
}
|
||||
|
||||
impl VhostUserBlkBackend {
|
||||
fn new(
|
||||
image_path: String,
|
||||
num_queues: usize,
|
||||
rdonly: bool,
|
||||
direct: bool,
|
||||
poll_queue: bool,
|
||||
) -> Result<Self> {
|
||||
let mut options = OpenOptions::new();
|
||||
options.read(true);
|
||||
options.write(!rdonly);
|
||||
if direct {
|
||||
options.custom_flags(libc::O_DIRECT);
|
||||
}
|
||||
let image: File = options.open(&image_path).unwrap();
|
||||
let mut raw_img: vm_virtio::RawFile = vm_virtio::RawFile::new(image, direct);
|
||||
|
||||
let image_id = build_disk_image_id(&PathBuf::from(&image_path));
|
||||
let image_type = qcow::detect_image_type(&mut raw_img).unwrap();
|
||||
let image = match image_type {
|
||||
ImageType::Raw => Arc::new(Mutex::new(raw_img)) as Arc<Mutex<dyn DiskFile>>,
|
||||
ImageType::Qcow2 => {
|
||||
Arc::new(Mutex::new(QcowFile::from(raw_img).unwrap())) as Arc<Mutex<dyn DiskFile>>
|
||||
}
|
||||
};
|
||||
|
||||
let nsectors = (image.lock().unwrap().seek(SeekFrom::End(0)).unwrap() as u64) / SECTOR_SIZE;
|
||||
let mut config = virtio_blk_config::default();
|
||||
|
||||
config.capacity = nsectors;
|
||||
config.blk_size = BLK_SIZE;
|
||||
config.size_max = 65535;
|
||||
config.seg_max = 128 - 2;
|
||||
config.min_io_size = 1;
|
||||
config.opt_io_size = 1;
|
||||
config.num_queues = num_queues as u16;
|
||||
config.wce = 1;
|
||||
|
||||
let mut queues_per_thread = Vec::new();
|
||||
let mut threads = Vec::new();
|
||||
for i in 0..num_queues {
|
||||
let thread = Mutex::new(VhostUserBlkThread::new(
|
||||
image.clone(),
|
||||
image_id.clone(),
|
||||
nsectors,
|
||||
)?);
|
||||
threads.push(thread);
|
||||
queues_per_thread.push(0b1 << i);
|
||||
}
|
||||
|
||||
Ok(VhostUserBlkBackend {
|
||||
threads,
|
||||
config,
|
||||
rdonly,
|
||||
poll_queue,
|
||||
queues_per_thread,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,19 +269,24 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
||||
}
|
||||
|
||||
fn set_event_idx(&mut self, enabled: bool) {
|
||||
self.event_idx = enabled;
|
||||
for thread in self.threads.iter() {
|
||||
thread.lock().unwrap().event_idx = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
||||
self.mem = Some(mem);
|
||||
for thread in self.threads.iter() {
|
||||
thread.lock().unwrap().mem = Some(mem.clone());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
&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());
|
||||
@@ -268,16 +294,17 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
||||
|
||||
debug!("event received: {:?}", device_event);
|
||||
|
||||
let mut thread = self.threads[thread_id].lock().unwrap();
|
||||
match device_event {
|
||||
q if device_event < self.config.num_queues => {
|
||||
let mut vring = vrings[q as usize].write().unwrap();
|
||||
0 => {
|
||||
let mut vring = vrings[0].write().unwrap();
|
||||
|
||||
if self.poll_queue {
|
||||
// Actively poll the queue until POLL_QUEUE_US has passed
|
||||
// without seeing a new request.
|
||||
let mut now = Instant::now();
|
||||
loop {
|
||||
if self.process_queue(&mut vring) {
|
||||
if thread.process_queue(&mut vring) {
|
||||
now = Instant::now();
|
||||
} else if now.elapsed().as_micros() > POLL_QUEUE_US {
|
||||
break;
|
||||
@@ -285,7 +312,7 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
||||
}
|
||||
}
|
||||
|
||||
if self.event_idx {
|
||||
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
|
||||
@@ -293,14 +320,14 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
||||
loop {
|
||||
vring
|
||||
.mut_queue()
|
||||
.update_avail_event(self.mem.as_ref().unwrap());
|
||||
if !self.process_queue(&mut vring) {
|
||||
.update_avail_event(thread.mem.as_ref().unwrap());
|
||||
if !thread.process_queue(&mut vring) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Without EVENT_IDX, a single call is enough.
|
||||
self.process_queue(&mut vring);
|
||||
thread.process_queue(&mut vring);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
@@ -321,22 +348,35 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
||||
buf.to_vec()
|
||||
}
|
||||
|
||||
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> {
|
||||
Some((self.kill_evt.try_clone().unwrap(), None))
|
||||
fn exit_event(&self, thread_index: usize) -> Option<(EventFd, Option<u16>)> {
|
||||
// The exit event is placed after the queue, which is event index 1.
|
||||
Some((
|
||||
self.threads[thread_index]
|
||||
.lock()
|
||||
.unwrap()
|
||||
.kill_evt
|
||||
.try_clone()
|
||||
.unwrap(),
|
||||
Some(1),
|
||||
))
|
||||
}
|
||||
|
||||
fn queues_per_thread(&self) -> Vec<u64> {
|
||||
self.queues_per_thread.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VhostUserBlkBackendConfig<'a> {
|
||||
pub image: &'a str,
|
||||
pub sock: &'a str,
|
||||
pub num_queues: usize,
|
||||
pub readonly: bool,
|
||||
pub direct: bool,
|
||||
pub poll_queue: bool,
|
||||
struct VhostUserBlkBackendConfig<'a> {
|
||||
image: &'a str,
|
||||
sock: &'a str,
|
||||
num_queues: usize,
|
||||
readonly: bool,
|
||||
direct: bool,
|
||||
poll_queue: bool,
|
||||
}
|
||||
|
||||
impl<'a> VhostUserBlkBackendConfig<'a> {
|
||||
pub fn parse(backend: &'a str) -> Result<Self> {
|
||||
fn parse(backend: &'a str) -> Result<Self> {
|
||||
let params_list: Vec<&str> = backend.split(',').collect();
|
||||
|
||||
let mut image: &str = "";
|
||||
@@ -347,10 +387,10 @@ impl<'a> VhostUserBlkBackendConfig<'a> {
|
||||
let mut poll_queue: bool = true;
|
||||
|
||||
for param in params_list.iter() {
|
||||
if param.starts_with("image=") {
|
||||
image = ¶m[6..];
|
||||
} else if param.starts_with("sock=") {
|
||||
sock = ¶m[5..];
|
||||
if param.starts_with("path=") {
|
||||
image = ¶m[5..];
|
||||
} else if param.starts_with("socket=") {
|
||||
sock = ¶m[7..];
|
||||
} else if param.starts_with("num_queues=") {
|
||||
num_queues_str = ¶m[11..];
|
||||
} else if param.starts_with("readonly=") {
|
||||
@@ -425,12 +465,6 @@ pub fn start_block_backend(backend_command: &str) {
|
||||
.unwrap();
|
||||
debug!("blk_daemon is created!\n");
|
||||
|
||||
let vring_worker = blk_daemon.get_vring_worker();
|
||||
blk_backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_vring_worker(Some(vring_worker));
|
||||
|
||||
if let Err(e) = blk_daemon.start() {
|
||||
error!(
|
||||
"Failed to start daemon for vhost-user-block with error: {:?}\n",
|
||||
@@ -443,8 +477,9 @@ pub fn start_block_backend(backend_command: &str) {
|
||||
error!("Error from the main thread: {:?}", e);
|
||||
}
|
||||
|
||||
let kill_evt = &blk_backend.write().unwrap().kill_evt;
|
||||
if let Err(e) = kill_evt.write(1) {
|
||||
error!("Error shutting down worker thread: {:?}", e)
|
||||
for thread in blk_backend.read().unwrap().threads.iter() {
|
||||
if let Err(e) = thread.lock().unwrap().kill_evt.write(1) {
|
||||
error!("Error shutting down worker thread: {:?}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.1.0"
|
||||
libc = "0.2.68"
|
||||
libc = "0.2.69"
|
||||
log = "0.4.8"
|
||||
vm-memory = "0.1.0"
|
||||
vm-memory = "0.2.0"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
||||
|
||||
@@ -682,11 +682,11 @@ pub trait FileSystem {
|
||||
/// implementation did not return a `Handle` from `open` then the contents of `handle` are
|
||||
/// undefined.
|
||||
///
|
||||
/// If the `FsOptions::HANDLE_KILLPRIV` feature is not enabled then then the file system is
|
||||
/// expected to clear the setuid and setgid bits.
|
||||
///
|
||||
/// If `delayed_write` is true then it indicates that this is a write for buffered data.
|
||||
///
|
||||
/// If `kill_priv` is true then it indicates that the file system is expected to clear the
|
||||
/// setuid and setgid bits.
|
||||
///
|
||||
/// This method should return exactly the number of bytes requested by the kernel, except in the
|
||||
/// case of error. An exception to this rule is if the file was opened with the "direct I/O"
|
||||
/// option (`libc::O_DIRECT`), in which case the kernel will forward the return code from this
|
||||
@@ -702,6 +702,7 @@ pub trait FileSystem {
|
||||
offset: u64,
|
||||
lock_owner: Option<u64>,
|
||||
delayed_write: bool,
|
||||
kill_priv: bool,
|
||||
flags: u32,
|
||||
) -> io::Result<usize> {
|
||||
Err(io::Error::from_raw_os_error(libc::ENOSYS))
|
||||
@@ -1096,6 +1097,22 @@ pub trait FileSystem {
|
||||
Err(io::Error::from_raw_os_error(libc::ENOSYS))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn copyfilerange(
|
||||
&self,
|
||||
ctx: Context,
|
||||
inode_in: Self::Inode,
|
||||
handle_in: Self::Handle,
|
||||
offset_in: u64,
|
||||
inode_out: Self::Inode,
|
||||
handle_out: Self::Handle,
|
||||
offset_out: u64,
|
||||
len: u64,
|
||||
flags: u64,
|
||||
) -> io::Result<usize> {
|
||||
Err(io::Error::from_raw_os_error(libc::ENOSYS))
|
||||
}
|
||||
|
||||
/// TODO: support this
|
||||
fn getlk(&self) -> io::Result<()> {
|
||||
Err(io::Error::from_raw_os_error(libc::ENOSYS))
|
||||
|
||||
@@ -42,7 +42,8 @@ impl FsCacheReqHandler for SlaveFsCacheReq {
|
||||
VhostUserFSSlaveMsgFlags::MAP_R
|
||||
};
|
||||
|
||||
self.fs_slave_map(&msg, fd)
|
||||
self.fs_slave_map(&msg, fd)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unmap(&mut self, requests: Vec<fuse::RemovemappingOne>) -> io::Result<()> {
|
||||
|
||||
@@ -55,6 +55,9 @@ const FOPEN_KEEP_CACHE: u32 = 2;
|
||||
/// The file is not seekable.
|
||||
const FOPEN_NONSEEKABLE: u32 = 4;
|
||||
|
||||
/// Allow caching this directory.
|
||||
const FOPEN_CACHE_DIR: u32 = 8;
|
||||
|
||||
bitflags! {
|
||||
/// Options controlling the behavior of files opened by the server in response
|
||||
/// to an open or create request.
|
||||
@@ -62,6 +65,7 @@ bitflags! {
|
||||
const DIRECT_IO = FOPEN_DIRECT_IO;
|
||||
const KEEP_CACHE = FOPEN_KEEP_CACHE;
|
||||
const NONSEEKABLE = FOPEN_NONSEEKABLE;
|
||||
const CACHE_DIR = FOPEN_CACHE_DIR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +134,21 @@ const HANDLE_KILLPRIV: u32 = 524_288;
|
||||
/// FileSystem supports posix acls.
|
||||
const POSIX_ACL: u32 = 1_048_576;
|
||||
|
||||
/// Reading the device after abort returns ECONNABORTED.
|
||||
const ABORT_ERROR: u32 = 2_097_152;
|
||||
|
||||
/// Init_out.max_pages contains the max number of req pages.
|
||||
const MAX_PAGES: u32 = 4_194_304;
|
||||
|
||||
/// Cache READLINK responses
|
||||
const CACHE_SYMLINKS: u32 = 8_388_608;
|
||||
|
||||
/// Kernel supports zero-message opendir
|
||||
const NO_OPENDIR_SUPPORT: u32 = 16_777_216;
|
||||
|
||||
/// Only invalidate cached pages on explicit request
|
||||
const EXPLICIT_INVAL_DATA: u32 = 33_554_432;
|
||||
|
||||
bitflags! {
|
||||
/// A bitfield passed in as a parameter to and returned from the `init` method of the
|
||||
/// `FileSystem` trait.
|
||||
@@ -296,6 +315,43 @@ bitflags! {
|
||||
///
|
||||
/// This feature is disabled by default.
|
||||
const POSIX_ACL = POSIX_ACL;
|
||||
|
||||
/// Indicates that if the connection is gone because of sysfs abort, reading from the device
|
||||
/// will return -ECONNABORTED.
|
||||
///
|
||||
/// This feature is not currently supported.
|
||||
const ABORT_ERROR = ABORT_ERROR;
|
||||
|
||||
/// Indicates support for negotiating the maximum number of pages supported.
|
||||
///
|
||||
/// If this feature is enabled, we can tell the kernel the maximum number of pages that we
|
||||
/// support to transfer in a single request.
|
||||
///
|
||||
/// This feature is enabled by default if supported by the kernel.
|
||||
const MAX_PAGES = MAX_PAGES;
|
||||
|
||||
/// Indicates that the kernel supports caching READLINK responses.
|
||||
///
|
||||
/// This feature is not currently supported.
|
||||
const CACHE_SYMLINKS = CACHE_SYMLINKS;
|
||||
|
||||
/// Indicates support for zero-message opens. If this flag is set in the `capable` parameter
|
||||
/// of the `init` trait method, then the file system may return `ENOSYS` from the opendir() handler
|
||||
/// to indicate success. Further attempts to open directories will be handled in the kernel. (If
|
||||
/// this flag is not set, returning ENOSYS will be treated as an error and signaled to the
|
||||
/// caller).
|
||||
///
|
||||
/// Setting (or not setting) the field in the `FsOptions` returned from the `init` method
|
||||
/// has no effect.
|
||||
const ZERO_MESSAGE_OPENDIR = NO_OPENDIR_SUPPORT;
|
||||
|
||||
/// Indicates support for explicit data invalidation. If this feature is enabled, the
|
||||
/// server is fully responsible for data cache invalidation, and the kernel won't
|
||||
/// invalidate files data cache on size change and only truncate that cache to new size
|
||||
/// in case the size decreased.
|
||||
///
|
||||
/// This feature is not currently supported.
|
||||
const EXPLICIT_INVAL_DATA = EXPLICIT_INVAL_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,6 +373,9 @@ pub const WRITE_CACHE: u32 = 1;
|
||||
/// `lock_owner` field is valid.
|
||||
pub const WRITE_LOCKOWNER: u32 = 2;
|
||||
|
||||
/// Kill suid and sgid bits
|
||||
pub const WRITE_KILL_PRIV: u32 = 4;
|
||||
|
||||
// Read flags.
|
||||
pub const READ_LOCKOWNER: u32 = 2;
|
||||
|
||||
@@ -337,6 +396,9 @@ const IOCTL_32BIT: u32 = 8;
|
||||
/// Is a directory
|
||||
const IOCTL_DIR: u32 = 16;
|
||||
|
||||
/// x32 compat ioctl on 64bit machine (64bit time_t)
|
||||
const IOCTL_COMPAT_X32: u32 = 32;
|
||||
|
||||
/// Maximum of in_iovecs + out_iovecs
|
||||
const IOCTL_MAX_IOV: u32 = 256;
|
||||
|
||||
@@ -357,6 +419,9 @@ bitflags! {
|
||||
/// Is a directory
|
||||
const IOCTL_DIR = IOCTL_DIR;
|
||||
|
||||
/// x32 compat ioctl on 64bit machine (64bit time_t)
|
||||
const IOCTL_COMPAT_X32 = IOCTL_COMPAT_X32;
|
||||
|
||||
/// Maximum of in_iovecs + out_iovecs
|
||||
const IOCTL_MAX_IOV = IOCTL_MAX_IOV;
|
||||
}
|
||||
@@ -835,7 +900,9 @@ pub struct InitOut {
|
||||
pub congestion_threshold: u16,
|
||||
pub max_write: u32,
|
||||
pub time_gran: u32,
|
||||
pub unused: [u32; 9],
|
||||
pub max_pages: u16,
|
||||
pub padding: u16,
|
||||
pub unused: [u32; 8],
|
||||
}
|
||||
unsafe impl ByteValued for InitOut {}
|
||||
|
||||
@@ -1049,6 +1116,19 @@ pub struct LseekOut {
|
||||
}
|
||||
unsafe impl ByteValued for LseekOut {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct CopyfilerangeIn {
|
||||
pub fh_in: u64,
|
||||
pub off_in: u64,
|
||||
pub nodeid_out: u64,
|
||||
pub fh_out: u64,
|
||||
pub off_out: u64,
|
||||
pub len: u64,
|
||||
pub flags: u64,
|
||||
}
|
||||
unsafe impl ByteValued for CopyfilerangeIn {}
|
||||
|
||||
bitflags! {
|
||||
pub struct SetupmappingFlags: u64 {
|
||||
const WRITE = 0x1;
|
||||
|
||||
@@ -557,7 +557,13 @@ impl PassthroughFs {
|
||||
OpenOptions::DIRECT_IO,
|
||||
flags & (libc::O_DIRECTORY as u32) == 0,
|
||||
),
|
||||
CachePolicy::Always => opts |= OpenOptions::KEEP_CACHE,
|
||||
CachePolicy::Always => {
|
||||
if flags & (libc::O_DIRECTORY as u32) == 0 {
|
||||
opts |= OpenOptions::KEEP_CACHE;
|
||||
} else {
|
||||
opts |= OpenOptions::CACHE_DIR;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
@@ -991,11 +997,15 @@ impl FileSystem for PassthroughFs {
|
||||
offset: u64,
|
||||
_lock_owner: Option<u64>,
|
||||
_delayed_write: bool,
|
||||
kill_priv: bool,
|
||||
_flags: u32,
|
||||
) -> io::Result<usize> {
|
||||
// We need to change credentials during a write so that the kernel will remove setuid or
|
||||
// setgid bits from the file if it was written to by someone other than the owner.
|
||||
let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?;
|
||||
if kill_priv {
|
||||
// We need to change credentials during a write so that the kernel will remove setuid
|
||||
// or setgid bits from the file if it was written to by someone other than the owner.
|
||||
let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?;
|
||||
}
|
||||
|
||||
let data = self
|
||||
.handles
|
||||
.read()
|
||||
@@ -1644,4 +1654,60 @@ impl FileSystem for PassthroughFs {
|
||||
Ok(res as u64)
|
||||
}
|
||||
}
|
||||
|
||||
fn copyfilerange(
|
||||
&self,
|
||||
_ctx: Context,
|
||||
inode_in: Inode,
|
||||
handle_in: Handle,
|
||||
offset_in: u64,
|
||||
inode_out: Inode,
|
||||
handle_out: Handle,
|
||||
offset_out: u64,
|
||||
len: u64,
|
||||
flags: u64,
|
||||
) -> io::Result<usize> {
|
||||
let data_in = self
|
||||
.handles
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&handle_in)
|
||||
.filter(|hd| hd.inode == inode_in)
|
||||
.map(Arc::clone)
|
||||
.ok_or_else(ebadf)?;
|
||||
|
||||
// Take just a read lock as we're not going to alter the file descriptor offset.
|
||||
let fd_in = data_in.file.read().unwrap().as_raw_fd();
|
||||
|
||||
let data_out = self
|
||||
.handles
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&handle_out)
|
||||
.filter(|hd| hd.inode == inode_out)
|
||||
.map(Arc::clone)
|
||||
.ok_or_else(ebadf)?;
|
||||
|
||||
// Take just a read lock as we're not going to alter the file descriptor offset.
|
||||
let fd_out = data_out.file.read().unwrap().as_raw_fd();
|
||||
|
||||
// Safe because this will only modify `offset_in` and `offset_out` and we check
|
||||
// the return value.
|
||||
let res = unsafe {
|
||||
libc::syscall(
|
||||
libc::SYS_copy_file_range,
|
||||
fd_in,
|
||||
&mut (offset_in as i64) as &mut _ as *mut _,
|
||||
fd_out,
|
||||
&mut (offset_out as i64) as &mut _ as *mut _,
|
||||
len,
|
||||
flags,
|
||||
)
|
||||
};
|
||||
if res < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(res as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::CStr;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Write};
|
||||
@@ -19,7 +20,7 @@ use crate::filesystem::{
|
||||
use crate::fuse::*;
|
||||
use crate::{Error, Result};
|
||||
|
||||
const MAX_BUFFER_SIZE: u32 = (1 << 20);
|
||||
const MAX_BUFFER_SIZE: u32 = 1 << 20;
|
||||
const DIRENT_PADDING: [u8; 8] = [0; 8];
|
||||
|
||||
struct ZCReader<'a>(Reader<'a>);
|
||||
@@ -124,6 +125,7 @@ impl<F: FileSystem + Sync> Server<F> {
|
||||
x if x == Opcode::Readdirplus as u32 => self.readdirplus(in_header, r, w),
|
||||
x if x == Opcode::Rename2 as u32 => self.rename2(in_header, r, w),
|
||||
x if x == Opcode::Lseek as u32 => self.lseek(in_header, r, w),
|
||||
x if x == Opcode::CopyFileRange as u32 => self.copyfilerange(in_header, r, w),
|
||||
x if x == Opcode::SetupMapping as u32 => self.setupmapping(in_header, r, w, vu_req),
|
||||
x if x == Opcode::RemoveMapping as u32 => self.removemapping(in_header, r, w, vu_req),
|
||||
_ => reply_error(
|
||||
@@ -634,6 +636,7 @@ impl<F: FileSystem + Sync> Server<F> {
|
||||
};
|
||||
|
||||
let delayed_write = write_flags & WRITE_CACHE != 0;
|
||||
let kill_priv = write_flags & WRITE_KILL_PRIV != 0;
|
||||
|
||||
let data_reader = ZCReader(r);
|
||||
|
||||
@@ -646,6 +649,7 @@ impl<F: FileSystem + Sync> Server<F> {
|
||||
offset,
|
||||
owner,
|
||||
delayed_write,
|
||||
kill_priv,
|
||||
flags,
|
||||
) {
|
||||
Ok(count) => {
|
||||
@@ -902,10 +906,14 @@ impl<F: FileSystem + Sync> Server<F> {
|
||||
| FsOptions::HANDLE_KILLPRIV
|
||||
| FsOptions::ASYNC_DIO
|
||||
| FsOptions::HAS_IOCTL_DIR
|
||||
| FsOptions::ATOMIC_O_TRUNC;
|
||||
| FsOptions::ATOMIC_O_TRUNC
|
||||
| FsOptions::MAX_PAGES;
|
||||
|
||||
let capable = FsOptions::from_bits_truncate(flags);
|
||||
|
||||
let page_size: u32 = unsafe { libc::sysconf(libc::_SC_PAGESIZE).try_into().unwrap() };
|
||||
let max_pages = ((MAX_BUFFER_SIZE - 1) / page_size) + 1;
|
||||
|
||||
match self.fs.init(capable) {
|
||||
Ok(want) => {
|
||||
let enabled = capable & (want | supported);
|
||||
@@ -919,6 +927,7 @@ impl<F: FileSystem + Sync> Server<F> {
|
||||
congestion_threshold: (::std::u16::MAX / 4) * 3,
|
||||
max_write: MAX_BUFFER_SIZE,
|
||||
time_gran: 1, // nanoseconds
|
||||
max_pages: max_pages.try_into().unwrap(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -1261,6 +1270,41 @@ impl<F: FileSystem + Sync> Server<F> {
|
||||
Err(e) => reply_error(e, in_header.unique, w),
|
||||
}
|
||||
}
|
||||
|
||||
fn copyfilerange(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
|
||||
let CopyfilerangeIn {
|
||||
fh_in,
|
||||
off_in,
|
||||
nodeid_out,
|
||||
fh_out,
|
||||
off_out,
|
||||
len,
|
||||
flags,
|
||||
..
|
||||
} = r.read_obj().map_err(Error::DecodeMessage)?;
|
||||
|
||||
match self.fs.copyfilerange(
|
||||
Context::from(in_header),
|
||||
in_header.nodeid.into(),
|
||||
fh_in.into(),
|
||||
off_in,
|
||||
nodeid_out.into(),
|
||||
fh_out.into(),
|
||||
off_out,
|
||||
len,
|
||||
flags,
|
||||
) {
|
||||
Ok(count) => {
|
||||
let out = WriteOut {
|
||||
size: count as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
reply_ok(Some(out), None, in_header.unique, w)
|
||||
}
|
||||
Err(e) => reply_error(e, in_header.unique, w),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reply_ok<T: ByteValued>(
|
||||
|
||||
@@ -7,12 +7,12 @@ edition = "2018"
|
||||
[dependencies]
|
||||
bitflags = "1.1.0"
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.68"
|
||||
libc = "0.2.69"
|
||||
log = "0.4.8"
|
||||
net_util = { path = "../net_util" }
|
||||
vhost_user_backend = { path = "../vhost_user_backend" }
|
||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
||||
virtio-bindings = "0.1.0"
|
||||
vm-memory = "0.1.0"
|
||||
vm-memory = "0.2.0"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
@@ -16,14 +16,13 @@ use epoll;
|
||||
use libc::{self, EAGAIN, EFD_NONBLOCK};
|
||||
use log::*;
|
||||
use net_util::Tap;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::io::Read;
|
||||
use std::io::{self};
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::process;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::vec::Vec;
|
||||
use vhost_rs::vhost_user::message::*;
|
||||
use vhost_rs::vhost_user::Error as VhostUserError;
|
||||
@@ -92,104 +91,64 @@ impl std::convert::From<Error> for std::io::Error {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VhostUserNetBackend {
|
||||
struct VhostUserNetThread {
|
||||
mem: Option<GuestMemoryMmap>,
|
||||
vring_worker: Option<Arc<VringWorker>>,
|
||||
kill_evt: EventFd,
|
||||
taps: Vec<(Tap, usize)>,
|
||||
rxs: Vec<RxVirtio>,
|
||||
txs: Vec<TxVirtio>,
|
||||
rx_tap_listenings: Vec<bool>,
|
||||
num_queues: usize,
|
||||
queue_size: u16,
|
||||
tap: Tap,
|
||||
rx: RxVirtio,
|
||||
tx: TxVirtio,
|
||||
rx_tap_listening: bool,
|
||||
}
|
||||
|
||||
impl VhostUserNetBackend {
|
||||
impl VhostUserNetThread {
|
||||
/// Create a new virtio network device with the given TAP interface.
|
||||
pub fn new_with_tap(taps: Vec<Tap>, num_queues: usize, queue_size: u16) -> Result<Self> {
|
||||
let mut taps_v: Vec<(Tap, usize)> = Vec::new();
|
||||
for (i, tap) in taps.iter().enumerate() {
|
||||
taps_v.push((tap.clone(), num_queues + i));
|
||||
}
|
||||
|
||||
let mut rxs: Vec<RxVirtio> = Vec::new();
|
||||
let mut txs: Vec<TxVirtio> = Vec::new();
|
||||
let mut rx_tap_listenings: Vec<bool> = Vec::new();
|
||||
|
||||
for _ in 0..taps.len() {
|
||||
let rx = RxVirtio::new();
|
||||
rxs.push(rx);
|
||||
let tx = TxVirtio::new();
|
||||
txs.push(tx);
|
||||
rx_tap_listenings.push(false);
|
||||
}
|
||||
|
||||
Ok(VhostUserNetBackend {
|
||||
fn new(tap: Tap) -> Result<Self> {
|
||||
Ok(VhostUserNetThread {
|
||||
mem: None,
|
||||
vring_worker: None,
|
||||
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
|
||||
taps: taps_v,
|
||||
rxs,
|
||||
txs,
|
||||
rx_tap_listenings,
|
||||
num_queues,
|
||||
queue_size,
|
||||
tap,
|
||||
rx: RxVirtio::new(),
|
||||
tx: TxVirtio::new(),
|
||||
rx_tap_listening: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new virtio network device with the given IP address and
|
||||
/// netmask.
|
||||
pub fn new(
|
||||
ip_addr: Ipv4Addr,
|
||||
netmask: Ipv4Addr,
|
||||
num_queues: usize,
|
||||
queue_size: u16,
|
||||
ifname: Option<&str>,
|
||||
) -> Result<Self> {
|
||||
let taps = open_tap(ifname, Some(ip_addr), Some(netmask), num_queues / 2)
|
||||
.map_err(Error::OpenTap)?;
|
||||
|
||||
Self::new_with_tap(taps, num_queues, queue_size)
|
||||
}
|
||||
|
||||
// Copies a single frame from `self.rx.frame_buf` into the guest. Returns true
|
||||
// if a buffer was used, and false if the frame must be deferred until a buffer
|
||||
// is made available by the driver.
|
||||
fn rx_single_frame(&mut self, mut queue: &mut Queue, index: usize) -> Result<bool> {
|
||||
fn rx_single_frame(&mut self, mut queue: &mut Queue) -> Result<bool> {
|
||||
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
|
||||
|
||||
let next_desc = queue.iter(&mem).next();
|
||||
|
||||
if next_desc.is_none() {
|
||||
// Queue has no available descriptors
|
||||
if self.rx_tap_listenings[index] {
|
||||
if self.rx_tap_listening {
|
||||
self.vring_worker
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.unregister_listener(
|
||||
self.taps[index].0.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::try_from(self.taps[index].1).unwrap(),
|
||||
)
|
||||
.unregister_listener(self.tap.as_raw_fd(), epoll::Events::EPOLLIN, 2)
|
||||
.unwrap();
|
||||
self.rx_tap_listenings[index] = false;
|
||||
self.rx_tap_listening = false;
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let write_complete = self.rxs[index].process_desc_chain(&mem, next_desc, &mut queue);
|
||||
let write_complete = self.rx.process_desc_chain(&mem, next_desc, &mut queue);
|
||||
|
||||
Ok(write_complete)
|
||||
}
|
||||
|
||||
fn process_rx(&mut self, vring: &mut Vring, index: usize) -> Result<()> {
|
||||
fn process_rx(&mut self, vring: &mut Vring) -> Result<()> {
|
||||
// Read as many frames as possible.
|
||||
loop {
|
||||
match self.read_tap(index) {
|
||||
match self.read_tap() {
|
||||
Ok(count) => {
|
||||
self.rxs[index].bytes_read = count;
|
||||
if !self.rx_single_frame(&mut vring.mut_queue(), index)? {
|
||||
self.rxs[index].deferred_frame = true;
|
||||
self.rx.bytes_read = count;
|
||||
if !self.rx_single_frame(&mut vring.mut_queue())? {
|
||||
self.rx.deferred_frame = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -207,8 +166,8 @@ impl VhostUserNetBackend {
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.rxs[index].deferred_irqs {
|
||||
self.rxs[index].deferred_irqs = false;
|
||||
if self.rx.deferred_irqs {
|
||||
self.rx.deferred_irqs = false;
|
||||
vring.signal_used_queue().unwrap();
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -216,15 +175,15 @@ impl VhostUserNetBackend {
|
||||
}
|
||||
}
|
||||
|
||||
fn resume_rx(&mut self, vring: &mut Vring, index: usize) -> Result<()> {
|
||||
if self.rxs[index].deferred_frame {
|
||||
if self.rx_single_frame(&mut vring.mut_queue(), index)? {
|
||||
self.rxs[index].deferred_frame = false;
|
||||
fn resume_rx(&mut self, vring: &mut Vring) -> Result<()> {
|
||||
if self.rx.deferred_frame {
|
||||
if self.rx_single_frame(&mut vring.mut_queue())? {
|
||||
self.rx.deferred_frame = false;
|
||||
// process_rx() was interrupted possibly before consuming all
|
||||
// packets in the tap; try continuing now.
|
||||
self.process_rx(vring, index)
|
||||
} else if self.rxs[index].deferred_irqs {
|
||||
self.rxs[index].deferred_irqs = false;
|
||||
self.process_rx(vring)
|
||||
} else if self.rx.deferred_irqs {
|
||||
self.rx.deferred_irqs = false;
|
||||
vring.signal_used_queue().unwrap();
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -235,16 +194,16 @@ impl VhostUserNetBackend {
|
||||
}
|
||||
}
|
||||
|
||||
fn process_tx(&mut self, mut queue: &mut Queue, index: usize) -> Result<()> {
|
||||
fn process_tx(&mut self, mut queue: &mut Queue) -> Result<()> {
|
||||
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
|
||||
|
||||
self.txs[index].process_desc_chain(&mem, &mut self.taps[index].0, &mut queue);
|
||||
self.tx.process_desc_chain(&mem, &mut self.tap, &mut queue);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_tap(&mut self, index: usize) -> io::Result<usize> {
|
||||
self.taps[index].0.read(&mut self.rxs[index].frame_buf)
|
||||
fn read_tap(&mut self) -> io::Result<usize> {
|
||||
self.tap.read(&mut self.rx.frame_buf)
|
||||
}
|
||||
|
||||
pub fn set_vring_worker(&mut self, vring_worker: Option<Arc<VringWorker>>) {
|
||||
@@ -252,6 +211,41 @@ impl VhostUserNetBackend {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VhostUserNetBackend {
|
||||
threads: Vec<Mutex<VhostUserNetThread>>,
|
||||
num_queues: usize,
|
||||
queue_size: u16,
|
||||
queues_per_thread: Vec<u64>,
|
||||
}
|
||||
|
||||
impl VhostUserNetBackend {
|
||||
fn new(
|
||||
ip_addr: Ipv4Addr,
|
||||
netmask: Ipv4Addr,
|
||||
num_queues: usize,
|
||||
queue_size: u16,
|
||||
ifname: Option<&str>,
|
||||
) -> Result<Self> {
|
||||
let mut taps = open_tap(ifname, Some(ip_addr), Some(netmask), num_queues / 2)
|
||||
.map_err(Error::OpenTap)?;
|
||||
|
||||
let mut queues_per_thread = Vec::new();
|
||||
let mut threads = Vec::new();
|
||||
for (i, tap) in taps.drain(..).enumerate() {
|
||||
let thread = Mutex::new(VhostUserNetThread::new(tap)?);
|
||||
threads.push(thread);
|
||||
queues_per_thread.push(0b11 << (i * 2));
|
||||
}
|
||||
|
||||
Ok(VhostUserNetBackend {
|
||||
threads,
|
||||
num_queues,
|
||||
queue_size,
|
||||
queues_per_thread,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserBackend for VhostUserNetBackend {
|
||||
fn num_queues(&self) -> usize {
|
||||
self.num_queues
|
||||
@@ -279,59 +273,55 @@ impl VhostUserBackend for VhostUserNetBackend {
|
||||
fn set_event_idx(&mut self, _enabled: bool) {}
|
||||
|
||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
||||
self.mem = Some(mem);
|
||||
for thread in self.threads.iter() {
|
||||
thread.lock().unwrap().mem = Some(mem.clone());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
&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 tap_start_index = self.num_queues as u16;
|
||||
let tap_end_index = (self.num_queues + self.num_queues / 2 - 1) as u16;
|
||||
|
||||
let mut thread = self.threads[thread_id].lock().unwrap();
|
||||
match device_event {
|
||||
x if ((x < self.num_queues as u16) && (x % 2 == 0)) => {
|
||||
let index = (x / 2) as usize;
|
||||
let mut vring = vrings[x as usize].write().unwrap();
|
||||
self.resume_rx(&mut vring, index)?;
|
||||
0 => {
|
||||
thread.resume_rx(&mut vrings[0].write().unwrap())?;
|
||||
|
||||
if !self.rx_tap_listenings[index] {
|
||||
self.vring_worker.as_ref().unwrap().register_listener(
|
||||
self.taps[index].0.as_raw_fd(),
|
||||
if !thread.rx_tap_listening {
|
||||
thread.vring_worker.as_ref().unwrap().register_listener(
|
||||
thread.tap.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::try_from(self.taps[index].1).unwrap(),
|
||||
2,
|
||||
)?;
|
||||
self.rx_tap_listenings[index] = true;
|
||||
thread.rx_tap_listening = true;
|
||||
}
|
||||
}
|
||||
x if ((x < self.num_queues as u16) && (x % 2 != 0)) => {
|
||||
let index = ((x - 1) / 2) as usize;
|
||||
let mut vring = vrings[x as usize].write().unwrap();
|
||||
self.process_tx(&mut vring.mut_queue(), index)?;
|
||||
1 => {
|
||||
thread.process_tx(&mut vrings[1].write().unwrap().mut_queue())?;
|
||||
}
|
||||
x if x >= tap_start_index && x <= tap_end_index => {
|
||||
let index = x as usize - self.num_queues;
|
||||
let mut vring = vrings[2 * index].write().unwrap();
|
||||
if self.rxs[index].deferred_frame
|
||||
2 => {
|
||||
let mut vring = vrings[0].write().unwrap();
|
||||
if thread.rx.deferred_frame
|
||||
// Process a deferred frame first if available. Don't read from tap again
|
||||
// until we manage to receive this deferred frame.
|
||||
{
|
||||
if self.rx_single_frame(&mut vring.mut_queue(), index)? {
|
||||
self.rxs[index].deferred_frame = false;
|
||||
self.process_rx(&mut vring, index)?;
|
||||
} else if self.rxs[index].deferred_irqs {
|
||||
self.rxs[index].deferred_irqs = false;
|
||||
if thread.rx_single_frame(&mut vring.mut_queue())? {
|
||||
thread.rx.deferred_frame = false;
|
||||
thread.process_rx(&mut vring)?;
|
||||
} else if thread.rx.deferred_irqs {
|
||||
thread.rx.deferred_irqs = false;
|
||||
vring.signal_used_queue()?;
|
||||
}
|
||||
} else {
|
||||
self.process_rx(&mut vring, index)?;
|
||||
thread.process_rx(&mut vring)?;
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::HandleEventUnknownEvent.into()),
|
||||
@@ -340,10 +330,22 @@ impl VhostUserBackend for VhostUserNetBackend {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> {
|
||||
let tap_end_index = (self.num_queues + self.num_queues / 2 - 1) as u16;
|
||||
let kill_index = tap_end_index + 1;
|
||||
Some((self.kill_evt.try_clone().unwrap(), Some(kill_index)))
|
||||
fn exit_event(&self, thread_index: usize) -> Option<(EventFd, Option<u16>)> {
|
||||
// The exit event is placed after the queues and the tap event, which
|
||||
// is event index 3.
|
||||
Some((
|
||||
self.threads[thread_index]
|
||||
.lock()
|
||||
.unwrap()
|
||||
.kill_evt
|
||||
.try_clone()
|
||||
.unwrap(),
|
||||
Some(3),
|
||||
))
|
||||
}
|
||||
|
||||
fn queues_per_thread(&self) -> Vec<u64> {
|
||||
self.queues_per_thread.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +425,7 @@ pub fn start_net_backend(backend_command: &str) {
|
||||
let backend_config = match VhostUserNetBackendConfig::parse(backend_command) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
println!("Failed parsing parameters {:?}", e);
|
||||
eprintln!("Failed parsing parameters {:?}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
@@ -446,15 +448,22 @@ pub fn start_net_backend(backend_command: &str) {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let vring_worker = net_daemon.get_vring_worker();
|
||||
let mut vring_workers = net_daemon.get_vring_workers();
|
||||
|
||||
net_backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_vring_worker(Some(vring_worker));
|
||||
if vring_workers.len() != net_backend.read().unwrap().threads.len() {
|
||||
error!("Number of vring workers must be identical to the number of backend threads");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
for thread in net_backend.read().unwrap().threads.iter() {
|
||||
thread
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_vring_worker(Some(vring_workers.remove(0)));
|
||||
}
|
||||
|
||||
if let Err(e) = net_daemon.start() {
|
||||
println!(
|
||||
error!(
|
||||
"failed to start daemon for vhost-user-net with error: {:?}",
|
||||
e
|
||||
);
|
||||
@@ -465,8 +474,9 @@ pub fn start_net_backend(backend_command: &str) {
|
||||
error!("Error from the main thread: {:?}", e);
|
||||
}
|
||||
|
||||
let kill_evt = &net_backend.write().unwrap().kill_evt;
|
||||
if let Err(e) = kill_evt.write(1) {
|
||||
error!("Error shutting down worker thread: {:?}", e)
|
||||
for thread in net_backend.read().unwrap().threads.iter() {
|
||||
if let Err(e) = thread.lock().unwrap().kill_evt.write(1) {
|
||||
error!("Error shutting down worker thread: {:?}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ authors = ["The Chromium OS Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.68"
|
||||
vm-memory = "0.1.0"
|
||||
libc = "0.2.69"
|
||||
vm-memory = "0.2.0"
|
||||
|
||||
@@ -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.1.0", features = ["backend-mmap"] }
|
||||
vm-memory = { version = "0.2.0", features = ["backend-mmap"] }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
extern crate serde;
|
||||
extern crate thiserror;
|
||||
extern crate vm_memory;
|
||||
|
||||
pub mod interrupt;
|
||||
@@ -9,8 +7,6 @@ use vm_memory::{
|
||||
MemoryRegionAddress,
|
||||
};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Trait meant for triggering the DMA mapping update related to an external
|
||||
/// device not managed fully through virtio. It is dedicated to virtio-iommu
|
||||
/// in order to trigger the map update anytime the mapping is updated from the
|
||||
@@ -23,34 +19,6 @@ pub trait ExternalDmaMapping: Send + Sync {
|
||||
fn unmap(&self, iova: u64, size: u64) -> std::result::Result<(), std::io::Error>;
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MigratableError {
|
||||
#[error("Failed to pause migratable component: {0}")]
|
||||
Pause(#[source] anyhow::Error),
|
||||
|
||||
#[error("Failed to resume migratable component: {0}")]
|
||||
Resume(#[source] anyhow::Error),
|
||||
}
|
||||
|
||||
/// A Pausable component can be paused and resumed.
|
||||
pub trait Pausable {
|
||||
/// Pause the component.
|
||||
fn pause(&mut self) -> std::result::Result<(), MigratableError>;
|
||||
|
||||
/// Resume the component.
|
||||
fn resume(&mut self) -> std::result::Result<(), MigratableError>;
|
||||
}
|
||||
|
||||
/// A snapshotable component can be snapshoted.
|
||||
pub trait Snapshotable {}
|
||||
|
||||
/// Trait to be implemented by any component (device, CPU, RAM, etc) that
|
||||
/// can be migrated.
|
||||
/// All migratable components are paused before being snapshotted, and then
|
||||
/// eventually resumed. Thus any Migratable component must be both Pausable
|
||||
/// and Snapshotable.
|
||||
pub trait Migratable: Send + Pausable + Snapshotable {}
|
||||
|
||||
fn get_region_host_address_range(
|
||||
region: &GuestRegionMmap,
|
||||
addr: MemoryRegionAddress,
|
||||
|
||||
12
vm-migration/Cargo.toml
Normal file
12
vm-migration/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "vm-migration"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
thiserror = "1.0"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
162
vm-migration/src/lib.rs
Normal file
162
vm-migration/src/lib.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
//
|
||||
|
||||
extern crate serde;
|
||||
extern crate thiserror;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MigratableError {
|
||||
#[error("Failed to pause migratable component: {0}")]
|
||||
Pause(#[source] anyhow::Error),
|
||||
|
||||
#[error("Failed to resume migratable component: {0}")]
|
||||
Resume(#[source] anyhow::Error),
|
||||
|
||||
#[error("Failed to snapshot migratable component: {0}")]
|
||||
Snapshot(#[source] anyhow::Error),
|
||||
|
||||
#[error("Failed to restore migratable component: {0}")]
|
||||
Restore(#[source] anyhow::Error),
|
||||
|
||||
#[error("Failed to send migratable component snapshot: {0}")]
|
||||
MigrateSend(#[source] anyhow::Error),
|
||||
|
||||
#[error("Failed to receive migratable component snapshot: {0}")]
|
||||
MigrateReceive(#[source] anyhow::Error),
|
||||
}
|
||||
|
||||
/// A Pausable component can be paused and resumed.
|
||||
pub trait Pausable {
|
||||
/// Pause the component.
|
||||
fn pause(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resume the component.
|
||||
fn resume(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A Snapshottable component snapshot section.
|
||||
/// Migratable component can split their migration snapshot into
|
||||
/// separate sections.
|
||||
/// Splitting a component migration data into different sections
|
||||
/// allows for easier and forward compatible extensions.
|
||||
#[derive(Clone, Default, Deserialize, Serialize)]
|
||||
pub struct SnapshotDataSection {
|
||||
/// The section id.
|
||||
pub id: String,
|
||||
|
||||
/// The section serialized snapshot.
|
||||
pub snapshot: Vec<u8>,
|
||||
}
|
||||
|
||||
/// A Snapshottable component's snapshot is a tree of snapshots, where leafs
|
||||
/// contain the snapshot data. Nodes of this tree track all their children
|
||||
/// through the snapshots field, which is basically their sub-components.
|
||||
/// Leaves will typically have an empty snapshots map, while nodes usually
|
||||
/// carry an empty snapshot_data.
|
||||
///
|
||||
/// For example, a device manager snapshot is the composition of all its
|
||||
/// devices snapshots. The device manager Snapshot would have no snapshot_data
|
||||
/// but one Snapshot child per tracked device. Then each device's Snapshot
|
||||
/// would carry an empty snapshots map but a map of SnapshotDataSection, i.e.
|
||||
/// the actual device snapshot data.
|
||||
#[derive(Clone, Default, Deserialize, Serialize)]
|
||||
pub struct Snapshot {
|
||||
/// The Snapshottable component id.
|
||||
pub id: String,
|
||||
|
||||
/// The Snapshottable component snapshots.
|
||||
pub snapshots: std::collections::HashMap<String, Box<Snapshot>>,
|
||||
|
||||
/// The Snapshottable component's snapshot data.
|
||||
/// A map of snapshot sections, indexed by the section ids.
|
||||
pub snapshot_data: std::collections::HashMap<String, SnapshotDataSection>,
|
||||
}
|
||||
|
||||
impl Snapshot {
|
||||
/// Create an empty Snapshot.
|
||||
pub fn new(id: &str) -> Self {
|
||||
Snapshot {
|
||||
id: id.to_string(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a sub-component's Snapshot to the Snapshot.
|
||||
pub fn add_snapshot(&mut self, snapshot: Snapshot) {
|
||||
self.snapshots
|
||||
.insert(snapshot.id.clone(), Box::new(snapshot));
|
||||
}
|
||||
|
||||
/// Add a SnapshotDatasection to the component snapshot data.
|
||||
pub fn add_data_section(&mut self, section: SnapshotDataSection) {
|
||||
self.snapshot_data.insert(section.id.clone(), section);
|
||||
}
|
||||
}
|
||||
|
||||
/// A snapshottable component can be snapshotted.
|
||||
pub trait Snapshottable: Pausable {
|
||||
/// The snapshottable component id.
|
||||
fn id(&self) -> String {
|
||||
String::new()
|
||||
}
|
||||
|
||||
/// Take a component snapshot.
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
Ok(Snapshot::new(""))
|
||||
}
|
||||
|
||||
/// Restore a component from its snapshot.
|
||||
fn restore(&mut self, _snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A transportable component can be sent or receive to a specific URL.
|
||||
///
|
||||
/// This trait is meant to be used for component that have custom
|
||||
/// transport handlers.
|
||||
pub trait Transportable: Pausable + Snapshottable {
|
||||
/// Send a component snapshot.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `snapshot` - The migratable component snapshot to send.
|
||||
/// * `destination_url` - The destination URL to send the snapshot to. This
|
||||
/// could be an HTTP endpoint, a TCP address or a local file.
|
||||
fn send(
|
||||
&self,
|
||||
_snapshot: &Snapshot,
|
||||
_destination_url: &str,
|
||||
) -> std::result::Result<(), MigratableError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Receive a component snapshot.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `source_url` - The source URL to fetch the snapshot from. This could be an HTTP
|
||||
/// endpoint, a TCP address or a local file.
|
||||
fn recv(&self, _source_url: &str) -> std::result::Result<Snapshot, MigratableError> {
|
||||
Ok(Snapshot::new(""))
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait to be implemented by any component (device, CPU, RAM, etc) that
|
||||
/// can be migrated.
|
||||
/// All migratable components are paused before being snapshotted, and then
|
||||
/// eventually resumed. Thus any Migratable component must be both Pausable
|
||||
/// and Snapshottable.
|
||||
/// Moreover a migratable component can be transported to a remote or local
|
||||
/// destination and thus must be Transportable.
|
||||
pub trait Migratable: Send + Pausable + Snapshottable + Transportable {}
|
||||
@@ -10,19 +10,24 @@ pci_support = ["pci"]
|
||||
mmio_support = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
arc-swap = ">=0.4.4"
|
||||
byteorder = "1.3.4"
|
||||
devices = { path = "../devices" }
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.68"
|
||||
libc = "0.2.69"
|
||||
log = "0.4.8"
|
||||
net_gen = { path = "../net_gen" }
|
||||
net_util = { path = "../net_util" }
|
||||
pci = { path = "../pci", optional = true }
|
||||
serde = ">=1.0.27"
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
tempfile = "3.1.0"
|
||||
virtio-bindings = { git = "https://github.com/rust-vmm/virtio-bindings", version = "0.1", features = ["virtio-v5_0_0"]}
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = { version = "0.1.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-memory = { version = "0.2.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-master", "vhost-user-slave"] }
|
||||
|
||||
@@ -14,8 +14,10 @@ use super::{
|
||||
VirtioDeviceType, VirtioInterruptType,
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use anyhow::anyhow;
|
||||
use epoll;
|
||||
use libc::{c_void, EFD_NONBLOCK};
|
||||
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
||||
use std::cmp;
|
||||
use std::convert::TryInto;
|
||||
@@ -31,11 +33,14 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use virtio_bindings::bindings::virtio_blk::*;
|
||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
||||
use vm_memory::{
|
||||
ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
||||
GuestMemoryError, GuestMemoryMmap,
|
||||
};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
use vmm_sys_util::{eventfd::EventFd, seek_hole::SeekHole, write_zeroes::PunchHole};
|
||||
|
||||
const SECTOR_SHIFT: u8 = 9;
|
||||
@@ -766,7 +771,7 @@ impl<T: DiskFile> BlockEpollHandler<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[derive(Copy, Clone, Debug, Default, Deserialize)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VirtioBlockGeometry {
|
||||
pub cylinders: u16,
|
||||
@@ -774,9 +779,31 @@ pub struct VirtioBlockGeometry {
|
||||
pub sectors: u8,
|
||||
}
|
||||
|
||||
// We must explicitly implement Serialize since the structure is packed and
|
||||
// it's unsafe to borrow from a packed structure. And by default, if we derive
|
||||
// Serialize from serde, it will borrow the values from the structure.
|
||||
// That's why this implementation copies each field separately before it
|
||||
// serializes the entire structure field by field.
|
||||
impl Serialize for VirtioBlockGeometry {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let cylinders = self.cylinders;
|
||||
let heads = self.heads;
|
||||
let sectors = self.sectors;
|
||||
|
||||
let mut virtio_block_geometry = serializer.serialize_struct("VirtioBlockGeometry", 4)?;
|
||||
virtio_block_geometry.serialize_field("cylinders", &cylinders)?;
|
||||
virtio_block_geometry.serialize_field("heads", &heads)?;
|
||||
virtio_block_geometry.serialize_field("sectors", §ors)?;
|
||||
virtio_block_geometry.end()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioBlockGeometry {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[derive(Copy, Clone, Debug, Default, Deserialize)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VirtioBlockConfig {
|
||||
pub capacity: u64,
|
||||
@@ -800,10 +827,67 @@ pub struct VirtioBlockConfig {
|
||||
unused1: [u8; 3],
|
||||
}
|
||||
|
||||
// We must explicitly implement Serialize since the structure is packed and
|
||||
// it's unsafe to borrow from a packed structure. And by default, if we derive
|
||||
// Serialize from serde, it will borrow the values from the structure.
|
||||
// That's why this implementation copies each field separately before it
|
||||
// serializes the entire structure field by field.
|
||||
impl Serialize for VirtioBlockConfig {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let capacity = self.capacity;
|
||||
let size_max = self.size_max;
|
||||
let seg_max = self.seg_max;
|
||||
let geometry = self.geometry;
|
||||
let blk_size = self.blk_size;
|
||||
let physical_block_exp = self.physical_block_exp;
|
||||
let alignment_offset = self.alignment_offset;
|
||||
let min_io_size = self.min_io_size;
|
||||
let opt_io_size = self.opt_io_size;
|
||||
let wce = self.wce;
|
||||
let unused = self.unused;
|
||||
let num_queues = self.num_queues;
|
||||
let max_discard_sectors = self.max_discard_sectors;
|
||||
let max_discard_seg = self.max_discard_seg;
|
||||
let discard_sector_alignment = self.discard_sector_alignment;
|
||||
let max_write_zeroes_sectors = self.max_write_zeroes_sectors;
|
||||
let max_write_zeroes_seg = self.max_write_zeroes_seg;
|
||||
let write_zeroes_may_unmap = self.write_zeroes_may_unmap;
|
||||
let unused1 = self.unused1;
|
||||
|
||||
let mut virtio_block_config = serializer.serialize_struct("VirtioBlockConfig", 60)?;
|
||||
virtio_block_config.serialize_field("capacity", &capacity)?;
|
||||
virtio_block_config.serialize_field("size_max", &size_max)?;
|
||||
virtio_block_config.serialize_field("seg_max", &seg_max)?;
|
||||
virtio_block_config.serialize_field("geometry", &geometry)?;
|
||||
virtio_block_config.serialize_field("blk_size", &blk_size)?;
|
||||
virtio_block_config.serialize_field("physical_block_exp", &physical_block_exp)?;
|
||||
virtio_block_config.serialize_field("alignment_offset", &alignment_offset)?;
|
||||
virtio_block_config.serialize_field("min_io_size", &min_io_size)?;
|
||||
virtio_block_config.serialize_field("opt_io_size", &opt_io_size)?;
|
||||
virtio_block_config.serialize_field("wce", &wce)?;
|
||||
virtio_block_config.serialize_field("unused", &unused)?;
|
||||
virtio_block_config.serialize_field("num_queues", &num_queues)?;
|
||||
virtio_block_config.serialize_field("max_discard_sectors", &max_discard_sectors)?;
|
||||
virtio_block_config.serialize_field("max_discard_seg", &max_discard_seg)?;
|
||||
virtio_block_config
|
||||
.serialize_field("discard_sector_alignment", &discard_sector_alignment)?;
|
||||
virtio_block_config
|
||||
.serialize_field("max_write_zeroes_sectors", &max_write_zeroes_sectors)?;
|
||||
virtio_block_config.serialize_field("max_write_zeroes_seg", &max_write_zeroes_seg)?;
|
||||
virtio_block_config.serialize_field("write_zeroes_may_unmap", &write_zeroes_may_unmap)?;
|
||||
virtio_block_config.serialize_field("unused1", &unused1)?;
|
||||
virtio_block_config.end()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioBlockConfig {}
|
||||
|
||||
/// Virtio device for exposing block level read/write operations on a host file.
|
||||
pub struct Block<T: DiskFile> {
|
||||
id: String,
|
||||
kill_evt: Option<EventFd>,
|
||||
disk_image: Arc<Mutex<T>>,
|
||||
disk_path: PathBuf,
|
||||
@@ -819,11 +903,21 @@ pub struct Block<T: DiskFile> {
|
||||
queue_size: Vec<u16>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct BlockState {
|
||||
pub disk_path: PathBuf,
|
||||
pub disk_nsectors: u64,
|
||||
pub avail_features: u64,
|
||||
pub acked_features: u64,
|
||||
pub config: VirtioBlockConfig,
|
||||
}
|
||||
|
||||
impl<T: DiskFile> Block<T> {
|
||||
/// Create a new virtio block device that operates on the given file.
|
||||
///
|
||||
/// The given file must be seekable and sizable.
|
||||
pub fn new(
|
||||
id: String,
|
||||
mut disk_image: T,
|
||||
disk_path: PathBuf,
|
||||
is_disk_read_only: bool,
|
||||
@@ -862,6 +956,7 @@ impl<T: DiskFile> Block<T> {
|
||||
}
|
||||
|
||||
Ok(Block {
|
||||
id,
|
||||
kill_evt: None,
|
||||
disk_image: Arc::new(Mutex::new(disk_image)),
|
||||
disk_path,
|
||||
@@ -877,6 +972,26 @@ impl<T: DiskFile> Block<T> {
|
||||
queue_size: vec![queue_size; num_queues],
|
||||
})
|
||||
}
|
||||
|
||||
fn state(&self) -> BlockState {
|
||||
BlockState {
|
||||
disk_path: self.disk_path.clone(),
|
||||
disk_nsectors: self.disk_nsectors,
|
||||
avail_features: self.avail_features,
|
||||
acked_features: self.acked_features,
|
||||
config: self.config,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &BlockState) -> io::Result<()> {
|
||||
self.disk_path = state.disk_path.clone();
|
||||
self.disk_nsectors = state.disk_nsectors;
|
||||
self.avail_features = state.avail_features;
|
||||
self.acked_features = state.acked_features;
|
||||
self.config = state.config;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DiskFile> Drop for Block<T> {
|
||||
@@ -1051,5 +1166,45 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
|
||||
}
|
||||
|
||||
virtio_pausable!(Block, T: 'static + DiskFile + Send);
|
||||
impl<T: 'static + DiskFile + Send> Snapshotable for Block<T> {}
|
||||
impl<T: 'static + DiskFile + Send> Snapshottable for Block<T> {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut block_snapshot = Snapshot::new(self.id.as_str());
|
||||
block_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(block_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(block_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||
let block_state = match serde_json::from_slice(&block_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize BLOCK {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
return self.set_state(&block_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not restore BLOCK state {:?}", e))
|
||||
});
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find BLOCK snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
impl<T: 'static + DiskFile + Send> Transportable for Block<T> {}
|
||||
impl<T: 'static + DiskFile + Send> Migratable for Block<T> {}
|
||||
|
||||
@@ -7,8 +7,10 @@ use super::{
|
||||
VirtioInterruptType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use anyhow::anyhow;
|
||||
use epoll;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||
use std;
|
||||
use std::cmp;
|
||||
use std::collections::VecDeque;
|
||||
@@ -20,8 +22,11 @@ use std::result;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
||||
use vm_memory::{ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
@@ -43,8 +48,8 @@ const PAUSE_EVENT: DeviceEventT = 5;
|
||||
//Console size feature bit
|
||||
const VIRTIO_CONSOLE_F_SIZE: u64 = 0;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default, Deserialize)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VirtioConsoleConfig {
|
||||
cols: u16,
|
||||
rows: u16,
|
||||
@@ -52,6 +57,30 @@ pub struct VirtioConsoleConfig {
|
||||
emerg_wr: u32,
|
||||
}
|
||||
|
||||
// We must explicitly implement Serialize since the structure is packed and
|
||||
// it's unsafe to borrow from a packed structure. And by default, if we derive
|
||||
// Serialize from serde, it will borrow the values from the structure.
|
||||
// That's why this implementation copies each field separately before it
|
||||
// serializes the entire structure field by field.
|
||||
impl Serialize for VirtioConsoleConfig {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let cols = self.cols;
|
||||
let rows = self.rows;
|
||||
let max_nr_ports = self.max_nr_ports;
|
||||
let emerg_wr = self.emerg_wr;
|
||||
|
||||
let mut virtio_console_config = serializer.serialize_struct("VirtioConsoleConfig", 12)?;
|
||||
virtio_console_config.serialize_field("cols", &cols)?;
|
||||
virtio_console_config.serialize_field("rows", &rows)?;
|
||||
virtio_console_config.serialize_field("max_nr_ports", &max_nr_ports)?;
|
||||
virtio_console_config.serialize_field("emerg_wr", &emerg_wr)?;
|
||||
virtio_console_config.end()
|
||||
}
|
||||
}
|
||||
|
||||
// Safe because it only has data and has no implicit padding.
|
||||
unsafe impl ByteValued for VirtioConsoleConfig {}
|
||||
|
||||
@@ -343,6 +372,7 @@ impl VirtioConsoleConfig {
|
||||
|
||||
/// Virtio device for exposing console to the guest OS through virtio.
|
||||
pub struct Console {
|
||||
id: String,
|
||||
kill_evt: Option<EventFd>,
|
||||
pause_evt: Option<EventFd>,
|
||||
avail_features: u64,
|
||||
@@ -356,9 +386,18 @@ pub struct Console {
|
||||
paused: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ConsoleState {
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
config: VirtioConsoleConfig,
|
||||
in_buffer: VecDeque<u8>,
|
||||
}
|
||||
|
||||
impl Console {
|
||||
/// Create a new virtio console device that gets random data from /dev/urandom.
|
||||
pub fn new(
|
||||
id: String,
|
||||
out: Box<dyn io::Write + Send + Sync + 'static>,
|
||||
cols: u16,
|
||||
rows: u16,
|
||||
@@ -383,6 +422,7 @@ impl Console {
|
||||
|
||||
Ok((
|
||||
Console {
|
||||
id,
|
||||
kill_evt: None,
|
||||
pause_evt: None,
|
||||
avail_features,
|
||||
@@ -398,6 +438,24 @@ impl Console {
|
||||
console_input,
|
||||
))
|
||||
}
|
||||
|
||||
fn state(&self) -> ConsoleState {
|
||||
ConsoleState {
|
||||
avail_features: self.avail_features,
|
||||
acked_features: self.acked_features,
|
||||
config: *(self.config.lock().unwrap()),
|
||||
in_buffer: self.input.in_buffer.lock().unwrap().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &ConsoleState) -> io::Result<()> {
|
||||
self.avail_features = state.avail_features;
|
||||
self.acked_features = state.acked_features;
|
||||
*(self.config.lock().unwrap()) = state.config;
|
||||
*(self.input.in_buffer.lock().unwrap()) = state.in_buffer.clone();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Console {
|
||||
@@ -563,5 +621,45 @@ impl VirtioDevice for Console {
|
||||
}
|
||||
|
||||
virtio_pausable!(Console);
|
||||
impl Snapshotable for Console {}
|
||||
impl Snapshottable for Console {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut console_snapshot = Snapshot::new(self.id.as_str());
|
||||
console_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(console_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(console_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||
let console_state = match serde_json::from_slice(&console_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize CONSOLE {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
return self.set_state(&console_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not restore CONSOLE state {:?}", e))
|
||||
});
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find CONSOLE snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
impl Transportable for Console {}
|
||||
impl Migratable for Console {}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use super::*;
|
||||
use crate::{ActivateResult, Error, Queue};
|
||||
use std::sync::Arc;
|
||||
use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap, GuestUsize};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
@@ -34,6 +34,15 @@ pub trait VirtioInterrupt: Send + Sync {
|
||||
pub type VirtioIommuRemapping =
|
||||
Box<dyn Fn(u64) -> std::result::Result<u64, std::io::Error> + Send + Sync>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UserspaceMapping {
|
||||
pub host_addr: u64,
|
||||
pub mem_slot: u32,
|
||||
pub addr: GuestAddress,
|
||||
pub len: GuestUsize,
|
||||
pub mergeable: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VirtioSharedMemory {
|
||||
pub offset: u64,
|
||||
@@ -42,6 +51,8 @@ pub struct VirtioSharedMemory {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VirtioSharedMemoryList {
|
||||
pub host_addr: u64,
|
||||
pub mem_slot: u32,
|
||||
pub addr: GuestAddress,
|
||||
pub len: GuestUsize,
|
||||
pub region_list: Vec<VirtioSharedMemory>,
|
||||
@@ -97,6 +108,14 @@ pub trait VirtioDevice: Send {
|
||||
None
|
||||
}
|
||||
|
||||
/// Updates the list of shared memory regions required by the device.
|
||||
fn set_shm_regions(
|
||||
&mut self,
|
||||
_shm_regions: VirtioSharedMemoryList,
|
||||
) -> std::result::Result<(), Error> {
|
||||
std::unimplemented!()
|
||||
}
|
||||
|
||||
fn iommu_translate(&self, addr: u64) -> u64 {
|
||||
addr
|
||||
}
|
||||
@@ -106,6 +125,15 @@ pub trait VirtioDevice: Send {
|
||||
/// every device as part of shutting down the VM. Acting on the device
|
||||
/// after a shutdown() can lead to unpredictable results.
|
||||
fn shutdown(&mut self) {}
|
||||
|
||||
fn update_memory(&mut self, _mem: &GuestMemoryMmap) -> std::result::Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the list of userspace mappings associated with this device.
|
||||
fn userspace_mappings(&self) -> Vec<UserspaceMapping> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait providing address translation the same way a physical DMA remapping
|
||||
|
||||
@@ -21,11 +21,12 @@ use std::result;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
use vm_device::{ExternalDmaMapping, Migratable, MigratableError, Pausable, Snapshotable};
|
||||
use vm_device::ExternalDmaMapping;
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic,
|
||||
GuestMemoryError, GuestMemoryMmap,
|
||||
};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// Queues sizes
|
||||
@@ -792,6 +793,7 @@ impl DmaRemapping for IommuMapping {
|
||||
}
|
||||
|
||||
pub struct Iommu {
|
||||
id: String,
|
||||
kill_evt: Option<EventFd>,
|
||||
pause_evt: Option<EventFd>,
|
||||
avail_features: u64,
|
||||
@@ -807,7 +809,7 @@ pub struct Iommu {
|
||||
}
|
||||
|
||||
impl Iommu {
|
||||
pub fn new() -> io::Result<(Self, Arc<IommuMapping>)> {
|
||||
pub fn new(id: String) -> io::Result<(Self, Arc<IommuMapping>)> {
|
||||
let config = VirtioIommuConfig {
|
||||
page_size_mask: VIRTIO_IOMMU_PAGE_SIZE_MASK,
|
||||
probe_size: PROBE_PROP_SIZE,
|
||||
@@ -821,6 +823,7 @@ impl Iommu {
|
||||
|
||||
Ok((
|
||||
Iommu {
|
||||
id,
|
||||
kill_evt: None,
|
||||
pause_evt: None,
|
||||
avail_features: 1u64 << VIRTIO_F_VERSION_1
|
||||
@@ -1038,5 +1041,10 @@ impl VirtioDevice for Iommu {
|
||||
}
|
||||
|
||||
virtio_pausable!(Iommu);
|
||||
impl Snapshotable for Iommu {}
|
||||
impl Snapshottable for Iommu {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
}
|
||||
impl Transportable for Iommu {}
|
||||
impl Migratable for Iommu {}
|
||||
|
||||
@@ -16,6 +16,10 @@ extern crate epoll;
|
||||
extern crate log;
|
||||
#[cfg(feature = "pci_support")]
|
||||
extern crate pci;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate vhost_rs;
|
||||
extern crate virtio_bindings;
|
||||
extern crate vm_device;
|
||||
@@ -29,6 +33,7 @@ mod device;
|
||||
pub mod block;
|
||||
mod console;
|
||||
mod iommu;
|
||||
pub mod mem;
|
||||
pub mod net;
|
||||
pub mod net_util;
|
||||
mod pmem;
|
||||
@@ -43,6 +48,7 @@ pub use self::block::*;
|
||||
pub use self::console::*;
|
||||
pub use self::device::*;
|
||||
pub use self::iommu::*;
|
||||
pub use self::mem::*;
|
||||
pub use self::net::*;
|
||||
pub use self::net_util::*;
|
||||
pub use self::pmem::*;
|
||||
@@ -62,11 +68,11 @@ const VIRTIO_F_IOMMU_PLATFORM: u32 = 33;
|
||||
const VIRTIO_F_IN_ORDER: u32 = 35;
|
||||
|
||||
// Types taken from linux/virtio_ids.h
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[repr(C)]
|
||||
enum VirtioDeviceType {
|
||||
pub enum VirtioDeviceType {
|
||||
TYPE_NET = 1,
|
||||
TYPE_BLOCK = 2,
|
||||
TYPE_CONSOLE = 3,
|
||||
@@ -77,6 +83,7 @@ enum VirtioDeviceType {
|
||||
TYPE_INPUT = 18,
|
||||
TYPE_VSOCK = 19,
|
||||
TYPE_IOMMU = 23,
|
||||
TYPE_MEM = 24,
|
||||
TYPE_FS = 26,
|
||||
TYPE_PMEM = 27,
|
||||
TYPE_UNKNOWN = 0xFF,
|
||||
@@ -95,6 +102,7 @@ impl From<u32> for VirtioDeviceType {
|
||||
18 => VirtioDeviceType::TYPE_INPUT,
|
||||
19 => VirtioDeviceType::TYPE_VSOCK,
|
||||
23 => VirtioDeviceType::TYPE_IOMMU,
|
||||
24 => VirtioDeviceType::TYPE_MEM,
|
||||
26 => VirtioDeviceType::TYPE_FS,
|
||||
27 => VirtioDeviceType::TYPE_PMEM,
|
||||
_ => VirtioDeviceType::TYPE_UNKNOWN,
|
||||
@@ -118,6 +126,7 @@ impl fmt::Display for VirtioDeviceType {
|
||||
VirtioDeviceType::TYPE_INPUT => "input",
|
||||
VirtioDeviceType::TYPE_VSOCK => "vsock",
|
||||
VirtioDeviceType::TYPE_IOMMU => "iommu",
|
||||
VirtioDeviceType::TYPE_MEM => "mem",
|
||||
VirtioDeviceType::TYPE_FS => "fs",
|
||||
VirtioDeviceType::TYPE_PMEM => "pmem",
|
||||
VirtioDeviceType::TYPE_UNKNOWN => "UNKNOWN",
|
||||
@@ -175,4 +184,7 @@ pub enum Error {
|
||||
EpollCtl(io::Error),
|
||||
EpollWait(io::Error),
|
||||
FailedSignalingDriver(io::Error),
|
||||
VhostUserUpdateMemory(vhost_user::Error),
|
||||
EventfdError(io::Error),
|
||||
SetShmRegionsNotSupported,
|
||||
}
|
||||
|
||||
966
vm-virtio/src/mem.rs
Normal file
966
vm-virtio/src/mem.rs
Normal file
@@ -0,0 +1,966 @@
|
||||
// Copyright (c) 2020 Ant Financial
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
|
||||
VirtioDeviceType, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::{VirtioInterrupt, VirtioInterruptType};
|
||||
use epoll;
|
||||
use libc;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std::cmp;
|
||||
use std::io::{self, Write};
|
||||
use std::mem::size_of;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::sync::mpsc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic,
|
||||
GuestMemoryError, GuestMemoryMmap, GuestMemoryRegion, GuestRegionMmap,
|
||||
};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 128;
|
||||
const NUM_QUEUES: usize = 1;
|
||||
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
|
||||
|
||||
// Use 2 MiB alignment so transparent hugepages can be used by KVM.
|
||||
pub const VIRTIO_MEM_DEFAULT_BLOCK_SIZE: u64 = 512 * 4096;
|
||||
const VIRTIO_MEM_USABLE_EXTENT: u64 = 256 * 1024 * 1024;
|
||||
|
||||
// Request processed successfully, applicable for
|
||||
// - VIRTIO_MEM_REQ_PLUG
|
||||
// - VIRTIO_MEM_REQ_UNPLUG
|
||||
// - VIRTIO_MEM_REQ_UNPLUG_ALL
|
||||
// - VIRTIO_MEM_REQ_STATE
|
||||
const VIRTIO_MEM_RESP_ACK: u16 = 0;
|
||||
|
||||
// Request denied - e.g. trying to plug more than requested, applicable for
|
||||
// - VIRTIO_MEM_REQ_PLUG
|
||||
const VIRTIO_MEM_RESP_NACK: u16 = 1;
|
||||
|
||||
// Request cannot be processed right now, try again later, applicable for
|
||||
// - VIRTIO_MEM_REQ_PLUG
|
||||
// - VIRTIO_MEM_REQ_UNPLUG
|
||||
// - VIRTIO_MEM_REQ_UNPLUG_ALL
|
||||
// VIRTIO_MEM_RESP_BUSY: u16 = 2;
|
||||
|
||||
// Error in request (e.g. addresses/alignemnt), applicable for
|
||||
// - VIRTIO_MEM_REQ_PLUG
|
||||
// - VIRTIO_MEM_REQ_UNPLUG
|
||||
// - VIRTIO_MEM_REQ_STATE
|
||||
const VIRTIO_MEM_RESP_ERROR: u16 = 3;
|
||||
|
||||
// State of memory blocks is "plugged"
|
||||
const VIRTIO_MEM_STATE_PLUGGED: u16 = 0;
|
||||
// State of memory blocks is "unplugged"
|
||||
const VIRTIO_MEM_STATE_UNPLUGGED: u16 = 1;
|
||||
// State of memory blocks is "mixed"
|
||||
const VIRTIO_MEM_STATE_MIXED: u16 = 2;
|
||||
|
||||
// request to plug memory blocks
|
||||
const VIRTIO_MEM_REQ_PLUG: u16 = 0;
|
||||
// request to unplug memory blocks
|
||||
const VIRTIO_MEM_REQ_UNPLUG: u16 = 1;
|
||||
// request to unplug all blocks and shrink the usable size
|
||||
const VIRTIO_MEM_REQ_UNPLUG_ALL: u16 = 2;
|
||||
// request information about the plugged state of memory blocks
|
||||
const VIRTIO_MEM_REQ_STATE: u16 = 3;
|
||||
|
||||
// Get resize event.
|
||||
const RESIZE_EVENT: DeviceEventT = 0;
|
||||
// New descriptors are pending on the virtio queue.
|
||||
const QUEUE_AVAIL_EVENT: DeviceEventT = 1;
|
||||
// The device has been dropped.
|
||||
const KILL_EVENT: DeviceEventT = 2;
|
||||
// The device should be paused.
|
||||
const PAUSE_EVENT: DeviceEventT = 3;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
// Guest gave us bad memory addresses.
|
||||
GuestMemory(GuestMemoryError),
|
||||
// Guest gave us a write only descriptor that protocol says to read from.
|
||||
UnexpectedWriteOnlyDescriptor,
|
||||
// Guest gave us a read only descriptor that protocol says to write to.
|
||||
UnexpectedReadOnlyDescriptor,
|
||||
// Guest gave us too few descriptors in a descriptor chain.
|
||||
DescriptorChainTooShort,
|
||||
// Guest gave us a buffer that was too short to use.
|
||||
BufferLengthTooSmall,
|
||||
// Guest sent us invalid request.
|
||||
InvalidRequest,
|
||||
// Failed to EventFd write.
|
||||
EventFdWriteFail(std::io::Error),
|
||||
// Failed to EventFd try_clone.
|
||||
EventFdTryCloneFail(std::io::Error),
|
||||
// Failed to MpscRecv.
|
||||
MpscRecvFail(mpsc::RecvError),
|
||||
// Resize invalid argument
|
||||
ResizeInval(String),
|
||||
// Fail to resize trigger
|
||||
ResizeTriggerFail(DeviceError),
|
||||
}
|
||||
|
||||
// Got from qemu/include/standard-headers/linux/virtio_mem.h
|
||||
// rust union doesn't support std::default::Default that
|
||||
// need by mem.read_obj.
|
||||
// Then move virtio_mem_req_plug, virtio_mem_req_unplug and
|
||||
// virtio_mem_req_state to virtio_mem_req.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
struct VirtioMemReq {
|
||||
req_type: u16,
|
||||
padding: [u16; 3],
|
||||
addr: u64,
|
||||
nb_blocks: u16,
|
||||
}
|
||||
|
||||
// Safe because it only has data and has no implicit padding.
|
||||
unsafe impl ByteValued for VirtioMemReq {}
|
||||
|
||||
// Got from qemu/include/standard-headers/linux/virtio_mem.h
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
struct VirtioMemRespState {
|
||||
state: u16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
struct VirtioMemResp {
|
||||
resp_type: u16,
|
||||
padding: [u16; 3],
|
||||
|
||||
state: VirtioMemRespState,
|
||||
}
|
||||
|
||||
// Safe because it only has data and has no implicit padding.
|
||||
unsafe impl ByteValued for VirtioMemResp {}
|
||||
|
||||
// Got from qemu/include/standard-headers/linux/virtio_mem.h
|
||||
#[repr(C, packed)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
struct VirtioMemConfig {
|
||||
// Block size and alignment. Cannot change.
|
||||
block_size: u32,
|
||||
// Valid with VIRTIO_MEM_F_ACPI_PXM. Cannot change.
|
||||
node_id: u16,
|
||||
padding: u16,
|
||||
// Start address of the memory region. Cannot change.
|
||||
addr: u64,
|
||||
// Region size (maximum). Cannot change.
|
||||
region_size: u64,
|
||||
// Currently usable region size. Can grow up to region_size. Can
|
||||
// shrink due to VIRTIO_MEM_REQ_UNPLUG_ALL (in which case no config
|
||||
// update will be sent).
|
||||
usable_region_size: u64,
|
||||
// Currently used size. Changes due to plug/unplug requests, but no
|
||||
// config updates will be sent.
|
||||
plugged_size: u64,
|
||||
// Requested size. New plug requests cannot exceed it. Can change.
|
||||
requested_size: u64,
|
||||
}
|
||||
|
||||
// Safe because it only has data and has no implicit padding.
|
||||
unsafe impl ByteValued for VirtioMemConfig {}
|
||||
|
||||
struct Request {
|
||||
req: VirtioMemReq,
|
||||
status_addr: GuestAddress,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
fn parse(
|
||||
avail_desc: &DescriptorChain,
|
||||
mem: &GuestMemoryMmap,
|
||||
) -> result::Result<Request, Error> {
|
||||
// The head contains the request type which MUST be readable.
|
||||
if avail_desc.is_write_only() {
|
||||
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
||||
}
|
||||
if avail_desc.len as usize != size_of::<VirtioMemReq>() {
|
||||
return Err(Error::InvalidRequest);
|
||||
}
|
||||
let req: VirtioMemReq = mem.read_obj(avail_desc.addr).map_err(Error::GuestMemory)?;
|
||||
|
||||
let status_desc = avail_desc
|
||||
.next_descriptor()
|
||||
.ok_or(Error::DescriptorChainTooShort)?;
|
||||
|
||||
// The status MUST always be writable
|
||||
if !status_desc.is_write_only() {
|
||||
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||
}
|
||||
|
||||
if (status_desc.len as usize) < size_of::<VirtioMemResp>() {
|
||||
return Err(Error::BufferLengthTooSmall);
|
||||
}
|
||||
|
||||
Ok(Request {
|
||||
req,
|
||||
status_addr: status_desc.addr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Resize {
|
||||
size: Arc<AtomicU64>,
|
||||
tx: mpsc::Sender<Result<(), Error>>,
|
||||
rx: Option<mpsc::Receiver<Result<(), Error>>>,
|
||||
evt: EventFd,
|
||||
}
|
||||
|
||||
impl Resize {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
Ok(Resize {
|
||||
size: Arc::new(AtomicU64::new(0)),
|
||||
tx,
|
||||
rx: Some(rx),
|
||||
evt: EventFd::new(EFD_NONBLOCK)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn try_clone(&self) -> Result<Self, Error> {
|
||||
Ok(Resize {
|
||||
size: self.size.clone(),
|
||||
tx: self.tx.clone(),
|
||||
rx: None,
|
||||
evt: self.evt.try_clone().map_err(Error::EventFdTryCloneFail)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn work(&self, size: u64) -> Result<(), Error> {
|
||||
if let Some(rx) = &self.rx {
|
||||
self.size.store(size, Ordering::SeqCst);
|
||||
self.evt.write(1).map_err(Error::EventFdWriteFail)?;
|
||||
rx.recv().map_err(Error::MpscRecvFail)?
|
||||
} else {
|
||||
panic!("work should not work with cloned resize")
|
||||
}
|
||||
}
|
||||
|
||||
fn get_size(&self) -> u64 {
|
||||
self.size.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
fn send(&self, r: Result<(), Error>) {
|
||||
self.tx.send(r).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
struct MemEpollHandler {
|
||||
host_addr: u64,
|
||||
host_fd: Option<RawFd>,
|
||||
mem_state: Vec<bool>,
|
||||
config: Arc<Mutex<VirtioMemConfig>>,
|
||||
resize: Resize,
|
||||
queue: Queue,
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
||||
queue_evt: EventFd,
|
||||
kill_evt: EventFd,
|
||||
pause_evt: EventFd,
|
||||
}
|
||||
|
||||
struct StateChangeRequest<'a> {
|
||||
config: VirtioMemConfig,
|
||||
addr: u64,
|
||||
size: u64,
|
||||
nb_blocks: u16,
|
||||
mem_state: &'a mut Vec<bool>,
|
||||
host_addr: u64,
|
||||
host_fd: Option<RawFd>,
|
||||
plug: bool,
|
||||
}
|
||||
|
||||
impl MemEpollHandler {
|
||||
fn virtio_mem_valid_range(config: &VirtioMemConfig, addr: u64, size: u64) -> bool {
|
||||
// address properly aligned?
|
||||
if addr % config.block_size as u64 != 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// reasonable size
|
||||
if addr + size <= addr || size == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// start address in usable range?
|
||||
if addr < config.addr || addr >= config.addr + config.usable_region_size {
|
||||
return false;
|
||||
}
|
||||
|
||||
// end address in usable range?
|
||||
if addr + size > config.addr + config.usable_region_size {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn virtio_mem_check_bitmap(
|
||||
bit_index: usize,
|
||||
nb_blocks: u16,
|
||||
mem_state: &[bool],
|
||||
plug: bool,
|
||||
) -> bool {
|
||||
for state in mem_state.iter().skip(bit_index).take(nb_blocks as usize) {
|
||||
if *state != plug {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn virtio_mem_set_bitmap(
|
||||
bit_index: usize,
|
||||
nb_blocks: u16,
|
||||
mem_state: &mut Vec<bool>,
|
||||
plug: bool,
|
||||
) {
|
||||
for state in mem_state
|
||||
.iter_mut()
|
||||
.skip(bit_index)
|
||||
.take(nb_blocks as usize)
|
||||
{
|
||||
*state = plug;
|
||||
}
|
||||
}
|
||||
|
||||
fn virtio_mem_state_change_request(r: StateChangeRequest) -> u16 {
|
||||
if r.plug && (r.config.plugged_size + r.size > r.config.requested_size) {
|
||||
return VIRTIO_MEM_RESP_NACK;
|
||||
}
|
||||
if !MemEpollHandler::virtio_mem_valid_range(&r.config, r.addr, r.size) {
|
||||
return VIRTIO_MEM_RESP_ERROR;
|
||||
}
|
||||
|
||||
let offset = r.addr - r.config.addr;
|
||||
|
||||
let bit_index = (offset / r.config.block_size as u64) as usize;
|
||||
if !MemEpollHandler::virtio_mem_check_bitmap(bit_index, r.nb_blocks, r.mem_state, !r.plug) {
|
||||
return VIRTIO_MEM_RESP_ERROR;
|
||||
}
|
||||
|
||||
if !r.plug {
|
||||
if let Some(fd) = r.host_fd {
|
||||
let res = unsafe {
|
||||
libc::fallocate64(
|
||||
fd,
|
||||
libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE,
|
||||
offset as libc::off64_t,
|
||||
r.size as libc::off64_t,
|
||||
)
|
||||
};
|
||||
if res != 0 {
|
||||
error!("fallocate64 get error {}", io::Error::last_os_error());
|
||||
return VIRTIO_MEM_RESP_ERROR;
|
||||
}
|
||||
}
|
||||
let res = unsafe {
|
||||
libc::madvise(
|
||||
(r.host_addr + offset) as *mut libc::c_void,
|
||||
r.size as libc::size_t,
|
||||
libc::MADV_DONTNEED,
|
||||
)
|
||||
};
|
||||
if res != 0 {
|
||||
error!("madvise get error {}", io::Error::last_os_error());
|
||||
return VIRTIO_MEM_RESP_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
MemEpollHandler::virtio_mem_set_bitmap(bit_index, r.nb_blocks, r.mem_state, r.plug);
|
||||
|
||||
VIRTIO_MEM_RESP_ACK
|
||||
}
|
||||
|
||||
fn virtio_mem_unplug_all(
|
||||
config: VirtioMemConfig,
|
||||
mem_state: &mut Vec<bool>,
|
||||
host_addr: u64,
|
||||
host_fd: Option<RawFd>,
|
||||
) -> u16 {
|
||||
for x in 0..(config.region_size / config.block_size as u64) as usize {
|
||||
if mem_state[x] {
|
||||
let resp_type =
|
||||
MemEpollHandler::virtio_mem_state_change_request(StateChangeRequest {
|
||||
config,
|
||||
addr: config.addr + x as u64 * config.block_size as u64,
|
||||
size: config.block_size as u64,
|
||||
nb_blocks: 1,
|
||||
mem_state,
|
||||
host_addr,
|
||||
host_fd,
|
||||
plug: false,
|
||||
});
|
||||
if resp_type != VIRTIO_MEM_RESP_ACK {
|
||||
return resp_type;
|
||||
}
|
||||
mem_state[x] = false;
|
||||
}
|
||||
}
|
||||
|
||||
VIRTIO_MEM_RESP_ACK
|
||||
}
|
||||
|
||||
fn virtio_mem_state_request(
|
||||
config: VirtioMemConfig,
|
||||
addr: u64,
|
||||
nb_blocks: u16,
|
||||
mem_state: &mut Vec<bool>,
|
||||
) -> (u16, u16) {
|
||||
let size: u64 = nb_blocks as u64 * config.block_size as u64;
|
||||
let resp_type = if MemEpollHandler::virtio_mem_valid_range(&config, addr, size) {
|
||||
VIRTIO_MEM_RESP_ACK
|
||||
} else {
|
||||
VIRTIO_MEM_RESP_ERROR
|
||||
};
|
||||
|
||||
let offset = addr - config.addr;
|
||||
let bit_index = (offset / config.block_size as u64) as usize;
|
||||
let resp_state =
|
||||
if MemEpollHandler::virtio_mem_check_bitmap(bit_index, nb_blocks, mem_state, true) {
|
||||
VIRTIO_MEM_STATE_PLUGGED
|
||||
} else if MemEpollHandler::virtio_mem_check_bitmap(
|
||||
bit_index, nb_blocks, mem_state, false,
|
||||
) {
|
||||
VIRTIO_MEM_STATE_UNPLUGGED
|
||||
} else {
|
||||
VIRTIO_MEM_STATE_MIXED
|
||||
};
|
||||
|
||||
(resp_type, resp_state)
|
||||
}
|
||||
|
||||
fn virtio_mem_send_response(
|
||||
mem: &GuestMemoryMmap,
|
||||
resp_type: u16,
|
||||
resp_state: u16,
|
||||
status_addr: GuestAddress,
|
||||
) -> u32 {
|
||||
let mut resp = VirtioMemResp::default();
|
||||
resp.resp_type = resp_type;
|
||||
resp.state.state = resp_state;
|
||||
match mem.write_obj(resp, status_addr) {
|
||||
Ok(_) => size_of::<VirtioMemResp>() as u32,
|
||||
Err(e) => {
|
||||
error!("bad guest memory address: {}", e);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn signal(&self, int_type: &VirtioInterruptType) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_cb
|
||||
.trigger(int_type, Some(&self.queue))
|
||||
.map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
}
|
||||
|
||||
fn process_queue(&mut self) -> bool {
|
||||
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
|
||||
let mut used_count = 0;
|
||||
let mem = self.mem.memory();
|
||||
for avail_desc in self.queue.iter(&mem) {
|
||||
let len = match Request::parse(&avail_desc, &mem) {
|
||||
Err(e) => {
|
||||
error!("failed parse VirtioMemReq: {:?}", e);
|
||||
0
|
||||
}
|
||||
Ok(r) => {
|
||||
let mut config = self.config.lock().unwrap();
|
||||
match r.req.req_type {
|
||||
VIRTIO_MEM_REQ_PLUG => {
|
||||
let size: u64 = r.req.nb_blocks as u64 * config.block_size as u64;
|
||||
let resp_type = MemEpollHandler::virtio_mem_state_change_request(
|
||||
StateChangeRequest {
|
||||
config: *config,
|
||||
addr: r.req.addr,
|
||||
size,
|
||||
nb_blocks: r.req.nb_blocks,
|
||||
mem_state: &mut self.mem_state,
|
||||
host_addr: self.host_addr,
|
||||
host_fd: self.host_fd,
|
||||
plug: true,
|
||||
},
|
||||
);
|
||||
if resp_type == VIRTIO_MEM_RESP_ACK {
|
||||
config.plugged_size += size;
|
||||
}
|
||||
MemEpollHandler::virtio_mem_send_response(
|
||||
&mem,
|
||||
resp_type,
|
||||
0u16,
|
||||
r.status_addr,
|
||||
)
|
||||
}
|
||||
VIRTIO_MEM_REQ_UNPLUG => {
|
||||
let size: u64 = r.req.nb_blocks as u64 * config.block_size as u64;
|
||||
let resp_type = MemEpollHandler::virtio_mem_state_change_request(
|
||||
StateChangeRequest {
|
||||
config: *config,
|
||||
addr: r.req.addr,
|
||||
size,
|
||||
nb_blocks: r.req.nb_blocks,
|
||||
mem_state: &mut self.mem_state,
|
||||
host_addr: self.host_addr,
|
||||
host_fd: self.host_fd,
|
||||
plug: false,
|
||||
},
|
||||
);
|
||||
if resp_type == VIRTIO_MEM_RESP_ACK {
|
||||
config.plugged_size -= size;
|
||||
}
|
||||
MemEpollHandler::virtio_mem_send_response(
|
||||
&mem,
|
||||
resp_type,
|
||||
0u16,
|
||||
r.status_addr,
|
||||
)
|
||||
}
|
||||
VIRTIO_MEM_REQ_UNPLUG_ALL => {
|
||||
let resp_type = MemEpollHandler::virtio_mem_unplug_all(
|
||||
*config,
|
||||
&mut self.mem_state,
|
||||
self.host_addr,
|
||||
self.host_fd,
|
||||
);
|
||||
if resp_type == VIRTIO_MEM_RESP_ACK {
|
||||
config.plugged_size = 0;
|
||||
config.usable_region_size = cmp::min(
|
||||
config.region_size,
|
||||
config.requested_size + VIRTIO_MEM_USABLE_EXTENT,
|
||||
);
|
||||
}
|
||||
MemEpollHandler::virtio_mem_send_response(
|
||||
&mem,
|
||||
resp_type,
|
||||
0u16,
|
||||
r.status_addr,
|
||||
)
|
||||
}
|
||||
VIRTIO_MEM_REQ_STATE => {
|
||||
let (resp_type, resp_state) = MemEpollHandler::virtio_mem_state_request(
|
||||
*config,
|
||||
r.req.addr,
|
||||
r.req.nb_blocks,
|
||||
&mut self.mem_state,
|
||||
);
|
||||
MemEpollHandler::virtio_mem_send_response(
|
||||
&mem,
|
||||
resp_type,
|
||||
resp_state,
|
||||
r.status_addr,
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
error!("VirtioMemReq unknown request type {:?}", r.req.req_type);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
used_desc_heads[used_count] = (avail_desc.index, len);
|
||||
used_count += 1;
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
self.queue.add_used(&mem, desc_index, len);
|
||||
}
|
||||
used_count > 0
|
||||
}
|
||||
|
||||
fn run(&mut self, paused: Arc<AtomicBool>) -> result::Result<(), DeviceError> {
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
||||
|
||||
// Add events
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.resize.evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RESIZE_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.queue_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(QUEUE_AVAIL_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.kill_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.pause_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
|
||||
const EPOLL_EVENTS_LEN: usize = 100;
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
// It's well defined from the epoll_wait() syscall
|
||||
// documentation that the epoll loop can be interrupted
|
||||
// before any of the requested events occurred or the
|
||||
// timeout expired. In both those cases, epoll_wait()
|
||||
// returns an error of type EINTR, but this should not
|
||||
// be considered as a regular error. Instead it is more
|
||||
// appropriate to retry, by calling into epoll_wait().
|
||||
continue;
|
||||
}
|
||||
return Err(DeviceError::EpollWait(e));
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let ev_type = event.data as u16;
|
||||
|
||||
match ev_type {
|
||||
RESIZE_EVENT => {
|
||||
if let Err(e) = self.resize.evt.read() {
|
||||
error!("Failed to get resize event: {:?}", e);
|
||||
break 'epoll;
|
||||
} else {
|
||||
let size = self.resize.get_size();
|
||||
let mut config = self.config.lock().unwrap();
|
||||
let mut need_break = false;
|
||||
let r = if config.requested_size == size {
|
||||
Err(Error::ResizeInval(format!("Virtio-mem resize {} is same with current config.requested_size", size)))
|
||||
} else if size > config.region_size {
|
||||
let region_size = config.region_size;
|
||||
Err(Error::ResizeInval(format!(
|
||||
"Virtio-mem resize {} is bigger than config.region_size {}",
|
||||
size, region_size
|
||||
)))
|
||||
} else if size % (config.block_size as u64) != 0 {
|
||||
let block_size = config.block_size;
|
||||
Err(Error::ResizeInval(format!(
|
||||
"Virtio-mem resize {} is not aligned with config.block_size {}",
|
||||
size, block_size
|
||||
)))
|
||||
} else {
|
||||
config.requested_size = size;
|
||||
let tmp_size = cmp::min(
|
||||
config.region_size,
|
||||
config.requested_size + VIRTIO_MEM_USABLE_EXTENT,
|
||||
);
|
||||
config.usable_region_size =
|
||||
cmp::max(config.usable_region_size, tmp_size);
|
||||
if let Err(e) = self.signal(&VirtioInterruptType::Config) {
|
||||
need_break = true;
|
||||
Err(Error::ResizeTriggerFail(e))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
if let Err(e) = &r {
|
||||
error!("{:?}", e);
|
||||
}
|
||||
self.resize.send(r);
|
||||
if need_break {
|
||||
break 'epoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
QUEUE_AVAIL_EVENT => {
|
||||
if let Err(e) = self.queue_evt.read() {
|
||||
error!("Failed to get queue event: {:?}", e);
|
||||
break 'epoll;
|
||||
} else if self.process_queue() {
|
||||
if let Err(e) = self.signal(&VirtioInterruptType::Queue) {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
break 'epoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
KILL_EVENT => {
|
||||
debug!("kill_evt received, stopping epoll loop");
|
||||
break 'epoll;
|
||||
}
|
||||
PAUSE_EVENT => {
|
||||
debug!("PAUSE_EVENT received, pausing virtio-pmem epoll loop");
|
||||
// We loop here to handle spurious park() returns.
|
||||
// Until we have not resumed, the paused boolean will
|
||||
// be true.
|
||||
while paused.load(Ordering::SeqCst) {
|
||||
thread::park();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("Unknown event for virtio-mem");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Virtio device for exposing entropy to the guest OS through virtio.
|
||||
pub struct Mem {
|
||||
id: String,
|
||||
resize: Resize,
|
||||
kill_evt: Option<EventFd>,
|
||||
pause_evt: Option<EventFd>,
|
||||
avail_features: u64,
|
||||
pub acked_features: u64,
|
||||
host_addr: u64,
|
||||
host_fd: Option<RawFd>,
|
||||
config: Arc<Mutex<VirtioMemConfig>>,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<dyn VirtioInterrupt>>,
|
||||
epoll_threads: Option<Vec<thread::JoinHandle<result::Result<(), DeviceError>>>>,
|
||||
paused: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Mem {
|
||||
// Create a new virtio-mem device.
|
||||
pub fn new(id: String, region: &Arc<GuestRegionMmap>, resize: Resize) -> io::Result<Mem> {
|
||||
let region_len = region.len();
|
||||
|
||||
if region_len != region_len / VIRTIO_MEM_DEFAULT_BLOCK_SIZE * VIRTIO_MEM_DEFAULT_BLOCK_SIZE
|
||||
{
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"Virtio-mem size is not aligned with {}",
|
||||
VIRTIO_MEM_DEFAULT_BLOCK_SIZE
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
// Fixme: Not support VIRTIO_MEM_F_ACPI_PXM
|
||||
let avail_features = 1u64 << VIRTIO_F_VERSION_1;
|
||||
|
||||
let mut config = VirtioMemConfig::default();
|
||||
config.block_size = VIRTIO_MEM_DEFAULT_BLOCK_SIZE as u32;
|
||||
config.addr = region.start_addr().raw_value();
|
||||
config.region_size = region.len();
|
||||
config.usable_region_size = cmp::min(
|
||||
config.region_size,
|
||||
config.requested_size + VIRTIO_MEM_USABLE_EXTENT,
|
||||
);
|
||||
|
||||
let host_fd = if let Some(f_offset) = region.file_offset() {
|
||||
Some(f_offset.file().as_raw_fd())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Mem {
|
||||
id,
|
||||
resize,
|
||||
kill_evt: None,
|
||||
pause_evt: None,
|
||||
avail_features,
|
||||
acked_features: 0u64,
|
||||
host_addr: region.as_ptr() as u64,
|
||||
host_fd,
|
||||
config: Arc::new(Mutex::new(config)),
|
||||
queue_evts: None,
|
||||
interrupt_cb: None,
|
||||
epoll_threads: None,
|
||||
paused: Arc::new(AtomicBool::new(false)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Mem {
|
||||
fn drop(&mut self) {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioDevice for Mem {
|
||||
fn device_type(&self) -> u32 {
|
||||
VirtioDeviceType::TYPE_MEM as u32
|
||||
}
|
||||
|
||||
fn queue_max_sizes(&self) -> &[u16] {
|
||||
QUEUE_SIZES
|
||||
}
|
||||
|
||||
fn features(&self) -> u64 {
|
||||
self.avail_features
|
||||
}
|
||||
|
||||
fn ack_features(&mut self, value: u64) {
|
||||
let mut v = value;
|
||||
// Check if the guest is ACK'ing a feature that we didn't claim to have.
|
||||
let unrequested_features = v & !self.avail_features;
|
||||
if unrequested_features != 0 {
|
||||
warn!("Received acknowledge request for unknown feature.");
|
||||
|
||||
// Don't count these features as acked.
|
||||
v &= !unrequested_features;
|
||||
}
|
||||
self.acked_features |= v;
|
||||
}
|
||||
|
||||
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
|
||||
let config = self.config.lock().unwrap();
|
||||
let config_slice = config.as_slice();
|
||||
let config_len = config_slice.len() as u64;
|
||||
if offset >= config_len {
|
||||
error!("Failed to read config space");
|
||||
return;
|
||||
}
|
||||
if let Some(end) = offset.checked_add(data.len() as u64) {
|
||||
// This write can't fail, offset and end are checked against config_len.
|
||||
data.write_all(&config_slice[offset as usize..cmp::min(end, config_len) as usize])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn write_config(&mut self, _offset: u64, _data: &[u8]) {
|
||||
warn!("virtio-mem device configuration is read-only");
|
||||
}
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
||||
mut queues: Vec<Queue>,
|
||||
mut queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES {
|
||||
error!(
|
||||
"Cannot perform activate. Expected {} queue(s), got {}",
|
||||
NUM_QUEUES,
|
||||
queues.len()
|
||||
);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
let (self_kill_evt, kill_evt) = EventFd::new(EFD_NONBLOCK)
|
||||
.and_then(|e| Ok((e.try_clone()?, e)))
|
||||
.map_err(|e| {
|
||||
error!("failed creating kill EventFd pair: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?;
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
let (self_pause_evt, pause_evt) = EventFd::new(EFD_NONBLOCK)
|
||||
.and_then(|e| Ok((e.try_clone()?, e)))
|
||||
.map_err(|e| {
|
||||
error!("failed creating pause EventFd pair: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?;
|
||||
self.pause_evt = Some(self_pause_evt);
|
||||
|
||||
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||
|
||||
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
|
||||
for queue_evt in queue_evts.iter() {
|
||||
// Save the queue EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
|
||||
error!("failed to clone queue EventFd: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?);
|
||||
}
|
||||
self.queue_evts = Some(tmp_queue_evts);
|
||||
|
||||
let config = self.config.lock().unwrap();
|
||||
let mut handler = MemEpollHandler {
|
||||
host_addr: self.host_addr,
|
||||
host_fd: self.host_fd,
|
||||
mem_state: vec![false; config.region_size as usize / config.block_size as usize],
|
||||
config: self.config.clone(),
|
||||
resize: self.resize.try_clone().map_err(|e| {
|
||||
error!("failed to clone resize EventFd: {:?}", e);
|
||||
ActivateError::BadActivate
|
||||
})?,
|
||||
queue: queues.remove(0),
|
||||
mem,
|
||||
interrupt_cb,
|
||||
queue_evt: queue_evts.remove(0),
|
||||
kill_evt,
|
||||
pause_evt,
|
||||
};
|
||||
|
||||
let paused = self.paused.clone();
|
||||
let mut epoll_threads = Vec::new();
|
||||
thread::Builder::new()
|
||||
.name("virtio_mem".to_string())
|
||||
.spawn(move || handler.run(paused))
|
||||
.map(|thread| epoll_threads.push(thread))
|
||||
.map_err(|e| {
|
||||
error!("failed to clone virtio-mem epoll thread: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?;
|
||||
self.epoll_threads = Some(epoll_threads);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<(Arc<dyn VirtioInterrupt>, Vec<EventFd>)> {
|
||||
// We first must resume the virtio thread if it was paused.
|
||||
if self.pause_evt.take().is_some() {
|
||||
self.resume().ok()?;
|
||||
}
|
||||
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
// Return the interrupt and queue EventFDs
|
||||
Some((
|
||||
self.interrupt_cb.take().unwrap(),
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
virtio_pausable!(Mem);
|
||||
impl Snapshottable for Mem {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
}
|
||||
impl Transportable for Mem {}
|
||||
impl Migratable for Mem {}
|
||||
@@ -15,6 +15,7 @@ use super::{
|
||||
ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType, VirtioInterruptType,
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use anyhow::anyhow;
|
||||
use epoll;
|
||||
use libc::EAGAIN;
|
||||
use libc::EFD_NONBLOCK;
|
||||
@@ -30,8 +31,11 @@ use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::vec::Vec;
|
||||
use virtio_bindings::bindings::virtio_net::*;
|
||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
||||
use vm_memory::{ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -234,6 +238,19 @@ impl NetEpollHandler {
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
|
||||
// If there are some already available descriptors on the RX queue,
|
||||
// then we can start the thread while listening onto the TAP.
|
||||
if queues[0].available_descriptors(&self.mem.memory()).unwrap() {
|
||||
epoll::ctl(
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.tap.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_TAP_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
self.rx_tap_listening = true;
|
||||
}
|
||||
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); NET_EVENTS_COUNT];
|
||||
|
||||
'epoll: loop {
|
||||
@@ -275,6 +292,7 @@ impl NetEpollHandler {
|
||||
// Drain pause event
|
||||
let _ = self.pause_evt.read();
|
||||
debug!("PAUSE_EVENT received, pausing virtio-net epoll loop");
|
||||
|
||||
// We loop here to handle spurious park() returns.
|
||||
// Until we have not resumed, the paused boolean will
|
||||
// be true.
|
||||
@@ -293,6 +311,7 @@ impl NetEpollHandler {
|
||||
}
|
||||
|
||||
pub struct Net {
|
||||
id: String,
|
||||
kill_evt: Option<EventFd>,
|
||||
pause_evt: Option<EventFd>,
|
||||
taps: Option<Vec<Tap>>,
|
||||
@@ -307,9 +326,18 @@ pub struct Net {
|
||||
queue_size: Vec<u16>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct NetState {
|
||||
pub avail_features: u64,
|
||||
pub acked_features: u64,
|
||||
pub config: VirtioNetConfig,
|
||||
pub queue_size: Vec<u16>,
|
||||
}
|
||||
|
||||
impl Net {
|
||||
/// Create a new virtio network device with the given TAP interface.
|
||||
pub fn new_with_tap(
|
||||
id: String,
|
||||
taps: Vec<Tap>,
|
||||
guest_mac: Option<MacAddr>,
|
||||
iommu: bool,
|
||||
@@ -339,6 +367,7 @@ impl Net {
|
||||
}
|
||||
|
||||
Ok(Net {
|
||||
id,
|
||||
kill_evt: None,
|
||||
pause_evt: None,
|
||||
taps: Some(taps),
|
||||
@@ -356,7 +385,9 @@ impl Net {
|
||||
|
||||
/// Create a new virtio network device with the given IP address and
|
||||
/// netmask.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
id: String,
|
||||
if_name: Option<&str>,
|
||||
ip_addr: Option<Ipv4Addr>,
|
||||
netmask: Option<Ipv4Addr>,
|
||||
@@ -367,7 +398,25 @@ impl Net {
|
||||
) -> Result<Self> {
|
||||
let taps = open_tap(if_name, ip_addr, netmask, num_queues / 2).map_err(Error::OpenTap)?;
|
||||
|
||||
Self::new_with_tap(taps, guest_mac, iommu, num_queues, queue_size)
|
||||
Self::new_with_tap(id, taps, guest_mac, iommu, num_queues, queue_size)
|
||||
}
|
||||
|
||||
fn state(&self) -> NetState {
|
||||
NetState {
|
||||
avail_features: self.avail_features,
|
||||
acked_features: self.acked_features,
|
||||
config: self.config,
|
||||
queue_size: self.queue_size.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &NetState) -> Result<()> {
|
||||
self.avail_features = state.avail_features;
|
||||
self.acked_features = state.acked_features;
|
||||
self.config = state.config;
|
||||
self.queue_size = state.queue_size.clone();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,5 +616,45 @@ impl VirtioDevice for Net {
|
||||
}
|
||||
|
||||
virtio_ctrl_q_pausable!(Net);
|
||||
impl Snapshotable for Net {}
|
||||
impl Snapshottable for Net {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut net_snapshot = Snapshot::new(self.id.as_str());
|
||||
net_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(net_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(net_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||
let net_state = match serde_json::from_slice(&net_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize NET {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
return self.set_state(&net_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not restore NET state {:?}", e))
|
||||
});
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find NET snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
impl Transportable for Net {}
|
||||
impl Migratable for Net {}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
use super::Error as DeviceError;
|
||||
use super::{DescriptorChain, DeviceEventT, Queue};
|
||||
use net_util::{MacAddr, Tap, TapError};
|
||||
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||
use std::cmp;
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
@@ -47,7 +48,7 @@ const CTRL_QUEUE_EVENT: DeviceEventT = 0;
|
||||
const CTRL_EVENT_COUNT: usize = 3;
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[derive(Copy, Clone, Debug, Default, Deserialize)]
|
||||
pub struct VirtioNetConfig {
|
||||
pub mac: [u8; 6],
|
||||
pub status: u16,
|
||||
@@ -57,6 +58,34 @@ pub struct VirtioNetConfig {
|
||||
pub duplex: u8,
|
||||
}
|
||||
|
||||
// We must explicitly implement Serialize since the structure is packed and
|
||||
// it's unsafe to borrow from a packed structure. And by default, if we derive
|
||||
// Serialize from serde, it will borrow the values from the structure.
|
||||
// That's why this implementation copies each field separately before it
|
||||
// serializes the entire structure field by field.
|
||||
impl Serialize for VirtioNetConfig {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mac = self.mac;
|
||||
let status = self.status;
|
||||
let max_virtqueue_pairs = self.max_virtqueue_pairs;
|
||||
let mtu = self.mtu;
|
||||
let speed = self.speed;
|
||||
let duplex = self.duplex;
|
||||
|
||||
let mut virtio_net_config = serializer.serialize_struct("VirtioNetConfig", 17)?;
|
||||
virtio_net_config.serialize_field("mac", &mac)?;
|
||||
virtio_net_config.serialize_field("status", &status)?;
|
||||
virtio_net_config.serialize_field("max_virtqueue_pairs", &max_virtqueue_pairs)?;
|
||||
virtio_net_config.serialize_field("mtu", &mtu)?;
|
||||
virtio_net_config.serialize_field("speed", &speed)?;
|
||||
virtio_net_config.serialize_field("duplex", &duplex)?;
|
||||
virtio_net_config.end()
|
||||
}
|
||||
}
|
||||
|
||||
// Safe because it only has data and has no implicit padding.
|
||||
unsafe impl ByteValued for VirtioNetConfig {}
|
||||
|
||||
@@ -430,7 +459,15 @@ impl RxVirtio {
|
||||
// Mark that we have at least one pending packet and we need to interrupt the guest.
|
||||
self.deferred_irqs = true;
|
||||
|
||||
write_count >= self.bytes_read
|
||||
// Update the frame_buf buffer.
|
||||
if write_count < self.bytes_read {
|
||||
self.frame_buf.copy_within(write_count..self.bytes_read, 0);
|
||||
self.bytes_read -= write_count;
|
||||
false
|
||||
} else {
|
||||
self.bytes_read = 0;
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
|
||||
VirtioDeviceType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
|
||||
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, UserspaceMapping,
|
||||
VirtioDevice, VirtioDeviceType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::{VirtioInterrupt, VirtioInterruptType};
|
||||
use anyhow::anyhow;
|
||||
use epoll;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||
use std::cmp;
|
||||
use std::fmt::{self, Display};
|
||||
use std::fs::File;
|
||||
@@ -24,10 +26,13 @@ use std::result;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic,
|
||||
GuestMemoryError, GuestMemoryMmap, GuestUsize,
|
||||
GuestMemoryError, GuestMemoryMmap, MmapRegion,
|
||||
};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
@@ -46,13 +51,33 @@ const KILL_EVENT: DeviceEventT = 1;
|
||||
// The device should be paused.
|
||||
const PAUSE_EVENT: DeviceEventT = 2;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default, Deserialize)]
|
||||
#[repr(C, packed)]
|
||||
struct VirtioPmemConfig {
|
||||
start: u64,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
// We must explicitly implement Serialize since the structure is packed and
|
||||
// it's unsafe to borrow from a packed structure. And by default, if we derive
|
||||
// Serialize from serde, it will borrow the values from the structure.
|
||||
// That's why this implementation copies each field separately before it
|
||||
// serializes the entire structure field by field.
|
||||
impl Serialize for VirtioPmemConfig {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let start = self.start;
|
||||
let size = self.size;
|
||||
|
||||
let mut virtio_pmem_config = serializer.serialize_struct("VirtioPmemConfig", 16)?;
|
||||
virtio_pmem_config.serialize_field("start", &start)?;
|
||||
virtio_pmem_config.serialize_field("size", &size)?;
|
||||
virtio_pmem_config.end()
|
||||
}
|
||||
}
|
||||
|
||||
// Safe because it only has data and has no implicit padding.
|
||||
unsafe impl ByteValued for VirtioPmemConfig {}
|
||||
|
||||
@@ -312,6 +337,7 @@ impl PmemEpollHandler {
|
||||
}
|
||||
|
||||
pub struct Pmem {
|
||||
id: String,
|
||||
kill_evt: Option<EventFd>,
|
||||
pause_evt: Option<EventFd>,
|
||||
disk: Option<File>,
|
||||
@@ -322,13 +348,32 @@ pub struct Pmem {
|
||||
interrupt_cb: Option<Arc<dyn VirtioInterrupt>>,
|
||||
epoll_threads: Option<Vec<thread::JoinHandle<result::Result<(), DeviceError>>>>,
|
||||
paused: Arc<AtomicBool>,
|
||||
mapping: UserspaceMapping,
|
||||
|
||||
// Hold ownership of the memory that is allocated for the device
|
||||
// which will be automatically dropped when the device is dropped
|
||||
_region: MmapRegion,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct PmemState {
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
config: VirtioPmemConfig,
|
||||
}
|
||||
|
||||
impl Pmem {
|
||||
pub fn new(disk: File, addr: GuestAddress, size: GuestUsize, iommu: bool) -> io::Result<Pmem> {
|
||||
pub fn new(
|
||||
id: String,
|
||||
disk: File,
|
||||
addr: GuestAddress,
|
||||
mapping: UserspaceMapping,
|
||||
_region: MmapRegion,
|
||||
iommu: bool,
|
||||
) -> io::Result<Pmem> {
|
||||
let config = VirtioPmemConfig {
|
||||
start: addr.raw_value().to_le(),
|
||||
size: size.to_le(),
|
||||
size: (_region.size() as u64).to_le(),
|
||||
};
|
||||
|
||||
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
|
||||
@@ -338,6 +383,7 @@ impl Pmem {
|
||||
}
|
||||
|
||||
Ok(Pmem {
|
||||
id,
|
||||
kill_evt: None,
|
||||
pause_evt: None,
|
||||
disk: Some(disk),
|
||||
@@ -348,8 +394,26 @@ impl Pmem {
|
||||
interrupt_cb: None,
|
||||
epoll_threads: None,
|
||||
paused: Arc::new(AtomicBool::new(false)),
|
||||
mapping,
|
||||
_region,
|
||||
})
|
||||
}
|
||||
|
||||
fn state(&self) -> PmemState {
|
||||
PmemState {
|
||||
avail_features: self.avail_features,
|
||||
acked_features: self.acked_features,
|
||||
config: self.config,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &PmemState) -> io::Result<()> {
|
||||
self.avail_features = state.avail_features;
|
||||
self.acked_features = state.acked_features;
|
||||
self.config = state.config;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Pmem {
|
||||
@@ -503,8 +567,53 @@ impl VirtioDevice for Pmem {
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
fn userspace_mappings(&self) -> Vec<UserspaceMapping> {
|
||||
vec![self.mapping.clone()]
|
||||
}
|
||||
}
|
||||
|
||||
virtio_pausable!(Pmem);
|
||||
impl Snapshotable for Pmem {}
|
||||
impl Snapshottable for Pmem {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut pmem_snapshot = Snapshot::new(self.id.as_str());
|
||||
pmem_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(pmem_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(pmem_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||
let pmem_state = match serde_json::from_slice(&pmem_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize PMEM {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
return self.set_state(&pmem_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not restore PMEM state {:?}", e))
|
||||
});
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find PMEM snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Transportable for Pmem {}
|
||||
impl Migratable for Pmem {}
|
||||
|
||||
@@ -17,7 +17,8 @@ use std::sync::Arc;
|
||||
|
||||
use crate::device::VirtioIommuRemapping;
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap,
|
||||
GuestUsize,
|
||||
};
|
||||
|
||||
pub(super) const VIRTQ_DESC_F_NEXT: u16 = 0x1;
|
||||
@@ -29,6 +30,8 @@ pub enum Error {
|
||||
GuestMemoryError,
|
||||
InvalidIndirectDescriptor,
|
||||
InvalidChain,
|
||||
InvalidOffset(u64),
|
||||
InvalidRingIndexFromMemory(GuestMemoryError),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
@@ -39,6 +42,8 @@ impl Display for Error {
|
||||
GuestMemoryError => write!(f, "error accessing guest memory"),
|
||||
InvalidChain => write!(f, "invalid descriptor chain"),
|
||||
InvalidIndirectDescriptor => write!(f, "invalid indirect descriptor"),
|
||||
InvalidOffset(o) => write!(f, "invalid offset {}", o),
|
||||
InvalidRingIndexFromMemory(e) => write!(f, "invalid ring index from memory: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -383,11 +388,15 @@ impl<'a, 'b> Iterator for AvailIter<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "GuestAddress")]
|
||||
struct GuestAddressDef(pub u64);
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
/// A virtio queue's parameters.
|
||||
pub struct Queue {
|
||||
/// The maximal size in elements offered by the device
|
||||
max_size: u16,
|
||||
pub max_size: u16,
|
||||
|
||||
/// The queue size in elements the driver selected
|
||||
pub size: u16,
|
||||
@@ -398,18 +407,22 @@ pub struct Queue {
|
||||
/// Interrupt vector index of the queue
|
||||
pub vector: u16,
|
||||
|
||||
#[serde(with = "GuestAddressDef")]
|
||||
/// Guest physical address of the descriptor table
|
||||
pub desc_table: GuestAddress,
|
||||
|
||||
#[serde(with = "GuestAddressDef")]
|
||||
/// Guest physical address of the available ring
|
||||
pub avail_ring: GuestAddress,
|
||||
|
||||
#[serde(with = "GuestAddressDef")]
|
||||
/// Guest physical address of the used ring
|
||||
pub used_ring: GuestAddress,
|
||||
|
||||
pub next_avail: Wrapping<u16>,
|
||||
pub next_used: Wrapping<u16>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
|
||||
}
|
||||
|
||||
@@ -640,6 +653,29 @@ impl Queue {
|
||||
pub fn go_to_previous_position(&mut self) {
|
||||
self.next_avail -= Wrapping(1);
|
||||
}
|
||||
|
||||
/// Get ring's index from memory.
|
||||
fn index_from_memory(&self, ring: GuestAddress, mem: &GuestMemoryMmap) -> Result<u16, Error> {
|
||||
mem.read_obj::<u16>(
|
||||
mem.checked_offset(ring, 2)
|
||||
.ok_or_else(|| Error::InvalidOffset(ring.raw_value() + 2))?,
|
||||
)
|
||||
.map_err(Error::InvalidRingIndexFromMemory)
|
||||
}
|
||||
|
||||
/// Get latest index from available ring.
|
||||
pub fn avail_index_from_memory(&self, mem: &GuestMemoryMmap) -> Result<u16, Error> {
|
||||
self.index_from_memory(self.avail_ring, mem)
|
||||
}
|
||||
|
||||
/// Get latest index from used ring.
|
||||
pub fn used_index_from_memory(&self, mem: &GuestMemoryMmap) -> Result<u16, Error> {
|
||||
self.index_from_memory(self.used_ring, mem)
|
||||
}
|
||||
|
||||
pub fn available_descriptors(&self, mem: &GuestMemoryMmap) -> Result<bool, Error> {
|
||||
Ok(self.used_index_from_memory(mem)? < self.avail_index_from_memory(mem)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -8,6 +8,7 @@ use super::{
|
||||
VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::{VirtioInterrupt, VirtioInterruptType};
|
||||
use anyhow::anyhow;
|
||||
use epoll;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std;
|
||||
@@ -18,8 +19,11 @@ use std::result;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
||||
use vm_memory::{Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
@@ -178,6 +182,7 @@ impl RngEpollHandler {
|
||||
|
||||
/// Virtio device for exposing entropy to the guest OS through virtio.
|
||||
pub struct Rng {
|
||||
id: String,
|
||||
kill_evt: Option<EventFd>,
|
||||
pause_evt: Option<EventFd>,
|
||||
random_file: Option<File>,
|
||||
@@ -189,9 +194,16 @@ pub struct Rng {
|
||||
paused: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct RngState {
|
||||
pub avail_features: u64,
|
||||
pub acked_features: u64,
|
||||
pub paused: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Rng {
|
||||
/// Create a new virtio rng device that gets random data from /dev/urandom.
|
||||
pub fn new(path: &str, iommu: bool) -> io::Result<Rng> {
|
||||
pub fn new(id: String, path: &str, iommu: bool) -> io::Result<Rng> {
|
||||
let random_file = File::open(path)?;
|
||||
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
|
||||
|
||||
@@ -200,6 +212,7 @@ impl Rng {
|
||||
}
|
||||
|
||||
Ok(Rng {
|
||||
id,
|
||||
kill_evt: None,
|
||||
pause_evt: None,
|
||||
random_file: Some(random_file),
|
||||
@@ -211,6 +224,22 @@ impl Rng {
|
||||
paused: Arc::new(AtomicBool::new(false)),
|
||||
})
|
||||
}
|
||||
|
||||
fn state(&self) -> RngState {
|
||||
RngState {
|
||||
avail_features: self.avail_features,
|
||||
acked_features: self.acked_features,
|
||||
paused: self.paused.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &RngState) -> io::Result<()> {
|
||||
self.avail_features = state.avail_features;
|
||||
self.acked_features = state.acked_features;
|
||||
self.paused = state.paused.clone();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Rng {
|
||||
@@ -357,5 +386,46 @@ impl VirtioDevice for Rng {
|
||||
}
|
||||
|
||||
virtio_pausable!(Rng);
|
||||
impl Snapshotable for Rng {}
|
||||
impl Snapshottable for Rng {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut rng_snapshot = Snapshot::new(self.id.as_str());
|
||||
rng_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(rng_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(rng_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||
let rng_state = match serde_json::from_slice(&rng_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize RNG {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
return self.set_state(&rng_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not restore RNG state {:?}", e))
|
||||
});
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find RNG snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Transportable for Rng {}
|
||||
impl Migratable for Rng {}
|
||||
|
||||
@@ -8,15 +8,20 @@ use crate::{
|
||||
DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK, DEVICE_INIT,
|
||||
INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use devices::BusDevice;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std::num::Wrapping;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
||||
use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
use vmm_sys_util::{errno::Result, eventfd::EventFd};
|
||||
|
||||
const VENDOR_ID: u32 = 0;
|
||||
@@ -24,6 +29,12 @@ const VENDOR_ID: u32 = 0;
|
||||
const MMIO_MAGIC_VALUE: u32 = 0x7472_6976;
|
||||
const MMIO_VERSION: u32 = 2;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
/// Failed to retrieve queue ring's index.
|
||||
QueueRingIndex(crate::queue::Error),
|
||||
}
|
||||
|
||||
pub struct VirtioInterruptIntx {
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
@@ -58,6 +69,18 @@ impl VirtioInterrupt for VirtioInterruptIntx {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct VirtioMmioDeviceState {
|
||||
device_activated: bool,
|
||||
features_select: u32,
|
||||
acked_features_select: u32,
|
||||
queue_select: u32,
|
||||
interrupt_status: usize,
|
||||
driver_status: u32,
|
||||
queues: Vec<Queue>,
|
||||
shm_region_select: u32,
|
||||
}
|
||||
|
||||
/// Implements the
|
||||
/// [MMIO](http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html#x1-1090002)
|
||||
/// transport for virtio devices.
|
||||
@@ -73,6 +96,7 @@ impl VirtioInterrupt for VirtioInterruptIntx {
|
||||
/// Typically one page (4096 bytes) of MMIO address space is sufficient to handle this transport
|
||||
/// and inner virtio device.
|
||||
pub struct MmioDevice {
|
||||
id: String,
|
||||
device: Arc<Mutex<dyn VirtioDevice>>,
|
||||
device_activated: bool,
|
||||
|
||||
@@ -92,6 +116,7 @@ pub struct MmioDevice {
|
||||
impl MmioDevice {
|
||||
/// Constructs a new MMIO transport for the given virtio device.
|
||||
pub fn new(
|
||||
id: String,
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
device: Arc<Mutex<dyn VirtioDevice>>,
|
||||
) -> Result<MmioDevice> {
|
||||
@@ -107,6 +132,7 @@ impl MmioDevice {
|
||||
.map(|&s| Queue::new(s))
|
||||
.collect();
|
||||
Ok(MmioDevice {
|
||||
id,
|
||||
device,
|
||||
device_activated: false,
|
||||
features_select: 0,
|
||||
@@ -123,6 +149,51 @@ impl MmioDevice {
|
||||
})
|
||||
}
|
||||
|
||||
fn state(&self) -> VirtioMmioDeviceState {
|
||||
VirtioMmioDeviceState {
|
||||
device_activated: self.device_activated,
|
||||
features_select: self.features_select,
|
||||
acked_features_select: self.acked_features_select,
|
||||
queue_select: self.queue_select,
|
||||
interrupt_status: self.interrupt_status.load(Ordering::SeqCst),
|
||||
driver_status: self.driver_status,
|
||||
queues: self.queues.clone(),
|
||||
shm_region_select: self.shm_region_select,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &VirtioMmioDeviceState) -> std::result::Result<(), Error> {
|
||||
self.device_activated = state.device_activated;
|
||||
self.features_select = state.features_select;
|
||||
self.acked_features_select = state.acked_features_select;
|
||||
self.queue_select = state.queue_select;
|
||||
self.interrupt_status
|
||||
.store(state.interrupt_status, Ordering::SeqCst);
|
||||
self.driver_status = state.driver_status;
|
||||
self.queues = state.queues.clone();
|
||||
|
||||
// Update virtqueues indexes for both available and used rings.
|
||||
if let Some(mem) = self.mem.as_ref() {
|
||||
let mem = mem.memory();
|
||||
for queue in self.queues.iter_mut() {
|
||||
queue.next_avail = Wrapping(
|
||||
queue
|
||||
.used_index_from_memory(&mem)
|
||||
.map_err(Error::QueueRingIndex)?,
|
||||
);
|
||||
queue.next_used = Wrapping(
|
||||
queue
|
||||
.used_index_from_memory(&mem)
|
||||
.map_err(Error::QueueRingIndex)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.shm_region_select = state.shm_region_select;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the list of queue events that must be triggered whenever the VM writes to
|
||||
/// `virtio::NOTIFY_REG_OFFSET` past the MMIO base. Each event must be triggered when the
|
||||
/// value being written equals the index of the event in this list.
|
||||
@@ -354,5 +425,81 @@ impl Pausable for MmioDevice {
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshotable for MmioDevice {}
|
||||
impl Snapshottable for MmioDevice {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut virtio_mmio_dev_snapshot = Snapshot::new(self.id.as_str());
|
||||
virtio_mmio_dev_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(virtio_mmio_dev_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(virtio_mmio_dev_section) =
|
||||
snapshot.snapshot_data.get(&format!("{}-section", self.id))
|
||||
{
|
||||
let virtio_mmio_dev_state =
|
||||
match serde_json::from_slice(&virtio_mmio_dev_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize VIRTIO_MMIO_DEVICE {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
// First restore the status of the virtqueues.
|
||||
self.set_state(&virtio_mmio_dev_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!(
|
||||
"Could not restore VIRTIO_MMIO_DEVICE state {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
// Then we can activate the device, as we know at this point that
|
||||
// the virtqueues are in the right state and the device is ready
|
||||
// to be activated, which will spawn each virtio worker thread.
|
||||
if self.device_activated && self.is_driver_ready() && self.are_queues_valid() {
|
||||
if let Some(interrupt_cb) = self.interrupt_cb.take() {
|
||||
if self.mem.is_some() {
|
||||
let mem = self.mem.as_ref().unwrap().clone();
|
||||
self.device
|
||||
.lock()
|
||||
.unwrap()
|
||||
.activate(
|
||||
mem,
|
||||
interrupt_cb,
|
||||
self.queues.clone(),
|
||||
self.queue_evts.split_off(0),
|
||||
)
|
||||
.map_err(|e| {
|
||||
MigratableError::Restore(anyhow!(
|
||||
"Failed activating the device: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find VIRTIO_MMIO_DEVICE snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Transportable for MmioDevice {}
|
||||
impl Migratable for MmioDevice {}
|
||||
|
||||
@@ -37,11 +37,11 @@ use vm_allocator::SystemAllocator;
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
|
||||
};
|
||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
||||
use vm_memory::{
|
||||
Address, ByteValued, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap,
|
||||
GuestUsize, Le32,
|
||||
};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
|
||||
use vmm_sys_util::{errno::Result, eventfd::EventFd};
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
@@ -259,6 +259,8 @@ const VIRTIO_PCI_VENDOR_ID: u16 = 0x1af4;
|
||||
const VIRTIO_PCI_DEVICE_ID_BASE: u16 = 0x1040; // Add to device type to get device ID.
|
||||
|
||||
pub struct VirtioPciDevice {
|
||||
id: String,
|
||||
|
||||
// PCI configuration registers.
|
||||
configuration: PciConfiguration,
|
||||
|
||||
@@ -300,11 +302,15 @@ pub struct VirtioPciDevice {
|
||||
// needed when the guest tries to early access the virtio configuration of
|
||||
// a device.
|
||||
cap_pci_cfg_info: VirtioPciCfgCapInfo,
|
||||
|
||||
// Details of bar regions to free
|
||||
bar_regions: Vec<(GuestAddress, GuestUsize, PciBarRegionType)>,
|
||||
}
|
||||
|
||||
impl VirtioPciDevice {
|
||||
/// Constructs a new PCI transport for the given virtio device.
|
||||
pub fn new(
|
||||
id: String,
|
||||
memory: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
device: Arc<Mutex<dyn VirtioDevice>>,
|
||||
msix_num: u16,
|
||||
@@ -370,6 +376,7 @@ impl VirtioPciDevice {
|
||||
let configuration = PciConfiguration::new(
|
||||
VIRTIO_PCI_VENDOR_ID,
|
||||
pci_device_id,
|
||||
0x1, // For modern virtio-PCI devices
|
||||
class,
|
||||
subclass,
|
||||
None,
|
||||
@@ -380,6 +387,7 @@ impl VirtioPciDevice {
|
||||
);
|
||||
|
||||
let mut virtio_pci_device = VirtioPciDevice {
|
||||
id,
|
||||
configuration,
|
||||
common_config: VirtioPciCommonConfig {
|
||||
driver_status: 0,
|
||||
@@ -402,6 +410,7 @@ impl VirtioPciDevice {
|
||||
use_64bit_bar,
|
||||
interrupt_source_group,
|
||||
cap_pci_cfg_info: VirtioPciCfgCapInfo::default(),
|
||||
bar_regions: vec![],
|
||||
};
|
||||
|
||||
if let Some(msix_config) = &virtio_pci_device.msix_config {
|
||||
@@ -560,6 +569,10 @@ impl VirtioPciDevice {
|
||||
self.write_bar(0, bar_offset as u64, data)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn virtio_device(&self) -> Arc<Mutex<dyn VirtioDevice>> {
|
||||
self.device.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioTransport for VirtioPciDevice {
|
||||
@@ -721,6 +734,8 @@ impl PciDevice for VirtioPciDevice {
|
||||
ranges.push((addr, CAPABILITY_BAR_SIZE, region_type));
|
||||
(addr, region_type)
|
||||
};
|
||||
self.bar_regions
|
||||
.push((virtio_pci_bar_addr, CAPABILITY_BAR_SIZE, region_type));
|
||||
|
||||
let config = PciBarConfiguration::default()
|
||||
.set_register_index(0)
|
||||
@@ -746,6 +761,11 @@ impl PciDevice for VirtioPciDevice {
|
||||
PciDeviceError::IoRegistrationFailed(shm_list.addr.raw_value(), e)
|
||||
})? as u8;
|
||||
|
||||
let region_type = PciBarRegionType::Memory64BitRegion;
|
||||
ranges.push((shm_list.addr, shm_list.len, region_type));
|
||||
self.bar_regions
|
||||
.push((shm_list.addr, shm_list.len, region_type));
|
||||
|
||||
for (idx, shm) in shm_list.region_list.iter().enumerate() {
|
||||
let shm_cap = VirtioPciCap64::new(
|
||||
PciCapabilityType::SharedMemoryConfig,
|
||||
@@ -763,6 +783,36 @@ impl PciDevice for VirtioPciDevice {
|
||||
Ok(ranges)
|
||||
}
|
||||
|
||||
fn free_bars(
|
||||
&mut self,
|
||||
allocator: &mut SystemAllocator,
|
||||
) -> std::result::Result<(), PciDeviceError> {
|
||||
for (addr, length, type_) in self.bar_regions.drain(..) {
|
||||
match type_ {
|
||||
PciBarRegionType::Memory32BitRegion => {
|
||||
allocator.free_mmio_hole_addresses(addr, length);
|
||||
}
|
||||
PciBarRegionType::Memory64BitRegion => {
|
||||
allocator.free_mmio_addresses(addr, length);
|
||||
}
|
||||
_ => error!("Unexpected PCI bar type"),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), std::io::Error> {
|
||||
// We only update our idea of the bar in order to support free_bars() above.
|
||||
// The majority of the reallocation is done inside DeviceManager.
|
||||
for (addr, _, _) in self.bar_regions.iter_mut() {
|
||||
if (*addr).0 == old_base {
|
||||
*addr = GuestAddress(new_base);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_bar(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
match offset {
|
||||
o if o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE => self.common_config.read(
|
||||
@@ -916,5 +966,10 @@ impl Pausable for VirtioPciDevice {
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshotable for VirtioPciDevice {}
|
||||
impl Snapshottable for VirtioPciDevice {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
}
|
||||
impl Transportable for VirtioPciDevice {}
|
||||
impl Migratable for VirtioPciDevice {}
|
||||
|
||||
@@ -26,14 +26,15 @@ use vhost_rs::vhost_user::{Master, VhostUserMaster, VhostUserMasterReqHandler};
|
||||
use vhost_rs::VhostBackend;
|
||||
use virtio_bindings::bindings::virtio_blk::*;
|
||||
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
||||
use vm_memory::{ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
struct SlaveReqHandler {}
|
||||
impl VhostUserMasterReqHandler for SlaveReqHandler {}
|
||||
|
||||
pub struct Blk {
|
||||
id: String,
|
||||
vhost_user_blk: Master,
|
||||
kill_evt: Option<EventFd>,
|
||||
pause_evt: Option<EventFd>,
|
||||
@@ -49,7 +50,7 @@ pub struct Blk {
|
||||
|
||||
impl Blk {
|
||||
/// Create a new vhost-user-blk device
|
||||
pub fn new(wce: bool, vu_cfg: VhostUserConfig) -> Result<Blk> {
|
||||
pub fn new(id: String, wce: bool, vu_cfg: VhostUserConfig) -> Result<Blk> {
|
||||
let mut vhost_user_blk = Master::connect(&vu_cfg.sock, vu_cfg.num_queues as u64)
|
||||
.map_err(Error::VhostUserCreateMaster)?;
|
||||
|
||||
@@ -140,6 +141,7 @@ impl Blk {
|
||||
}
|
||||
|
||||
Ok(Blk {
|
||||
id,
|
||||
vhost_user_blk,
|
||||
kill_evt: None,
|
||||
pause_evt: None,
|
||||
@@ -321,8 +323,17 @@ impl VirtioDevice for Blk {
|
||||
fn shutdown(&mut self) {
|
||||
let _ = unsafe { libc::close(self.vhost_user_blk.as_raw_fd()) };
|
||||
}
|
||||
|
||||
fn update_memory(&mut self, mem: &GuestMemoryMmap) -> std::result::Result<(), crate::Error> {
|
||||
update_mem_table(&mut self.vhost_user_blk, mem).map_err(crate::Error::VhostUserUpdateMemory)
|
||||
}
|
||||
}
|
||||
|
||||
virtio_pausable!(Blk);
|
||||
impl Snapshotable for Blk {}
|
||||
impl Snapshottable for Blk {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
}
|
||||
impl Transportable for Blk {}
|
||||
impl Migratable for Blk {}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::vu_common_ctrl::{reset_vhost_user, setup_vhost_user};
|
||||
use super::vu_common_ctrl::{reset_vhost_user, setup_vhost_user, update_mem_table};
|
||||
use super::Error as DeviceError;
|
||||
use super::{Error, Result};
|
||||
use crate::vhost_user::handler::{VhostUserEpollConfig, VhostUserEpollHandler};
|
||||
use crate::{
|
||||
ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType, VirtioInterrupt,
|
||||
VirtioSharedMemoryList, VIRTIO_F_VERSION_1,
|
||||
ActivateError, ActivateResult, Queue, UserspaceMapping, VirtioDevice, VirtioDeviceType,
|
||||
VirtioInterrupt, VirtioSharedMemoryList, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use libc::{self, c_void, off64_t, pread64, pwrite64, EFD_NONBLOCK};
|
||||
use std::cmp;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -26,10 +26,11 @@ use vhost_rs::vhost_user::{
|
||||
HandlerResult, Master, MasterReqHandler, VhostUserMaster, VhostUserMasterReqHandler,
|
||||
};
|
||||
use vhost_rs::VhostBackend;
|
||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
||||
use vm_memory::{
|
||||
Address, ByteValued, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap,
|
||||
Address, ByteValued, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
||||
GuestMemoryMmap, MmapRegion,
|
||||
};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const NUM_QUEUE_OFFSET: usize = 1;
|
||||
@@ -38,32 +39,48 @@ struct SlaveReqHandler {
|
||||
cache_offset: GuestAddress,
|
||||
cache_size: u64,
|
||||
mmap_cache_addr: u64,
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
}
|
||||
|
||||
impl SlaveReqHandler {
|
||||
// Make sure request is within cache range
|
||||
fn is_req_valid(&self, offset: u64, len: u64) -> bool {
|
||||
let end = match offset.checked_add(len) {
|
||||
Some(n) => n,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
!(offset >= self.cache_size || end > self.cache_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserMasterReqHandler for SlaveReqHandler {
|
||||
fn handle_config_change(&mut self) -> HandlerResult<()> {
|
||||
fn handle_config_change(&mut self) -> HandlerResult<u64> {
|
||||
debug!("handle_config_change");
|
||||
Ok(())
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn fs_slave_map(&mut self, fs: &VhostUserFSSlaveMsg, fd: RawFd) -> HandlerResult<()> {
|
||||
fn fs_slave_map(&mut self, fs: &VhostUserFSSlaveMsg, fd: RawFd) -> HandlerResult<u64> {
|
||||
debug!("fs_slave_map");
|
||||
|
||||
for i in 0..VHOST_USER_FS_SLAVE_ENTRIES {
|
||||
let offset = fs.cache_offset[i];
|
||||
let len = fs.len[i];
|
||||
|
||||
// Ignore if the length is 0.
|
||||
if fs.len[i] == 0 {
|
||||
if len == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if fs.cache_offset[i] > self.cache_size {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "Wrong offset"));
|
||||
if !self.is_req_valid(offset, len) {
|
||||
return Err(io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
let addr = self.mmap_cache_addr + fs.cache_offset[i];
|
||||
let addr = self.mmap_cache_addr + offset;
|
||||
let ret = unsafe {
|
||||
libc::mmap(
|
||||
addr as *mut libc::c_void,
|
||||
fs.len[i] as usize,
|
||||
len as usize,
|
||||
fs.flags[i].bits() as i32,
|
||||
libc::MAP_SHARED | libc::MAP_FIXED,
|
||||
fd,
|
||||
@@ -80,13 +97,14 @@ impl VhostUserMasterReqHandler for SlaveReqHandler {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn fs_slave_unmap(&mut self, fs: &VhostUserFSSlaveMsg) -> HandlerResult<()> {
|
||||
fn fs_slave_unmap(&mut self, fs: &VhostUserFSSlaveMsg) -> HandlerResult<u64> {
|
||||
debug!("fs_slave_unmap");
|
||||
|
||||
for i in 0..VHOST_USER_FS_SLAVE_ENTRIES {
|
||||
let offset = fs.cache_offset[i];
|
||||
let mut len = fs.len[i];
|
||||
|
||||
// Ignore if the length is 0.
|
||||
@@ -100,11 +118,11 @@ impl VhostUserMasterReqHandler for SlaveReqHandler {
|
||||
len = self.cache_size;
|
||||
}
|
||||
|
||||
if fs.cache_offset[i] > self.cache_size {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "Wrong offset"));
|
||||
if !self.is_req_valid(offset, len) {
|
||||
return Err(io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
let addr = self.mmap_cache_addr + fs.cache_offset[i];
|
||||
let addr = self.mmap_cache_addr + offset;
|
||||
let ret = unsafe {
|
||||
libc::mmap(
|
||||
addr as *mut libc::c_void,
|
||||
@@ -120,37 +138,40 @@ impl VhostUserMasterReqHandler for SlaveReqHandler {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn fs_slave_sync(&mut self, fs: &VhostUserFSSlaveMsg) -> HandlerResult<()> {
|
||||
fn fs_slave_sync(&mut self, fs: &VhostUserFSSlaveMsg) -> HandlerResult<u64> {
|
||||
debug!("fs_slave_sync");
|
||||
|
||||
for i in 0..VHOST_USER_FS_SLAVE_ENTRIES {
|
||||
let offset = fs.cache_offset[i];
|
||||
let len = fs.len[i];
|
||||
|
||||
// Ignore if the length is 0.
|
||||
if fs.len[i] == 0 {
|
||||
if len == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if fs.cache_offset[i] > self.cache_size {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "Wrong offset"));
|
||||
if !self.is_req_valid(offset, len) {
|
||||
return Err(io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
let addr = self.mmap_cache_addr + fs.cache_offset[i];
|
||||
let ret = unsafe {
|
||||
libc::msync(addr as *mut libc::c_void, fs.len[i] as usize, libc::MS_SYNC)
|
||||
};
|
||||
let addr = self.mmap_cache_addr + offset;
|
||||
let ret =
|
||||
unsafe { libc::msync(addr as *mut libc::c_void, len as usize, libc::MS_SYNC) };
|
||||
if ret == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn fs_slave_io(&mut self, fs: &VhostUserFSSlaveMsg, fd: RawFd) -> HandlerResult<()> {
|
||||
fn fs_slave_io(&mut self, fs: &VhostUserFSSlaveMsg, fd: RawFd) -> HandlerResult<u64> {
|
||||
debug!("fs_slave_io");
|
||||
|
||||
let mut done: u64 = 0;
|
||||
for i in 0..VHOST_USER_FS_SLAVE_ENTRIES {
|
||||
// Ignore if the length is 0.
|
||||
if fs.len[i] == 0 {
|
||||
@@ -160,19 +181,34 @@ impl VhostUserMasterReqHandler for SlaveReqHandler {
|
||||
let mut foffset = fs.fd_offset[i];
|
||||
let mut len = fs.len[i] as usize;
|
||||
let gpa = fs.cache_offset[i];
|
||||
if gpa < self.cache_offset.raw_value()
|
||||
|| gpa >= self.cache_offset.raw_value() + self.cache_size
|
||||
{
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"gpa is out of cache range",
|
||||
));
|
||||
}
|
||||
let cache_end = self.cache_offset.raw_value() + self.cache_size;
|
||||
let efault = libc::EFAULT;
|
||||
|
||||
let offset = gpa
|
||||
.checked_sub(self.cache_offset.raw_value())
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "gpa is out of cache range"))?;
|
||||
let mut ptr = self.mmap_cache_addr + offset;
|
||||
let mut ptr = if gpa >= self.cache_offset.raw_value() && gpa < cache_end {
|
||||
let offset = gpa
|
||||
.checked_sub(self.cache_offset.raw_value())
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(efault))?;
|
||||
let end = gpa
|
||||
.checked_add(fs.len[i])
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(efault))?;
|
||||
|
||||
if end >= cache_end {
|
||||
return Err(io::Error::from_raw_os_error(efault));
|
||||
}
|
||||
|
||||
self.mmap_cache_addr + offset
|
||||
} else {
|
||||
self.mem
|
||||
.memory()
|
||||
.get_host_address(GuestAddress(gpa))
|
||||
.map_err(|e| {
|
||||
error!(
|
||||
"Failed to find RAM region associated with guest physical address 0x{:x}: {:?}",
|
||||
gpa, e
|
||||
);
|
||||
io::Error::from_raw_os_error(efault)
|
||||
})? as u64
|
||||
};
|
||||
|
||||
while len > 0 {
|
||||
let ret = if (fs.flags[i] & VhostUserFSSlaveMsgFlags::MAP_W)
|
||||
@@ -199,6 +235,7 @@ impl VhostUserMasterReqHandler for SlaveReqHandler {
|
||||
len -= ret as usize;
|
||||
foffset += ret as u64;
|
||||
ptr += ret as u64;
|
||||
done += ret as u64;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +244,7 @@ impl VhostUserMasterReqHandler for SlaveReqHandler {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(done)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +267,7 @@ impl Default for VirtioFsConfig {
|
||||
unsafe impl ByteValued for VirtioFsConfig {}
|
||||
|
||||
pub struct Fs {
|
||||
id: String,
|
||||
vu: Master,
|
||||
queue_sizes: Vec<u16>,
|
||||
avail_features: u64,
|
||||
@@ -237,7 +275,9 @@ pub struct Fs {
|
||||
config: VirtioFsConfig,
|
||||
kill_evt: Option<EventFd>,
|
||||
pause_evt: Option<EventFd>,
|
||||
cache: Option<(VirtioSharedMemoryList, u64)>,
|
||||
// Hold ownership of the memory that is allocated for the device
|
||||
// which will be automatically dropped when the device is dropped
|
||||
cache: Option<(VirtioSharedMemoryList, MmapRegion)>,
|
||||
slave_req_support: bool,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<dyn VirtioInterrupt>>,
|
||||
@@ -248,11 +288,12 @@ pub struct Fs {
|
||||
impl Fs {
|
||||
/// Create a new virtio-fs device.
|
||||
pub fn new(
|
||||
id: String,
|
||||
path: &str,
|
||||
tag: &str,
|
||||
req_num_queues: usize,
|
||||
queue_size: u16,
|
||||
cache: Option<(VirtioSharedMemoryList, u64)>,
|
||||
cache: Option<(VirtioSharedMemoryList, MmapRegion)>,
|
||||
) -> Result<Fs> {
|
||||
let mut slave_req_support = false;
|
||||
|
||||
@@ -313,6 +354,7 @@ impl Fs {
|
||||
config.num_request_queues = req_num_queues as u32;
|
||||
|
||||
Ok(Fs {
|
||||
id,
|
||||
vu: master,
|
||||
queue_sizes: vec![queue_size; num_queues],
|
||||
avail_features,
|
||||
@@ -450,11 +492,12 @@ impl VirtioDevice for Fs {
|
||||
|
||||
// Initialize slave communication.
|
||||
let slave_req_handler = if self.slave_req_support {
|
||||
if let Some(cache) = self.cache.clone() {
|
||||
if let Some(cache) = self.cache.as_ref() {
|
||||
let vu_master_req_handler = Arc::new(Mutex::new(SlaveReqHandler {
|
||||
cache_offset: cache.0.addr,
|
||||
cache_size: cache.0.len,
|
||||
mmap_cache_addr: cache.1,
|
||||
mmap_cache_addr: cache.0.host_addr,
|
||||
mem,
|
||||
}));
|
||||
|
||||
let req_handler = MasterReqHandler::new(vu_master_req_handler).map_err(|e| {
|
||||
@@ -520,15 +563,55 @@ impl VirtioDevice for Fs {
|
||||
))
|
||||
}
|
||||
|
||||
fn shutdown(&mut self) {
|
||||
let _ = unsafe { libc::close(self.vu.as_raw_fd()) };
|
||||
}
|
||||
|
||||
fn get_shm_regions(&self) -> Option<VirtioSharedMemoryList> {
|
||||
if let Some(cache) = self.cache.clone() {
|
||||
Some(cache.0)
|
||||
if let Some(cache) = self.cache.as_ref() {
|
||||
Some(cache.0.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn set_shm_regions(
|
||||
&mut self,
|
||||
shm_regions: VirtioSharedMemoryList,
|
||||
) -> std::result::Result<(), crate::Error> {
|
||||
if let Some(mut cache) = self.cache.as_mut() {
|
||||
cache.0 = shm_regions;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(crate::Error::SetShmRegionsNotSupported)
|
||||
}
|
||||
}
|
||||
|
||||
fn update_memory(&mut self, mem: &GuestMemoryMmap) -> std::result::Result<(), crate::Error> {
|
||||
update_mem_table(&mut self.vu, mem).map_err(crate::Error::VhostUserUpdateMemory)
|
||||
}
|
||||
|
||||
fn userspace_mappings(&self) -> Vec<UserspaceMapping> {
|
||||
let mut mappings = Vec::new();
|
||||
if let Some(cache) = self.cache.as_ref() {
|
||||
mappings.push(UserspaceMapping {
|
||||
host_addr: cache.0.host_addr,
|
||||
mem_slot: cache.0.mem_slot,
|
||||
addr: cache.0.addr,
|
||||
len: cache.0.len,
|
||||
mergeable: false,
|
||||
})
|
||||
}
|
||||
|
||||
mappings
|
||||
}
|
||||
}
|
||||
|
||||
virtio_pausable!(Fs);
|
||||
impl Snapshotable for Fs {}
|
||||
impl Snapshottable for Fs {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
}
|
||||
impl Transportable for Fs {}
|
||||
impl Migratable for Fs {}
|
||||
|
||||
@@ -27,8 +27,8 @@ use vhost_rs::vhost_user::{Master, VhostUserMaster, VhostUserMasterReqHandler};
|
||||
use vhost_rs::VhostBackend;
|
||||
use virtio_bindings::bindings::virtio_net;
|
||||
use virtio_bindings::bindings::virtio_ring;
|
||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
||||
use vm_memory::{ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const DEFAULT_QUEUE_NUMBER: usize = 2;
|
||||
@@ -37,6 +37,7 @@ struct SlaveReqHandler {}
|
||||
impl VhostUserMasterReqHandler for SlaveReqHandler {}
|
||||
|
||||
pub struct Net {
|
||||
id: String,
|
||||
vhost_user_net: Master,
|
||||
kill_evt: Option<EventFd>,
|
||||
pause_evt: Option<EventFd>,
|
||||
@@ -55,7 +56,7 @@ pub struct Net {
|
||||
impl Net {
|
||||
/// Create a new vhost-user-net device
|
||||
/// Create a new vhost-user-net device
|
||||
pub fn new(mac_addr: MacAddr, vu_cfg: VhostUserConfig) -> Result<Net> {
|
||||
pub fn new(id: String, mac_addr: MacAddr, vu_cfg: VhostUserConfig) -> Result<Net> {
|
||||
let mut vhost_user_net = Master::connect(&vu_cfg.sock, vu_cfg.num_queues as u64)
|
||||
.map_err(Error::VhostUserCreateMaster)?;
|
||||
|
||||
@@ -141,6 +142,7 @@ impl Net {
|
||||
}
|
||||
|
||||
Ok(Net {
|
||||
id,
|
||||
vhost_user_net,
|
||||
kill_evt: None,
|
||||
pause_evt: None,
|
||||
@@ -358,8 +360,17 @@ impl VirtioDevice for Net {
|
||||
fn shutdown(&mut self) {
|
||||
let _ = unsafe { libc::close(self.vhost_user_net.as_raw_fd()) };
|
||||
}
|
||||
|
||||
fn update_memory(&mut self, mem: &GuestMemoryMmap) -> std::result::Result<(), crate::Error> {
|
||||
update_mem_table(&mut self.vhost_user_net, mem).map_err(crate::Error::VhostUserUpdateMemory)
|
||||
}
|
||||
}
|
||||
|
||||
virtio_ctrl_q_pausable!(Net);
|
||||
impl Snapshotable for Net {}
|
||||
impl Snapshottable for Net {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
}
|
||||
impl Transportable for Net {}
|
||||
impl Migratable for Net {}
|
||||
|
||||
@@ -27,13 +27,7 @@ pub struct VhostUserConfig {
|
||||
pub queue_size: u16,
|
||||
}
|
||||
|
||||
pub fn setup_vhost_user_vring(
|
||||
vu: &mut Master,
|
||||
mem: &GuestMemoryMmap,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
virtio_interrupt: &Arc<dyn VirtioInterrupt>,
|
||||
) -> Result<Vec<(Option<EventFd>, Queue)>> {
|
||||
pub fn update_mem_table(vu: &mut Master, mem: &GuestMemoryMmap) -> Result<()> {
|
||||
let mut regions: Vec<VhostUserMemoryRegionInfo> = Vec::new();
|
||||
mem.with_regions_mut(|_, region| {
|
||||
let (mmap_handle, mmap_offset) = match region.file_offset() {
|
||||
@@ -58,6 +52,19 @@ pub fn setup_vhost_user_vring(
|
||||
vu.set_mem_table(regions.as_slice())
|
||||
.map_err(Error::VhostUserSetMemTable)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn setup_vhost_user_vring(
|
||||
vu: &mut Master,
|
||||
mem: &GuestMemoryMmap,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
virtio_interrupt: &Arc<dyn VirtioInterrupt>,
|
||||
) -> Result<Vec<(Option<EventFd>, Queue)>> {
|
||||
// Let's first provide the memory table to the backend.
|
||||
update_mem_table(vu, mem)?;
|
||||
|
||||
let mut vu_interrupt_list = Vec::new();
|
||||
|
||||
for (queue_index, queue) in queues.into_iter().enumerate() {
|
||||
|
||||
@@ -44,8 +44,8 @@ use std::result;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
||||
use vm_memory::{GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
@@ -376,6 +376,7 @@ where
|
||||
|
||||
/// Virtio device exposing virtual socket to the guest.
|
||||
pub struct Vsock<B: VsockBackend> {
|
||||
id: String,
|
||||
cid: u64,
|
||||
backend: Arc<RwLock<B>>,
|
||||
kill_evt: Option<EventFd>,
|
||||
@@ -394,7 +395,7 @@ where
|
||||
{
|
||||
/// Create a new virtio-vsock device with the given VM CID and vsock
|
||||
/// backend.
|
||||
pub fn new(cid: u64, backend: B, iommu: bool) -> io::Result<Vsock<B>> {
|
||||
pub fn new(id: String, cid: u64, backend: B, iommu: bool) -> io::Result<Vsock<B>> {
|
||||
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_F_IN_ORDER;
|
||||
|
||||
if iommu {
|
||||
@@ -402,6 +403,7 @@ where
|
||||
}
|
||||
|
||||
Ok(Vsock {
|
||||
id,
|
||||
cid,
|
||||
backend: Arc::new(RwLock::new(backend)),
|
||||
kill_evt: None,
|
||||
@@ -574,7 +576,15 @@ where
|
||||
|
||||
virtio_pausable!(Vsock, T: 'static + VsockBackend + Sync);
|
||||
|
||||
impl<B> Snapshotable for Vsock<B> where B: VsockBackend + Sync + 'static {}
|
||||
impl<B> Snapshottable for Vsock<B>
|
||||
where
|
||||
B: VsockBackend + Sync + 'static,
|
||||
{
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
}
|
||||
impl<B> Transportable for Vsock<B> where B: VsockBackend + Sync + 'static {}
|
||||
impl<B> Migratable for Vsock<B> where B: VsockBackend + Sync + 'static {}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -265,7 +265,7 @@ mod tests {
|
||||
cid: CID,
|
||||
mem: GuestMemoryMmap::from_ranges(&[(GuestAddress(0), MEM_SIZE)]).unwrap(),
|
||||
mem_size: MEM_SIZE,
|
||||
device: Vsock::new(CID, TestBackend::new(), false).unwrap(),
|
||||
device: Vsock::new(String::from("vsock"), CID, TestBackend::new(), false).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ acpi = ["acpi_tables","devices/acpi"]
|
||||
pci_support = ["pci", "vfio", "vm-virtio/pci_support"]
|
||||
mmio_support = ["vm-virtio/mmio_support"]
|
||||
cmos = ["devices/cmos"]
|
||||
fwdebug = ["devices/fwdebug"]
|
||||
|
||||
[dependencies]
|
||||
arc-swap = ">=0.4.4"
|
||||
@@ -19,24 +20,27 @@ anyhow = "1.0"
|
||||
arch = { path = "../arch" }
|
||||
devices = { path = "../devices" }
|
||||
epoll = ">=4.0.1"
|
||||
kvm-bindings = "0.2.0"
|
||||
kvm-ioctls = "0.5.0"
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch", features = ["with-serde", "fam-wrappers"] }
|
||||
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.68"
|
||||
libc = "0.2.69"
|
||||
log = "0.4.8"
|
||||
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "master" }
|
||||
net_util = { path = "../net_util" }
|
||||
pci = {path = "../pci", optional = true}
|
||||
qcow = { path = "../qcow" }
|
||||
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.21.1" }
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
url = "2.1.1"
|
||||
vfio = { path = "../vfio", optional = true }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = { version = "0.1.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-memory = { version = "0.2.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
vmm-sys-util = { version = ">=0.5.0", features = ["with-serde"] }
|
||||
signal-hook = "0.1.13"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
|
||||
@@ -4,11 +4,14 @@
|
||||
//
|
||||
|
||||
use crate::api::http_endpoint::{
|
||||
VmActionHandler, VmAddDevice, VmCreate, VmInfo, VmRemoveDevice, VmResize, VmmPing, VmmShutdown,
|
||||
VmActionHandler, VmAddDevice, VmAddDisk, VmAddFs, VmAddNet, VmAddPmem, VmAddVsock, VmCreate,
|
||||
VmInfo, VmRemoveDevice, VmResize, VmRestore, VmSnapshot, VmmPing, VmmShutdown,
|
||||
};
|
||||
use crate::api::{ApiRequest, VmAction};
|
||||
use crate::seccomp_filters::{get_seccomp_filter, Thread};
|
||||
use crate::{Error, Result};
|
||||
use micro_http::{HttpServer, MediaType, Request, Response, StatusCode, Version};
|
||||
use seccomp::{SeccompFilter, SeccompLevel};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::Sender;
|
||||
@@ -59,11 +62,18 @@ lazy_static! {
|
||||
r.routes.insert(endpoint!("/vm.resume"), Box::new(VmActionHandler::new(VmAction::Resume)));
|
||||
r.routes.insert(endpoint!("/vm.shutdown"), Box::new(VmActionHandler::new(VmAction::Shutdown)));
|
||||
r.routes.insert(endpoint!("/vm.reboot"), Box::new(VmActionHandler::new(VmAction::Reboot)));
|
||||
r.routes.insert(endpoint!("/vm.snapshot"), Box::new(VmSnapshot {}));
|
||||
r.routes.insert(endpoint!("/vm.restore"), Box::new(VmRestore {}));
|
||||
r.routes.insert(endpoint!("/vmm.shutdown"), Box::new(VmmShutdown {}));
|
||||
r.routes.insert(endpoint!("/vmm.ping"), Box::new(VmmPing {}));
|
||||
r.routes.insert(endpoint!("/vm.resize"), Box::new(VmResize {}));
|
||||
r.routes.insert(endpoint!("/vm.add-device"), Box::new(VmAddDevice {}));
|
||||
r.routes.insert(endpoint!("/vm.remove-device"), Box::new(VmRemoveDevice {}));
|
||||
r.routes.insert(endpoint!("/vm.add-disk"), Box::new(VmAddDisk {}));
|
||||
r.routes.insert(endpoint!("/vm.add-fs"), Box::new(VmAddFs {}));
|
||||
r.routes.insert(endpoint!("/vm.add-pmem"), Box::new(VmAddPmem {}));
|
||||
r.routes.insert(endpoint!("/vm.add-net"), Box::new(VmAddNet {}));
|
||||
r.routes.insert(endpoint!("/vm.add-vsock"), Box::new(VmAddVsock {}));
|
||||
|
||||
r
|
||||
};
|
||||
@@ -92,13 +102,21 @@ pub fn start_http_thread(
|
||||
path: &str,
|
||||
api_notifier: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
seccomp_level: &SeccompLevel,
|
||||
) -> Result<thread::JoinHandle<Result<()>>> {
|
||||
std::fs::remove_file(path).unwrap_or_default();
|
||||
let socket_path = PathBuf::from(path);
|
||||
|
||||
// Retrieve seccomp filter for API thread
|
||||
let api_seccomp_filter =
|
||||
get_seccomp_filter(seccomp_level, Thread::Api).map_err(Error::CreateSeccompFilter)?;
|
||||
|
||||
thread::Builder::new()
|
||||
.name("http-server".to_string())
|
||||
.spawn(move || {
|
||||
// Apply seccomp filter for API thread.
|
||||
SeccompFilter::apply(api_seccomp_filter).map_err(Error::ApplySeccompFilter)?;
|
||||
|
||||
let mut server = HttpServer::new(socket_path).unwrap();
|
||||
server.start_server().unwrap();
|
||||
loop {
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
|
||||
use crate::api::http::EndpointHandler;
|
||||
use crate::api::{
|
||||
vm_add_device, vm_boot, vm_create, vm_delete, vm_info, vm_pause, vm_reboot, vm_remove_device,
|
||||
vm_resize, vm_resume, vm_shutdown, vmm_ping, vmm_shutdown, ApiError, ApiRequest, ApiResult,
|
||||
DeviceConfig, VmAction, VmConfig, VmRemoveDeviceData, VmResizeData,
|
||||
vm_add_device, vm_add_disk, vm_add_fs, vm_add_net, vm_add_pmem, vm_add_vsock, vm_boot,
|
||||
vm_create, vm_delete, vm_info, vm_pause, vm_reboot, vm_remove_device, vm_resize, vm_restore,
|
||||
vm_resume, vm_shutdown, vm_snapshot, vmm_ping, vmm_shutdown, ApiError, ApiRequest, ApiResult,
|
||||
DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, RestoreConfig, VmAction, VmConfig,
|
||||
VmRemoveDeviceData, VmResizeData, VmSnapshotConfig, VsockConfig,
|
||||
};
|
||||
use micro_http::{Body, Method, Request, Response, StatusCode, Version};
|
||||
use serde_json::Error as SerdeError;
|
||||
@@ -42,6 +44,12 @@ pub enum HttpError {
|
||||
/// Could not reboot a VM
|
||||
VmReboot(ApiError),
|
||||
|
||||
/// Could not snapshot a VM
|
||||
VmSnapshot(ApiError),
|
||||
|
||||
/// Could not restore a VM
|
||||
VmRestore(ApiError),
|
||||
|
||||
/// Could not act on a VM
|
||||
VmAction(ApiError),
|
||||
|
||||
@@ -59,6 +67,21 @@ pub enum HttpError {
|
||||
|
||||
/// Could not handle VMM ping
|
||||
VmmPing(ApiError),
|
||||
|
||||
/// Could not add a disk to a VM
|
||||
VmAddDisk(ApiError),
|
||||
|
||||
/// Could not add a fs to a VM
|
||||
VmAddFs(ApiError),
|
||||
|
||||
/// Could not add a pmem device to a VM
|
||||
VmAddPmem(ApiError),
|
||||
|
||||
/// Could not add a network device to a VM
|
||||
VmAddNet(ApiError),
|
||||
|
||||
/// Could not add a vsock device to a VM
|
||||
VmAddVsock(ApiError),
|
||||
}
|
||||
|
||||
fn error_response(error: HttpError, status: StatusCode) -> Response {
|
||||
@@ -182,6 +205,84 @@ impl EndpointHandler for VmInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// /api/v1/vm.snapshot handler
|
||||
pub struct VmSnapshot {}
|
||||
|
||||
impl EndpointHandler for VmSnapshot {
|
||||
fn handle_request(
|
||||
&self,
|
||||
req: &Request,
|
||||
api_notifier: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
) -> Response {
|
||||
match req.method() {
|
||||
Method::Put => {
|
||||
match &req.body {
|
||||
Some(body) => {
|
||||
// Deserialize into a VmSnapshotConfig
|
||||
let vm_snapshot_data: VmSnapshotConfig =
|
||||
match serde_json::from_slice(body.raw())
|
||||
.map_err(HttpError::SerdeJsonDeserialize)
|
||||
{
|
||||
Ok(data) => data,
|
||||
Err(e) => return error_response(e, StatusCode::BadRequest),
|
||||
};
|
||||
|
||||
// Call vm_snapshot()
|
||||
match vm_snapshot(api_notifier, api_sender, Arc::new(vm_snapshot_data))
|
||||
.map_err(HttpError::VmSnapshot)
|
||||
{
|
||||
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
|
||||
Err(e) => error_response(e, StatusCode::InternalServerError),
|
||||
}
|
||||
}
|
||||
None => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
_ => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /api/v1/vm.restore handler
|
||||
pub struct VmRestore {}
|
||||
|
||||
impl EndpointHandler for VmRestore {
|
||||
fn handle_request(
|
||||
&self,
|
||||
req: &Request,
|
||||
api_notifier: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
) -> Response {
|
||||
match req.method() {
|
||||
Method::Put => {
|
||||
match &req.body {
|
||||
Some(body) => {
|
||||
// Deserialize into a RestoreConfig
|
||||
let vm_restore_data: RestoreConfig =
|
||||
match serde_json::from_slice(body.raw())
|
||||
.map_err(HttpError::SerdeJsonDeserialize)
|
||||
{
|
||||
Ok(data) => data,
|
||||
Err(e) => return error_response(e, StatusCode::BadRequest),
|
||||
};
|
||||
|
||||
// Call vm_restore()
|
||||
match vm_restore(api_notifier, api_sender, Arc::new(vm_restore_data))
|
||||
.map_err(HttpError::VmRestore)
|
||||
{
|
||||
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
|
||||
Err(e) => error_response(e, StatusCode::InternalServerError),
|
||||
}
|
||||
}
|
||||
None => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
_ => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /api/v1/vmm.info handler
|
||||
pub struct VmmPing {}
|
||||
|
||||
@@ -352,3 +453,196 @@ impl EndpointHandler for VmRemoveDevice {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /api/v1/vm.add-disk handler
|
||||
pub struct VmAddDisk {}
|
||||
|
||||
impl EndpointHandler for VmAddDisk {
|
||||
fn handle_request(
|
||||
&self,
|
||||
req: &Request,
|
||||
api_notifier: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
) -> Response {
|
||||
match req.method() {
|
||||
Method::Put => {
|
||||
match &req.body {
|
||||
Some(body) => {
|
||||
// Deserialize into a DiskConfig
|
||||
let vm_add_disk_data: DiskConfig = match serde_json::from_slice(body.raw())
|
||||
.map_err(HttpError::SerdeJsonDeserialize)
|
||||
{
|
||||
Ok(config) => config,
|
||||
Err(e) => return error_response(e, StatusCode::BadRequest),
|
||||
};
|
||||
|
||||
// Call vm_add_device()
|
||||
match vm_add_disk(api_notifier, api_sender, Arc::new(vm_add_disk_data))
|
||||
.map_err(HttpError::VmAddDisk)
|
||||
{
|
||||
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
|
||||
Err(e) => error_response(e, StatusCode::InternalServerError),
|
||||
}
|
||||
}
|
||||
|
||||
None => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
_ => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /api/v1/vm.add-fs handler
|
||||
pub struct VmAddFs {}
|
||||
|
||||
impl EndpointHandler for VmAddFs {
|
||||
fn handle_request(
|
||||
&self,
|
||||
req: &Request,
|
||||
api_notifier: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
) -> Response {
|
||||
match req.method() {
|
||||
Method::Put => {
|
||||
match &req.body {
|
||||
Some(body) => {
|
||||
// Deserialize into a FsConfig
|
||||
let vm_add_fs_data: FsConfig = match serde_json::from_slice(body.raw())
|
||||
.map_err(HttpError::SerdeJsonDeserialize)
|
||||
{
|
||||
Ok(config) => config,
|
||||
Err(e) => return error_response(e, StatusCode::BadRequest),
|
||||
};
|
||||
|
||||
// Call vm_add_fs()
|
||||
match vm_add_fs(api_notifier, api_sender, Arc::new(vm_add_fs_data))
|
||||
.map_err(HttpError::VmAddFs)
|
||||
{
|
||||
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
|
||||
Err(e) => error_response(e, StatusCode::InternalServerError),
|
||||
}
|
||||
}
|
||||
|
||||
None => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
_ => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /api/v1/vm.add-pmem handler
|
||||
pub struct VmAddPmem {}
|
||||
|
||||
impl EndpointHandler for VmAddPmem {
|
||||
fn handle_request(
|
||||
&self,
|
||||
req: &Request,
|
||||
api_notifier: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
) -> Response {
|
||||
match req.method() {
|
||||
Method::Put => {
|
||||
match &req.body {
|
||||
Some(body) => {
|
||||
// Deserialize into a PmemConfig
|
||||
let vm_add_pmem_data: PmemConfig = match serde_json::from_slice(body.raw())
|
||||
.map_err(HttpError::SerdeJsonDeserialize)
|
||||
{
|
||||
Ok(config) => config,
|
||||
Err(e) => return error_response(e, StatusCode::BadRequest),
|
||||
};
|
||||
|
||||
match vm_add_pmem(api_notifier, api_sender, Arc::new(vm_add_pmem_data))
|
||||
.map_err(HttpError::VmAddPmem)
|
||||
{
|
||||
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
|
||||
Err(e) => error_response(e, StatusCode::InternalServerError),
|
||||
}
|
||||
}
|
||||
|
||||
None => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
_ => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /api/v1/vm.add-net handler
|
||||
pub struct VmAddNet {}
|
||||
|
||||
impl EndpointHandler for VmAddNet {
|
||||
fn handle_request(
|
||||
&self,
|
||||
req: &Request,
|
||||
api_notifier: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
) -> Response {
|
||||
match req.method() {
|
||||
Method::Put => {
|
||||
match &req.body {
|
||||
Some(body) => {
|
||||
// Deserialize into a NetConfig
|
||||
let vm_add_net_data: NetConfig = match serde_json::from_slice(body.raw())
|
||||
.map_err(HttpError::SerdeJsonDeserialize)
|
||||
{
|
||||
Ok(config) => config,
|
||||
Err(e) => return error_response(e, StatusCode::BadRequest),
|
||||
};
|
||||
|
||||
match vm_add_net(api_notifier, api_sender, Arc::new(vm_add_net_data))
|
||||
.map_err(HttpError::VmAddNet)
|
||||
{
|
||||
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
|
||||
Err(e) => error_response(e, StatusCode::InternalServerError),
|
||||
}
|
||||
}
|
||||
|
||||
None => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
_ => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /api/v1/vm.add-vsock handler
|
||||
pub struct VmAddVsock {}
|
||||
|
||||
impl EndpointHandler for VmAddVsock {
|
||||
fn handle_request(
|
||||
&self,
|
||||
req: &Request,
|
||||
api_notifier: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
) -> Response {
|
||||
match req.method() {
|
||||
Method::Put => {
|
||||
match &req.body {
|
||||
Some(body) => {
|
||||
// Deserialize into a VsockConfig
|
||||
let vm_add_vsock_data: VsockConfig =
|
||||
match serde_json::from_slice(body.raw())
|
||||
.map_err(HttpError::SerdeJsonDeserialize)
|
||||
{
|
||||
Ok(config) => config,
|
||||
Err(e) => return error_response(e, StatusCode::BadRequest),
|
||||
};
|
||||
|
||||
match vm_add_vsock(api_notifier, api_sender, Arc::new(vm_add_vsock_data))
|
||||
.map_err(HttpError::VmAddVsock)
|
||||
{
|
||||
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
|
||||
Err(e) => error_response(e, StatusCode::InternalServerError),
|
||||
}
|
||||
}
|
||||
|
||||
None => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
_ => Response::new(Version::Http11, StatusCode::BadRequest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
//! 5. The thread handles the response and forwards potential errors.
|
||||
|
||||
extern crate micro_http;
|
||||
extern crate vm_device;
|
||||
extern crate vmm_sys_util;
|
||||
|
||||
pub use self::http::start_http_thread;
|
||||
@@ -36,7 +37,9 @@ pub use self::http::start_http_thread;
|
||||
pub mod http;
|
||||
pub mod http_endpoint;
|
||||
|
||||
use crate::config::{DeviceConfig, VmConfig};
|
||||
use crate::config::{
|
||||
DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, RestoreConfig, VmConfig, VsockConfig,
|
||||
};
|
||||
use crate::vm::{Error as VmError, VmState};
|
||||
use std::io;
|
||||
use std::sync::mpsc::{channel, RecvError, SendError, Sender};
|
||||
@@ -94,6 +97,12 @@ pub enum ApiError {
|
||||
/// The VM could not reboot.
|
||||
VmReboot(VmError),
|
||||
|
||||
/// The VM could not be snapshotted.
|
||||
VmSnapshot(VmError),
|
||||
|
||||
/// The VM could not restored.
|
||||
VmRestore(VmError),
|
||||
|
||||
/// The VMM could not shutdown.
|
||||
VmmShutdown(VmError),
|
||||
|
||||
@@ -105,6 +114,27 @@ pub enum ApiError {
|
||||
|
||||
/// The device could not be removed from the VM.
|
||||
VmRemoveDevice(VmError),
|
||||
|
||||
/// Cannot create seccomp filter
|
||||
CreateSeccompFilter(seccomp::SeccompError),
|
||||
|
||||
/// Cannot apply seccomp filter
|
||||
ApplySeccompFilter(seccomp::Error),
|
||||
|
||||
/// The disk could not be added to the VM.
|
||||
VmAddDisk(VmError),
|
||||
|
||||
/// The fs could not be added to the VM.
|
||||
VmAddFs(VmError),
|
||||
|
||||
/// The pmem device could not be added to the VM.
|
||||
VmAddPmem(VmError),
|
||||
|
||||
/// The network device could not be added to the VM.
|
||||
VmAddNet(VmError),
|
||||
|
||||
/// The vsock device could not be added to the VM.
|
||||
VmAddVsock(VmError),
|
||||
}
|
||||
pub type ApiResult<T> = std::result::Result<T, ApiError>;
|
||||
|
||||
@@ -130,6 +160,12 @@ pub struct VmRemoveDeviceData {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct VmSnapshotConfig {
|
||||
/// The snapshot destination URL
|
||||
pub destination_url: String,
|
||||
}
|
||||
|
||||
pub enum ApiResponsePayload {
|
||||
/// No data is sent on the channel.
|
||||
Empty,
|
||||
@@ -198,6 +234,27 @@ pub enum ApiRequest {
|
||||
|
||||
/// Remove a device from the VM.
|
||||
VmRemoveDevice(Arc<VmRemoveDeviceData>, Sender<ApiResponse>),
|
||||
|
||||
/// Add a disk to the VM.
|
||||
VmAddDisk(Arc<DiskConfig>, Sender<ApiResponse>),
|
||||
|
||||
/// Add a fs to the VM.
|
||||
VmAddFs(Arc<FsConfig>, Sender<ApiResponse>),
|
||||
|
||||
/// Add a pmem device to the VM.
|
||||
VmAddPmem(Arc<PmemConfig>, Sender<ApiResponse>),
|
||||
|
||||
/// Add a network device to the VM.
|
||||
VmAddNet(Arc<NetConfig>, Sender<ApiResponse>),
|
||||
|
||||
/// Add a vsock device to the VM.
|
||||
VmAddVsock(Arc<VsockConfig>, Sender<ApiResponse>),
|
||||
|
||||
/// Take a VM snapshot
|
||||
VmSnapshot(Arc<VmSnapshotConfig>, Sender<ApiResponse>),
|
||||
|
||||
/// Restore from a VM snapshot
|
||||
VmRestore(Arc<RestoreConfig>, Sender<ApiResponse>),
|
||||
}
|
||||
|
||||
pub fn vm_create(
|
||||
@@ -286,6 +343,42 @@ pub fn vm_resume(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<
|
||||
vm_action(api_evt, api_sender, VmAction::Resume)
|
||||
}
|
||||
|
||||
pub fn vm_snapshot(
|
||||
api_evt: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
data: Arc<VmSnapshotConfig>,
|
||||
) -> ApiResult<()> {
|
||||
let (response_sender, response_receiver) = channel();
|
||||
|
||||
// Send the VM snapshot request.
|
||||
api_sender
|
||||
.send(ApiRequest::VmSnapshot(data, response_sender))
|
||||
.map_err(ApiError::RequestSend)?;
|
||||
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
|
||||
|
||||
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn vm_restore(
|
||||
api_evt: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
data: Arc<RestoreConfig>,
|
||||
) -> ApiResult<()> {
|
||||
let (response_sender, response_receiver) = channel();
|
||||
|
||||
// Send the VM restore request.
|
||||
api_sender
|
||||
.send(ApiRequest::VmRestore(data, response_sender))
|
||||
.map_err(ApiError::RequestSend)?;
|
||||
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
|
||||
|
||||
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn vm_info(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<VmInfo> {
|
||||
let (response_sender, response_receiver) = channel();
|
||||
|
||||
@@ -386,3 +479,93 @@ pub fn vm_remove_device(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn vm_add_disk(
|
||||
api_evt: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
data: Arc<DiskConfig>,
|
||||
) -> ApiResult<()> {
|
||||
let (response_sender, response_receiver) = channel();
|
||||
|
||||
// Send the VM add-disk request.
|
||||
api_sender
|
||||
.send(ApiRequest::VmAddDisk(data, response_sender))
|
||||
.map_err(ApiError::RequestSend)?;
|
||||
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
|
||||
|
||||
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn vm_add_fs(
|
||||
api_evt: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
data: Arc<FsConfig>,
|
||||
) -> ApiResult<()> {
|
||||
let (response_sender, response_receiver) = channel();
|
||||
|
||||
// Send the VM add-fs request.
|
||||
api_sender
|
||||
.send(ApiRequest::VmAddFs(data, response_sender))
|
||||
.map_err(ApiError::RequestSend)?;
|
||||
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
|
||||
|
||||
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn vm_add_pmem(
|
||||
api_evt: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
data: Arc<PmemConfig>,
|
||||
) -> ApiResult<()> {
|
||||
let (response_sender, response_receiver) = channel();
|
||||
|
||||
// Send the VM add-pmem request.
|
||||
api_sender
|
||||
.send(ApiRequest::VmAddPmem(data, response_sender))
|
||||
.map_err(ApiError::RequestSend)?;
|
||||
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
|
||||
|
||||
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn vm_add_net(
|
||||
api_evt: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
data: Arc<NetConfig>,
|
||||
) -> ApiResult<()> {
|
||||
let (response_sender, response_receiver) = channel();
|
||||
|
||||
// Send the VM add-net request.
|
||||
api_sender
|
||||
.send(ApiRequest::VmAddNet(data, response_sender))
|
||||
.map_err(ApiError::RequestSend)?;
|
||||
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
|
||||
|
||||
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn vm_add_vsock(
|
||||
api_evt: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
data: Arc<VsockConfig>,
|
||||
) -> ApiResult<()> {
|
||||
let (response_sender, response_receiver) = channel();
|
||||
|
||||
// Send the VM add-vsock request.
|
||||
api_sender
|
||||
.send(ApiRequest::VmAddVsock(data, response_sender))
|
||||
.map_err(ApiError::RequestSend)?;
|
||||
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
|
||||
|
||||
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -171,6 +171,121 @@ paths:
|
||||
404:
|
||||
description: The device could not be removed from the VM instance.
|
||||
|
||||
/vm.add-disk:
|
||||
put:
|
||||
summary: Add a new disk to the VM
|
||||
requestBody:
|
||||
description: The details of the new disk
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DiskConfig'
|
||||
required: true
|
||||
responses:
|
||||
204:
|
||||
description: The new disk was successfully added to the VM instance.
|
||||
500:
|
||||
description: The new disk could not be added to the VM instance.
|
||||
|
||||
/vm.add-fs:
|
||||
put:
|
||||
summary: Add a new virtio-fs device to the VM
|
||||
requestBody:
|
||||
description: The details of the new virtio-fs
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FsConfig'
|
||||
required: true
|
||||
responses:
|
||||
204:
|
||||
description: The new device was successfully added to the VM instance.
|
||||
500:
|
||||
description: The new device could not be added to the VM instance.
|
||||
|
||||
/vm.add-pmem:
|
||||
put:
|
||||
summary: Add a new pmem device to the VM
|
||||
requestBody:
|
||||
description: The details of the new pmem device
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PmemConfig'
|
||||
required: true
|
||||
responses:
|
||||
204:
|
||||
description: The new device was successfully added to the VM instance.
|
||||
500:
|
||||
description: The new device could not be added to the VM instance.
|
||||
|
||||
/vm.add-net:
|
||||
put:
|
||||
summary: Add a new network device to the VM
|
||||
requestBody:
|
||||
description: The details of the new network device
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/NetConfig'
|
||||
required: true
|
||||
responses:
|
||||
204:
|
||||
description: The new device was successfully added to the VM instance.
|
||||
500:
|
||||
description: The new device could not be added to the VM instance.
|
||||
|
||||
/vm.add-vsock:
|
||||
put:
|
||||
summary: Add a new vsock device to the VM
|
||||
requestBody:
|
||||
description: The details of the new vsock device
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VsockConfig'
|
||||
required: true
|
||||
responses:
|
||||
204:
|
||||
description: The new device was successfully added to the VM instance.
|
||||
500:
|
||||
description: The new device could not be added to the VM instance.
|
||||
|
||||
|
||||
/vm.snapshot:
|
||||
put:
|
||||
summary: Returns a VM snapshot.
|
||||
requestBody:
|
||||
description: The snapshot configuration
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VmSnapshotConfig'
|
||||
required: true
|
||||
responses:
|
||||
204:
|
||||
description: The VM instance was successfully snapshotted.
|
||||
404:
|
||||
description: The VM instance could not be snapshotted because it is not created.
|
||||
405:
|
||||
description: The VM instance could not be snapshotted because it is not booted.
|
||||
|
||||
/vm.restore:
|
||||
put:
|
||||
summary: Restore a VM from a snapshot.
|
||||
requestBody:
|
||||
description: The restore configuration
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RestoreConfig'
|
||||
required: true
|
||||
responses:
|
||||
204:
|
||||
description: The VM instance was successfully restored.
|
||||
404:
|
||||
description: The VM instance could not be restored because it is already created.
|
||||
|
||||
components:
|
||||
schemas:
|
||||
|
||||
@@ -208,6 +323,8 @@ components:
|
||||
$ref: '#/components/schemas/MemoryConfig'
|
||||
kernel:
|
||||
$ref: '#/components/schemas/KernelConfig'
|
||||
initramfs:
|
||||
$ref: '#/components/schemas/InitramfsConfig'
|
||||
cmdline:
|
||||
$ref: '#/components/schemas/CmdLineConfig'
|
||||
disks:
|
||||
@@ -237,8 +354,6 @@ components:
|
||||
items:
|
||||
$ref: '#/components/schemas/DeviceConfig'
|
||||
vsock:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VsockConfig'
|
||||
iommu:
|
||||
type: boolean
|
||||
@@ -277,6 +392,15 @@ components:
|
||||
mergeable:
|
||||
type: boolean
|
||||
default: false
|
||||
hotplug_method:
|
||||
type: string
|
||||
default: "acpi"
|
||||
shared:
|
||||
type: boolean
|
||||
default: false
|
||||
hugepages:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
KernelConfig:
|
||||
required:
|
||||
@@ -286,6 +410,15 @@ components:
|
||||
path:
|
||||
type: string
|
||||
|
||||
InitramfsConfig:
|
||||
nullable: true
|
||||
required:
|
||||
- path
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
|
||||
CmdLineConfig:
|
||||
required:
|
||||
- args
|
||||
@@ -327,6 +460,8 @@ components:
|
||||
poll_queue:
|
||||
type: boolean
|
||||
default: true
|
||||
id:
|
||||
type: string
|
||||
|
||||
NetConfig:
|
||||
type: object
|
||||
@@ -356,6 +491,8 @@ components:
|
||||
default: false
|
||||
vhost_socket:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
|
||||
RngConfig:
|
||||
required:
|
||||
@@ -392,11 +529,12 @@ components:
|
||||
type: integer
|
||||
format: int64
|
||||
default: 8589934592
|
||||
id:
|
||||
type: string
|
||||
|
||||
PmemConfig:
|
||||
required:
|
||||
- file
|
||||
- size
|
||||
type: object
|
||||
properties:
|
||||
file:
|
||||
@@ -410,6 +548,11 @@ components:
|
||||
mergeable:
|
||||
type: boolean
|
||||
default: false
|
||||
discard_writes:
|
||||
type: boolean
|
||||
default: false
|
||||
id:
|
||||
type: string
|
||||
|
||||
ConsoleConfig:
|
||||
required:
|
||||
@@ -455,6 +598,8 @@ components:
|
||||
iommu:
|
||||
type: boolean
|
||||
default: false
|
||||
id:
|
||||
type: string
|
||||
|
||||
VmResize:
|
||||
type: object
|
||||
@@ -478,3 +623,19 @@ components:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
|
||||
VmSnapshotConfig:
|
||||
type: object
|
||||
properties:
|
||||
destination_url:
|
||||
type: string
|
||||
|
||||
RestoreConfig:
|
||||
required:
|
||||
- source_url
|
||||
type: object
|
||||
properties:
|
||||
source_url:
|
||||
type: string
|
||||
prefault:
|
||||
type: boolean
|
||||
|
||||
1976
vmm/src/config.rs
1976
vmm/src/config.rs
File diff suppressed because it is too large
Load Diff
491
vmm/src/cpu.rs
491
vmm/src/cpu.rs
@@ -10,27 +10,42 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
//
|
||||
|
||||
use crate::config::CpusConfig;
|
||||
use crate::device_manager::DeviceManager;
|
||||
use crate::CPU_MANAGER_SNAPSHOT_ID;
|
||||
#[cfg(feature = "acpi")]
|
||||
use acpi_tables::{aml, aml::Aml, sdt::SDT};
|
||||
use anyhow::anyhow;
|
||||
#[cfg(feature = "acpi")]
|
||||
use arch::layout;
|
||||
use arch::EntryPoint;
|
||||
use devices::{ioapic, BusDevice};
|
||||
use kvm_bindings::CpuId;
|
||||
use kvm_bindings::{
|
||||
kvm_fpu, kvm_lapic_state, kvm_mp_state, kvm_regs, kvm_sregs, kvm_vcpu_events, kvm_xcrs,
|
||||
kvm_xsave, CpuId, Msrs,
|
||||
};
|
||||
use kvm_ioctls::*;
|
||||
use libc::{c_void, siginfo_t};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::cmp;
|
||||
use std::os::unix::thread::JoinHandleExt;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use std::thread;
|
||||
use std::{fmt, io, result};
|
||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
||||
use vm_memory::{Address, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::signal::{register_signal_handler, SIGRTMIN};
|
||||
|
||||
// CPUID feature bits
|
||||
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
||||
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
|
||||
|
||||
// Debug I/O port
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const DEBUG_IOPORT: u16 = 0x80;
|
||||
@@ -84,6 +99,9 @@ pub enum Error {
|
||||
/// Cannot spawn a new vCPU thread.
|
||||
VcpuSpawn(io::Error),
|
||||
|
||||
/// Cannot patch the CPU ID
|
||||
PatchCpuId(kvm_ioctls::Error),
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
/// Error configuring the general purpose registers
|
||||
REGSConfiguration(arch::x86_64::regs::Error),
|
||||
@@ -121,6 +139,60 @@ pub enum Error {
|
||||
|
||||
/// Asking for more vCPUs that we can have
|
||||
DesiredVCPUCountExceedsMax,
|
||||
|
||||
/// Failed to get KVM vcpu lapic.
|
||||
VcpuGetLapic(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to set KVM vcpu lapic.
|
||||
VcpuSetLapic(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to get KVM vcpu MP state.
|
||||
VcpuGetMpState(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to set KVM vcpu MP state.
|
||||
VcpuSetMpState(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to get KVM vcpu msrs.
|
||||
VcpuGetMsrs(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to set KVM vcpu msrs.
|
||||
VcpuSetMsrs(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to get KVM vcpu regs.
|
||||
VcpuGetRegs(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to set KVM vcpu regs.
|
||||
VcpuSetRegs(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to get KVM vcpu sregs.
|
||||
VcpuGetSregs(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to set KVM vcpu sregs.
|
||||
VcpuSetSregs(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to get KVM vcpu events.
|
||||
VcpuGetVcpuEvents(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to set KVM vcpu events.
|
||||
VcpuSetVcpuEvents(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to get KVM vcpu FPU.
|
||||
VcpuGetFpu(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to set KVM vcpu FPU.
|
||||
VcpuSetFpu(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to get KVM vcpu XSAVE.
|
||||
VcpuGetXsave(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to set KVM vcpu XSAVE.
|
||||
VcpuSetXsave(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to get KVM vcpu XCRS.
|
||||
VcpuGetXcrs(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to set KVM vcpu XCRS.
|
||||
VcpuSetXcrs(kvm_ioctls::Error),
|
||||
}
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
@@ -242,6 +314,19 @@ pub struct Vcpu {
|
||||
vm_ts: std::time::Instant,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct VcpuKvmState {
|
||||
msrs: Msrs,
|
||||
vcpu_events: kvm_vcpu_events,
|
||||
regs: kvm_regs,
|
||||
sregs: kvm_sregs,
|
||||
fpu: kvm_fpu,
|
||||
lapic_state: kvm_lapic_state,
|
||||
xsave: kvm_xsave,
|
||||
xcrs: kvm_xcrs,
|
||||
mp_state: kvm_mp_state,
|
||||
}
|
||||
|
||||
impl Vcpu {
|
||||
/// Constructs a new VCPU for `vm`.
|
||||
///
|
||||
@@ -256,17 +341,17 @@ impl Vcpu {
|
||||
mmio_bus: Arc<devices::Bus>,
|
||||
ioapic: Option<Arc<Mutex<ioapic::Ioapic>>>,
|
||||
creation_ts: std::time::Instant,
|
||||
) -> Result<Self> {
|
||||
) -> Result<Arc<Mutex<Self>>> {
|
||||
let kvm_vcpu = fd.create_vcpu(id).map_err(Error::VcpuFd)?;
|
||||
// Initially the cpuid per vCPU is the one supported by this VM.
|
||||
Ok(Vcpu {
|
||||
Ok(Arc::new(Mutex::new(Vcpu {
|
||||
fd: kvm_vcpu,
|
||||
id,
|
||||
io_bus,
|
||||
mmio_bus,
|
||||
ioapic,
|
||||
vm_ts: creation_ts,
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
/// Configures a x86_64 specific vcpu and should be called once per vcpu from the vcpu's thread.
|
||||
@@ -375,6 +460,113 @@ impl Vcpu {
|
||||
ts.as_micros()
|
||||
);
|
||||
}
|
||||
|
||||
fn kvm_state(&self) -> Result<VcpuKvmState> {
|
||||
let mut msrs = arch::x86_64::regs::boot_msr_entries();
|
||||
self.fd.get_msrs(&mut msrs).map_err(Error::VcpuGetMsrs)?;
|
||||
|
||||
let vcpu_events = self
|
||||
.fd
|
||||
.get_vcpu_events()
|
||||
.map_err(Error::VcpuGetVcpuEvents)?;
|
||||
let regs = self.fd.get_regs().map_err(Error::VcpuGetRegs)?;
|
||||
let sregs = self.fd.get_sregs().map_err(Error::VcpuGetSregs)?;
|
||||
let lapic_state = self.fd.get_lapic().map_err(Error::VcpuGetLapic)?;
|
||||
let fpu = self.fd.get_fpu().map_err(Error::VcpuGetFpu)?;
|
||||
let xsave = self.fd.get_xsave().map_err(Error::VcpuGetXsave)?;
|
||||
let xcrs = self.fd.get_xcrs().map_err(Error::VcpuGetXsave)?;
|
||||
let mp_state = self.fd.get_mp_state().map_err(Error::VcpuGetMpState)?;
|
||||
|
||||
Ok(VcpuKvmState {
|
||||
msrs,
|
||||
vcpu_events,
|
||||
regs,
|
||||
sregs,
|
||||
fpu,
|
||||
lapic_state,
|
||||
xsave,
|
||||
xcrs,
|
||||
mp_state,
|
||||
})
|
||||
}
|
||||
|
||||
fn set_kvm_state(&mut self, state: &VcpuKvmState) -> Result<()> {
|
||||
self.fd.set_regs(&state.regs).map_err(Error::VcpuSetRegs)?;
|
||||
|
||||
self.fd.set_fpu(&state.fpu).map_err(Error::VcpuSetFpu)?;
|
||||
|
||||
self.fd
|
||||
.set_xsave(&state.xsave)
|
||||
.map_err(Error::VcpuSetXsave)?;
|
||||
|
||||
self.fd
|
||||
.set_sregs(&state.sregs)
|
||||
.map_err(Error::VcpuSetSregs)?;
|
||||
|
||||
self.fd.set_xcrs(&state.xcrs).map_err(Error::VcpuSetXcrs)?;
|
||||
|
||||
self.fd.set_msrs(&state.msrs).map_err(Error::VcpuSetMsrs)?;
|
||||
|
||||
self.fd
|
||||
.set_lapic(&state.lapic_state)
|
||||
.map_err(Error::VcpuSetLapic)?;
|
||||
|
||||
self.fd
|
||||
.set_mp_state(state.mp_state)
|
||||
.map_err(Error::VcpuSetMpState)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const VCPU_SNAPSHOT_ID: &str = "vcpu";
|
||||
impl Pausable for Vcpu {}
|
||||
impl Snapshottable for Vcpu {
|
||||
fn id(&self) -> String {
|
||||
VCPU_SNAPSHOT_ID.to_string()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot = serde_json::to_vec(&self.kvm_state().map_err(|e| {
|
||||
MigratableError::Snapshot(anyhow!("Could not get vCPU KVM state {:?}", e))
|
||||
})?)
|
||||
.map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut vcpu_snapshot = Snapshot::new(&format!("{}", self.id));
|
||||
vcpu_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", VCPU_SNAPSHOT_ID),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(vcpu_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(vcpu_section) = snapshot
|
||||
.snapshot_data
|
||||
.get(&format!("{}-section", VCPU_SNAPSHOT_ID))
|
||||
{
|
||||
let vcpu_state = match serde_json::from_slice(&vcpu_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize the vCPU snapshot {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
self.set_kvm_state(&vcpu_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not set the vCPU KVM state {:?}", e))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find the vCPU snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CpuManager {
|
||||
@@ -391,6 +583,7 @@ pub struct CpuManager {
|
||||
reset_evt: EventFd,
|
||||
vcpu_states: Vec<VcpuState>,
|
||||
selected_cpu: u8,
|
||||
vcpus: Vec<Arc<Mutex<Vcpu>>>,
|
||||
}
|
||||
|
||||
const CPU_ENABLE_FLAG: usize = 0;
|
||||
@@ -478,7 +671,7 @@ impl VcpuState {
|
||||
fn signal_thread(&self) {
|
||||
if let Some(handle) = self.handle.as_ref() {
|
||||
unsafe {
|
||||
libc::pthread_kill(handle.as_pthread_t(), SIGRTMIN());
|
||||
libc::pthread_kill(handle.as_pthread_t() as _, SIGRTMIN());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -500,21 +693,21 @@ impl VcpuState {
|
||||
|
||||
impl CpuManager {
|
||||
pub fn new(
|
||||
boot_vcpus: u8,
|
||||
max_vcpus: u8,
|
||||
config: &CpusConfig,
|
||||
device_manager: &Arc<Mutex<DeviceManager>>,
|
||||
guest_memory: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
kvm: &Kvm,
|
||||
fd: Arc<VmFd>,
|
||||
cpuid: CpuId,
|
||||
reset_evt: EventFd,
|
||||
) -> Result<Arc<Mutex<CpuManager>>> {
|
||||
let mut vcpu_states = Vec::with_capacity(usize::from(max_vcpus));
|
||||
vcpu_states.resize_with(usize::from(max_vcpus), VcpuState::default);
|
||||
let mut vcpu_states = Vec::with_capacity(usize::from(config.max_vcpus));
|
||||
vcpu_states.resize_with(usize::from(config.max_vcpus), VcpuState::default);
|
||||
|
||||
let device_manager = device_manager.lock().unwrap();
|
||||
let cpuid = CpuManager::patch_cpuid(kvm)?;
|
||||
let cpu_manager = Arc::new(Mutex::new(CpuManager {
|
||||
boot_vcpus,
|
||||
max_vcpus,
|
||||
boot_vcpus: config.boot_vcpus,
|
||||
max_vcpus: config.max_vcpus,
|
||||
io_bus: device_manager.io_bus().clone(),
|
||||
mmio_bus: device_manager.mmio_bus().clone(),
|
||||
ioapic: device_manager.ioapic().clone(),
|
||||
@@ -526,6 +719,7 @@ impl CpuManager {
|
||||
vcpu_states,
|
||||
reset_evt,
|
||||
selected_cpu: 0,
|
||||
vcpus: Vec::with_capacity(usize::from(config.max_vcpus)),
|
||||
}));
|
||||
|
||||
device_manager
|
||||
@@ -545,6 +739,153 @@ impl CpuManager {
|
||||
Ok(cpu_manager)
|
||||
}
|
||||
|
||||
fn patch_cpuid(kvm: &Kvm) -> Result<CpuId> {
|
||||
let mut cpuid_patches = Vec::new();
|
||||
|
||||
// Patch tsc deadline timer bit
|
||||
cpuid_patches.push(CpuidPatch {
|
||||
function: 1,
|
||||
index: 0,
|
||||
flags_bit: None,
|
||||
eax_bit: None,
|
||||
ebx_bit: None,
|
||||
ecx_bit: Some(TSC_DEADLINE_TIMER_ECX_BIT),
|
||||
edx_bit: None,
|
||||
});
|
||||
|
||||
// Patch hypervisor bit
|
||||
cpuid_patches.push(CpuidPatch {
|
||||
function: 1,
|
||||
index: 0,
|
||||
flags_bit: None,
|
||||
eax_bit: None,
|
||||
ebx_bit: None,
|
||||
ecx_bit: Some(HYPERVISOR_ECX_BIT),
|
||||
edx_bit: None,
|
||||
});
|
||||
|
||||
// Supported CPUID
|
||||
let mut cpuid = kvm
|
||||
.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
|
||||
.map_err(Error::PatchCpuId)?;
|
||||
|
||||
CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches);
|
||||
|
||||
Ok(cpuid)
|
||||
}
|
||||
|
||||
fn start_vcpu(
|
||||
&mut self,
|
||||
cpu_id: u8,
|
||||
creation_ts: std::time::Instant,
|
||||
vcpu_thread_barrier: Arc<Barrier>,
|
||||
entry_point: Option<EntryPoint>,
|
||||
inserting: bool,
|
||||
snapshot: Option<Snapshot>,
|
||||
) -> Result<()> {
|
||||
let ioapic = if let Some(ioapic) = &self.ioapic {
|
||||
Some(ioapic.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let vcpu = Vcpu::new(
|
||||
cpu_id,
|
||||
&self.fd,
|
||||
self.io_bus.clone(),
|
||||
self.mmio_bus.clone(),
|
||||
ioapic,
|
||||
creation_ts,
|
||||
)?;
|
||||
|
||||
let reset_evt = self.reset_evt.try_clone().unwrap();
|
||||
let vcpu_kill_signalled = self.vcpus_kill_signalled.clone();
|
||||
let vcpu_pause_signalled = self.vcpus_pause_signalled.clone();
|
||||
|
||||
let vcpu_kill = self.vcpu_states[usize::from(cpu_id)].kill.clone();
|
||||
|
||||
if let Some(snapshot) = snapshot {
|
||||
let mut cpuid = self.cpuid.clone();
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0xb, None, CpuidReg::EDX, u32::from(cpu_id));
|
||||
|
||||
vcpu.lock()
|
||||
.unwrap()
|
||||
.fd
|
||||
.set_cpuid2(&cpuid)
|
||||
.map_err(Error::SetSupportedCpusFailed)?;
|
||||
|
||||
vcpu.lock()
|
||||
.unwrap()
|
||||
.restore(snapshot)
|
||||
.expect("Failed to restore vCPU");
|
||||
} else {
|
||||
let vm_memory = self.vm_memory.clone();
|
||||
|
||||
vcpu.lock()
|
||||
.unwrap()
|
||||
.configure(entry_point, &vm_memory, self.cpuid.clone())
|
||||
.expect("Failed to configure vCPU");
|
||||
}
|
||||
|
||||
let vcpu_clone = Arc::clone(&vcpu);
|
||||
self.vcpus.push(vcpu_clone);
|
||||
|
||||
let handle = Some(
|
||||
thread::Builder::new()
|
||||
.name(format!("vcpu{}", cpu_id))
|
||||
.spawn(move || {
|
||||
extern "C" fn handle_signal(_: i32, _: *mut siginfo_t, _: *mut c_void) {}
|
||||
// This uses an async signal safe handler to kill the vcpu handles.
|
||||
register_signal_handler(SIGRTMIN(), handle_signal)
|
||||
.expect("Failed to register vcpu signal handler");
|
||||
|
||||
// Block until all CPUs are ready.
|
||||
vcpu_thread_barrier.wait();
|
||||
|
||||
loop {
|
||||
// vcpu.run() returns false on a KVM_EXIT_SHUTDOWN (triple-fault) so trigger a reset
|
||||
match vcpu.lock().unwrap().run() {
|
||||
Err(e) => {
|
||||
error!("VCPU generated error: {:?}", e);
|
||||
break;
|
||||
}
|
||||
Ok(true) => {}
|
||||
Ok(false) => {
|
||||
reset_evt.write(1).unwrap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We've been told to terminate
|
||||
if vcpu_kill_signalled.load(Ordering::SeqCst)
|
||||
|| vcpu_kill.load(Ordering::SeqCst)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// If we are being told to pause, we park the thread
|
||||
// until the pause boolean is toggled.
|
||||
// The resume operation is responsible for toggling
|
||||
// the boolean and unpark the thread.
|
||||
// We enter a loop because park() could spuriously
|
||||
// return. We will then park() again unless the
|
||||
// pause boolean has been toggled.
|
||||
while vcpu_pause_signalled.load(Ordering::SeqCst) {
|
||||
thread::park();
|
||||
}
|
||||
}
|
||||
})
|
||||
.map_err(Error::VcpuSpawn)?,
|
||||
);
|
||||
|
||||
// On hot plug calls into this function entry_point is None. It is for
|
||||
// those hotplug CPU additions that we need to set the inserting flag.
|
||||
self.vcpu_states[usize::from(cpu_id)].handle = handle;
|
||||
self.vcpu_states[usize::from(cpu_id)].inserting = inserting;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn activate_vcpus(&mut self, desired_vcpus: u8, entry_point: Option<EntryPoint>) -> Result<()> {
|
||||
if desired_vcpus > self.max_vcpus {
|
||||
return Err(Error::DesiredVCPUCountExceedsMax);
|
||||
@@ -556,86 +897,14 @@ impl CpuManager {
|
||||
));
|
||||
|
||||
for cpu_id in self.present_vcpus()..desired_vcpus {
|
||||
let ioapic = if let Some(ioapic) = &self.ioapic {
|
||||
Some(ioapic.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut vcpu = Vcpu::new(
|
||||
self.start_vcpu(
|
||||
cpu_id,
|
||||
&self.fd,
|
||||
self.io_bus.clone(),
|
||||
self.mmio_bus.clone(),
|
||||
ioapic,
|
||||
creation_ts,
|
||||
vcpu_thread_barrier.clone(),
|
||||
entry_point,
|
||||
entry_point.is_none(),
|
||||
None,
|
||||
)?;
|
||||
|
||||
let vcpu_thread_barrier = vcpu_thread_barrier.clone();
|
||||
|
||||
let reset_evt = self.reset_evt.try_clone().unwrap();
|
||||
let vcpu_kill_signalled = self.vcpus_kill_signalled.clone();
|
||||
let vcpu_pause_signalled = self.vcpus_pause_signalled.clone();
|
||||
|
||||
let vcpu_kill = self.vcpu_states[usize::from(cpu_id)].kill.clone();
|
||||
let vm_memory = self.vm_memory.clone();
|
||||
let cpuid = self.cpuid.clone();
|
||||
|
||||
let handle = Some(
|
||||
thread::Builder::new()
|
||||
.name(format!("vcpu{}", vcpu.id))
|
||||
.spawn(move || {
|
||||
extern "C" fn handle_signal(_: i32, _: *mut siginfo_t, _: *mut c_void) {}
|
||||
// This uses an async signal safe handler to kill the vcpu handles.
|
||||
register_signal_handler(SIGRTMIN(), handle_signal)
|
||||
.expect("Failed to register vcpu signal handler");
|
||||
|
||||
vcpu.configure(entry_point, &vm_memory, cpuid)
|
||||
.expect("Failed to configure vCPU");
|
||||
|
||||
// Block until all CPUs are ready.
|
||||
vcpu_thread_barrier.wait();
|
||||
|
||||
loop {
|
||||
// vcpu.run() returns false on a KVM_EXIT_SHUTDOWN (triple-fault) so trigger a reset
|
||||
match vcpu.run() {
|
||||
Err(e) => {
|
||||
error!("VCPU generated error: {:?}", e);
|
||||
break;
|
||||
}
|
||||
Ok(true) => {}
|
||||
Ok(false) => {
|
||||
reset_evt.write(1).unwrap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We've been told to terminate
|
||||
if vcpu_kill_signalled.load(Ordering::SeqCst)
|
||||
|| vcpu_kill.load(Ordering::SeqCst)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// If we are being told to pause, we park the thread
|
||||
// until the pause boolean is toggled.
|
||||
// The resume operation is responsible for toggling
|
||||
// the boolean and unpark the thread.
|
||||
// We enter a loop because park() could spuriously
|
||||
// return. We will then park() again unless the
|
||||
// pause boolean has been toggled.
|
||||
while vcpu_pause_signalled.load(Ordering::SeqCst) {
|
||||
thread::park();
|
||||
}
|
||||
}
|
||||
})
|
||||
.map_err(Error::VcpuSpawn)?,
|
||||
);
|
||||
|
||||
// On hot plug calls into this function entry_point is None. It is for
|
||||
// those hotplug CPU additions that we need to set the inserting flag.
|
||||
self.vcpu_states[usize::from(cpu_id)].handle = handle;
|
||||
self.vcpu_states[usize::from(cpu_id)].inserting = entry_point.is_none();
|
||||
}
|
||||
|
||||
// Unblock all CPU threads.
|
||||
@@ -1068,5 +1337,45 @@ impl Pausable for CpuManager {
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshotable for CpuManager {}
|
||||
impl Snapshottable for CpuManager {
|
||||
fn id(&self) -> String {
|
||||
CPU_MANAGER_SNAPSHOT_ID.to_string()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let mut cpu_manager_snapshot = Snapshot::new(CPU_MANAGER_SNAPSHOT_ID);
|
||||
|
||||
// The CpuManager snapshot is a collection of all vCPUs snapshots.
|
||||
for vcpu in &self.vcpus {
|
||||
let cpu_snapshot = vcpu.lock().unwrap().snapshot()?;
|
||||
cpu_manager_snapshot.add_snapshot(cpu_snapshot);
|
||||
}
|
||||
|
||||
Ok(cpu_manager_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
let creation_ts = std::time::Instant::now();
|
||||
let vcpu_thread_barrier = Arc::new(Barrier::new((snapshot.snapshots.len() + 1) as usize));
|
||||
|
||||
for (cpu_id, snapshot) in snapshot.snapshots.iter() {
|
||||
debug!("Restoring VCPU {}", cpu_id);
|
||||
self.start_vcpu(
|
||||
cpu_id.parse::<u8>().unwrap(),
|
||||
creation_ts,
|
||||
vcpu_thread_barrier.clone(),
|
||||
None,
|
||||
false,
|
||||
Some(*snapshot.clone()),
|
||||
)
|
||||
.map_err(|e| MigratableError::Restore(anyhow!("Could not restore vCPU {:?}", e)))?;
|
||||
}
|
||||
|
||||
// Unblock all restored CPU threads.
|
||||
vcpu_thread_barrier.wait();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Transportable for CpuManager {}
|
||||
impl Migratable for CpuManager {}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
202
vmm/src/lib.rs
202
vmm/src/lib.rs
@@ -3,6 +3,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
extern crate anyhow;
|
||||
extern crate arc_swap;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
@@ -13,19 +14,25 @@ extern crate serde;
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate tempfile;
|
||||
extern crate url;
|
||||
extern crate vmm_sys_util;
|
||||
|
||||
use crate::api::{ApiError, ApiRequest, ApiResponse, ApiResponsePayload, VmInfo, VmmPingResponse};
|
||||
use crate::config::{DeviceConfig, VmConfig};
|
||||
use crate::config::{
|
||||
DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, RestoreConfig, VmConfig, VsockConfig,
|
||||
};
|
||||
use crate::migration::{recv_vm_snapshot, vm_config_from_snapshot};
|
||||
use crate::seccomp_filters::{get_seccomp_filter, Thread};
|
||||
use crate::vm::{Error as VmError, Vm, VmState};
|
||||
use libc::EFD_NONBLOCK;
|
||||
use seccomp::{SeccompFilter, SeccompLevel};
|
||||
use std::io;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::{Receiver, RecvError, SendError, Sender};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{result, thread};
|
||||
use vm_device::Pausable;
|
||||
use vm_migration::{Pausable, Snapshottable, Transportable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub mod api;
|
||||
@@ -34,6 +41,8 @@ pub mod cpu;
|
||||
pub mod device_manager;
|
||||
pub mod interrupt;
|
||||
pub mod memory_manager;
|
||||
pub mod migration;
|
||||
pub mod seccomp_filters;
|
||||
pub mod vm;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
@@ -84,6 +93,12 @@ pub enum Error {
|
||||
|
||||
// Error following "exe" link
|
||||
ExePathReadLink(io::Error),
|
||||
|
||||
/// Cannot create seccomp filter
|
||||
CreateSeccompFilter(seccomp::SeccompError),
|
||||
|
||||
/// Cannot apply seccomp filter
|
||||
ApplySeccompFilter(seccomp::Error),
|
||||
}
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
@@ -161,9 +176,14 @@ pub fn start_vmm_thread(
|
||||
api_event: EventFd,
|
||||
api_sender: Sender<ApiRequest>,
|
||||
api_receiver: Receiver<ApiRequest>,
|
||||
seccomp_level: &SeccompLevel,
|
||||
) -> Result<thread::JoinHandle<Result<()>>> {
|
||||
let http_api_event = api_event.try_clone().map_err(Error::EventFdClone)?;
|
||||
|
||||
// Retrieve seccomp filter
|
||||
let vmm_seccomp_filter =
|
||||
get_seccomp_filter(seccomp_level, Thread::Vmm).map_err(Error::CreateSeccompFilter)?;
|
||||
|
||||
// Find the path that the "/proc/<pid>/exe" symlink points to. Must be done before spawning
|
||||
// a thread as Rust does not put the child threads in the same thread group which prevents the
|
||||
// link from being followed as per PTRACE_MODE_READ_FSCREDS (see proc(5) and ptrace(2)). The
|
||||
@@ -173,6 +193,9 @@ pub fn start_vmm_thread(
|
||||
let thread = thread::Builder::new()
|
||||
.name("vmm".to_string())
|
||||
.spawn(move || {
|
||||
// Apply seccomp filter for VMM thread.
|
||||
SeccompFilter::apply(vmm_seccomp_filter).map_err(Error::ApplySeccompFilter)?;
|
||||
|
||||
let mut vmm = Vmm::new(vmm_version.to_string(), api_event, vmm_path)?;
|
||||
|
||||
vmm.control_loop(Arc::new(api_receiver))
|
||||
@@ -180,7 +203,7 @@ pub fn start_vmm_thread(
|
||||
.map_err(Error::VmmThreadSpawn)?;
|
||||
|
||||
// The VMM thread is started, we can start serving HTTP requests
|
||||
api::start_http_thread(http_path, http_api_event, api_sender)?;
|
||||
api::start_http_thread(http_path, http_api_event, api_sender, seccomp_level)?;
|
||||
|
||||
Ok(thread)
|
||||
}
|
||||
@@ -271,6 +294,57 @@ impl Vmm {
|
||||
}
|
||||
}
|
||||
|
||||
fn vm_snapshot(&mut self, destination_url: &str) -> result::Result<(), VmError> {
|
||||
if let Some(ref mut vm) = self.vm {
|
||||
vm.snapshot()
|
||||
.map_err(VmError::Snapshot)
|
||||
.and_then(|snapshot| {
|
||||
vm.send(&snapshot, destination_url)
|
||||
.map_err(VmError::SnapshotSend)
|
||||
})
|
||||
} else {
|
||||
Err(VmError::VmNotRunning)
|
||||
}
|
||||
}
|
||||
|
||||
fn vm_restore(&mut self, restore_cfg: RestoreConfig) -> result::Result<(), VmError> {
|
||||
if self.vm.is_some() || self.vm_config.is_some() {
|
||||
return Err(VmError::VmAlreadyCreated);
|
||||
}
|
||||
|
||||
let source_url = restore_cfg.source_url.as_path().to_str();
|
||||
if source_url.is_none() {
|
||||
return Err(VmError::RestoreSourceUrlPathToStr);
|
||||
}
|
||||
// Safe to unwrap as we checked it was Some(&str).
|
||||
let source_url = source_url.unwrap();
|
||||
|
||||
let vm_snapshot = recv_vm_snapshot(source_url).map_err(VmError::Restore)?;
|
||||
let vm_config = vm_config_from_snapshot(&vm_snapshot).map_err(VmError::Restore)?;
|
||||
|
||||
self.vm_config = Some(Arc::clone(&vm_config));
|
||||
|
||||
let exit_evt = self.exit_evt.try_clone().map_err(VmError::EventFdClone)?;
|
||||
let reset_evt = self.reset_evt.try_clone().map_err(VmError::EventFdClone)?;
|
||||
|
||||
let vm = Vm::new_from_snapshot(
|
||||
&vm_snapshot,
|
||||
exit_evt,
|
||||
reset_evt,
|
||||
self.vmm_path.clone(),
|
||||
source_url,
|
||||
restore_cfg.prefault,
|
||||
)?;
|
||||
self.vm = Some(vm);
|
||||
|
||||
// Now we can restore the rest of the VM.
|
||||
if let Some(ref mut vm) = self.vm {
|
||||
vm.restore(vm_snapshot).map_err(VmError::Restore)
|
||||
} else {
|
||||
Err(VmError::VmNotCreated)
|
||||
}
|
||||
}
|
||||
|
||||
fn vm_shutdown(&mut self) -> result::Result<(), VmError> {
|
||||
if let Some(ref mut vm) = self.vm.take() {
|
||||
vm.shutdown()
|
||||
@@ -388,7 +462,72 @@ impl Vmm {
|
||||
fn vm_remove_device(&mut self, id: String) -> result::Result<(), VmError> {
|
||||
if let Some(ref mut vm) = self.vm {
|
||||
if let Err(e) = vm.remove_device(id) {
|
||||
error!("Error when adding new device to the VM: {:?}", e);
|
||||
error!("Error when removing new device to the VM: {:?}", e);
|
||||
Err(e)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(VmError::VmNotRunning)
|
||||
}
|
||||
}
|
||||
|
||||
fn vm_add_disk(&mut self, disk_cfg: DiskConfig) -> result::Result<(), VmError> {
|
||||
if let Some(ref mut vm) = self.vm {
|
||||
if let Err(e) = vm.add_disk(disk_cfg) {
|
||||
error!("Error when adding new disk to the VM: {:?}", e);
|
||||
Err(e)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(VmError::VmNotRunning)
|
||||
}
|
||||
}
|
||||
|
||||
fn vm_add_fs(&mut self, fs_cfg: FsConfig) -> result::Result<(), VmError> {
|
||||
if let Some(ref mut vm) = self.vm {
|
||||
if let Err(e) = vm.add_fs(fs_cfg) {
|
||||
error!("Error when adding new fs to the VM: {:?}", e);
|
||||
Err(e)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(VmError::VmNotRunning)
|
||||
}
|
||||
}
|
||||
|
||||
fn vm_add_pmem(&mut self, pmem_cfg: PmemConfig) -> result::Result<(), VmError> {
|
||||
if let Some(ref mut vm) = self.vm {
|
||||
if let Err(e) = vm.add_pmem(pmem_cfg) {
|
||||
error!("Error when adding new pmem device to the VM: {:?}", e);
|
||||
Err(e)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(VmError::VmNotRunning)
|
||||
}
|
||||
}
|
||||
|
||||
fn vm_add_net(&mut self, net_cfg: NetConfig) -> result::Result<(), VmError> {
|
||||
if let Some(ref mut vm) = self.vm {
|
||||
if let Err(e) = vm.add_net(net_cfg) {
|
||||
error!("Error when adding new network device to the VM: {:?}", e);
|
||||
Err(e)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(VmError::VmNotRunning)
|
||||
}
|
||||
}
|
||||
|
||||
fn vm_add_vsock(&mut self, vsock_cfg: VsockConfig) -> result::Result<(), VmError> {
|
||||
if let Some(ref mut vm) = self.vm {
|
||||
if let Err(e) = vm.add_vsock(vsock_cfg) {
|
||||
error!("Error when adding new vsock device to the VM: {:?}", e);
|
||||
Err(e)
|
||||
} else {
|
||||
Ok(())
|
||||
@@ -533,6 +672,22 @@ impl Vmm {
|
||||
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmSnapshot(snapshot_data, sender) => {
|
||||
let response = self
|
||||
.vm_snapshot(&snapshot_data.destination_url)
|
||||
.map_err(ApiError::VmSnapshot)
|
||||
.map(|_| ApiResponsePayload::Empty);
|
||||
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmRestore(restore_data, sender) => {
|
||||
let response = self
|
||||
.vm_restore(restore_data.as_ref().clone())
|
||||
.map_err(ApiError::VmRestore)
|
||||
.map(|_| ApiResponsePayload::Empty);
|
||||
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmmShutdown(sender) => {
|
||||
let response = self
|
||||
.vmm_shutdown()
|
||||
@@ -567,6 +722,41 @@ impl Vmm {
|
||||
.map(|_| ApiResponsePayload::Empty);
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmAddDisk(add_disk_data, sender) => {
|
||||
let response = self
|
||||
.vm_add_disk(add_disk_data.as_ref().clone())
|
||||
.map_err(ApiError::VmAddDisk)
|
||||
.map(|_| ApiResponsePayload::Empty);
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmAddFs(add_fs_data, sender) => {
|
||||
let response = self
|
||||
.vm_add_fs(add_fs_data.as_ref().clone())
|
||||
.map_err(ApiError::VmAddFs)
|
||||
.map(|_| ApiResponsePayload::Empty);
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmAddPmem(add_pmem_data, sender) => {
|
||||
let response = self
|
||||
.vm_add_pmem(add_pmem_data.as_ref().clone())
|
||||
.map_err(ApiError::VmAddPmem)
|
||||
.map(|_| ApiResponsePayload::Empty);
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmAddNet(add_net_data, sender) => {
|
||||
let response = self
|
||||
.vm_add_net(add_net_data.as_ref().clone())
|
||||
.map_err(ApiError::VmAddNet)
|
||||
.map(|_| ApiResponsePayload::Empty);
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
ApiRequest::VmAddVsock(add_vsock_data, sender) => {
|
||||
let response = self
|
||||
.vm_add_vsock(add_vsock_data.as_ref().clone())
|
||||
.map_err(ApiError::VmAddVsock)
|
||||
.map(|_| ApiResponsePayload::Empty);
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -577,3 +767,7 @@ impl Vmm {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const CPU_MANAGER_SNAPSHOT_ID: &str = "cpu-manager";
|
||||
const MEMORY_MANAGER_SNAPSHOT_ID: &str = "memory-manager";
|
||||
const DEVICE_MANAGER_SNAPSHOT_ID: &str = "device-manager";
|
||||
|
||||
@@ -3,25 +3,37 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use crate::config::{HotplugMethod, MemoryConfig};
|
||||
use crate::MEMORY_MANAGER_SNAPSHOT_ID;
|
||||
#[cfg(feature = "acpi")]
|
||||
use acpi_tables::{aml, aml::Aml};
|
||||
use arch::RegionType;
|
||||
use devices::BusDevice;
|
||||
use kvm_bindings::kvm_userspace_memory_region;
|
||||
use anyhow::anyhow;
|
||||
use arch::{layout, RegionType};
|
||||
use devices::{ioapic, BusDevice};
|
||||
use kvm_bindings::{kvm_userspace_memory_region, KVM_MEM_READONLY};
|
||||
use kvm_ioctls::*;
|
||||
use std::convert::TryInto;
|
||||
use std::ffi;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io;
|
||||
use std::os::unix::io::FromRawFd;
|
||||
use std::os::unix::io::{FromRawFd, RawFd};
|
||||
use std::path::PathBuf;
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_allocator::SystemAllocator;
|
||||
use url::Url;
|
||||
use vm_allocator::{GsiApic, SystemAllocator};
|
||||
use vm_memory::guest_memory::FileOffset;
|
||||
use vm_memory::{
|
||||
mmap::MmapRegionError, Address, Error as MmapError, GuestAddress, GuestAddressSpace,
|
||||
GuestMemory, GuestMemoryAtomic, GuestMemoryMmap, GuestMemoryRegion, GuestRegionMmap,
|
||||
GuestUsize, MmapRegion,
|
||||
mmap::MmapRegionError, Address, Bytes, Error as MmapError, GuestAddress, GuestAddressSpace,
|
||||
GuestMemory, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, GuestMemoryRegion,
|
||||
GuestRegionMmap, GuestUsize, MemoryRegionAddress, MmapRegion,
|
||||
};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
|
||||
const X86_64_IRQ_BASE: u32 = 5;
|
||||
|
||||
const HOTPLUG_COUNT: usize = 8;
|
||||
|
||||
@@ -45,8 +57,15 @@ pub struct MemoryManager {
|
||||
backing_file: Option<PathBuf>,
|
||||
mergeable: bool,
|
||||
allocator: Arc<Mutex<SystemAllocator>>,
|
||||
hotplug_method: HotplugMethod,
|
||||
boot_ram: u64,
|
||||
current_ram: u64,
|
||||
next_hotplug_slot: usize,
|
||||
pub virtiomem_region: Option<Arc<GuestRegionMmap>>,
|
||||
pub virtiomem_resize: Option<vm_virtio::Resize>,
|
||||
snapshot: Mutex<Option<GuestMemoryLoadGuard<GuestMemoryMmap>>>,
|
||||
shared: bool,
|
||||
hugepages: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -80,6 +99,25 @@ pub enum Error {
|
||||
|
||||
/// Failed to set the user memory region.
|
||||
SetUserMemoryRegion(kvm_ioctls::Error),
|
||||
|
||||
/// Failed to EventFd.
|
||||
EventFdFail(io::Error),
|
||||
|
||||
/// Eventfd write error
|
||||
EventfdError(io::Error),
|
||||
|
||||
/// Failed to virtio-mem resize
|
||||
VirtioMemResizeFail(vm_virtio::mem::Error),
|
||||
|
||||
/// Cannot restore VM
|
||||
Restore(MigratableError),
|
||||
|
||||
/// Cannot create the system allocator
|
||||
CreateSystemAllocator,
|
||||
|
||||
/// The number of external backing files doesn't match the number of
|
||||
/// memory regions.
|
||||
InvalidAmountExternalBackingFiles,
|
||||
}
|
||||
|
||||
pub fn get_host_cpu_phys_bits() -> u8 {
|
||||
@@ -192,15 +230,13 @@ impl BusDevice for MemoryManager {
|
||||
|
||||
impl MemoryManager {
|
||||
pub fn new(
|
||||
allocator: Arc<Mutex<SystemAllocator>>,
|
||||
fd: Arc<VmFd>,
|
||||
boot_ram: u64,
|
||||
hotplug_size: Option<u64>,
|
||||
backing_file: &Option<PathBuf>,
|
||||
mergeable: bool,
|
||||
config: &MemoryConfig,
|
||||
ext_regions: Option<Vec<MemoryRegion>>,
|
||||
prefault: bool,
|
||||
) -> Result<Arc<Mutex<MemoryManager>>, Error> {
|
||||
// Init guest memory
|
||||
let arch_mem_regions = arch::arch_memory_regions(boot_ram);
|
||||
let arch_mem_regions = arch::arch_memory_regions(config.size);
|
||||
|
||||
let ram_regions: Vec<(GuestAddress, usize)> = arch_mem_regions
|
||||
.iter()
|
||||
@@ -209,12 +245,34 @@ impl MemoryManager {
|
||||
.collect();
|
||||
|
||||
let mut mem_regions = Vec::new();
|
||||
for region in ram_regions.iter() {
|
||||
mem_regions.push(MemoryManager::create_ram_region(
|
||||
backing_file,
|
||||
region.0,
|
||||
region.1,
|
||||
)?);
|
||||
if let Some(ext_regions) = &ext_regions {
|
||||
if ram_regions.len() > ext_regions.len() {
|
||||
return Err(Error::InvalidAmountExternalBackingFiles);
|
||||
}
|
||||
|
||||
for region in ext_regions.iter() {
|
||||
mem_regions.push(MemoryManager::create_ram_region(
|
||||
&Some(region.backing_file.clone()),
|
||||
region.start_addr,
|
||||
region.size as usize,
|
||||
true,
|
||||
prefault,
|
||||
false,
|
||||
false,
|
||||
)?);
|
||||
}
|
||||
} else {
|
||||
for region in ram_regions.iter() {
|
||||
mem_regions.push(MemoryManager::create_ram_region(
|
||||
&config.file,
|
||||
region.0,
|
||||
region.1,
|
||||
false,
|
||||
prefault,
|
||||
config.shared,
|
||||
config.hugepages,
|
||||
)?);
|
||||
}
|
||||
}
|
||||
|
||||
let guest_memory =
|
||||
@@ -228,8 +286,32 @@ impl MemoryManager {
|
||||
mem_end.unchecked_add(1)
|
||||
};
|
||||
|
||||
if let Some(size) = hotplug_size {
|
||||
start_of_device_area = start_of_device_area.unchecked_add(size);
|
||||
let mut virtiomem_region = None;
|
||||
let mut virtiomem_resize = None;
|
||||
if let Some(size) = config.hotplug_size {
|
||||
if config.hotplug_method == HotplugMethod::VirtioMem {
|
||||
// Alignment must be "natural" i.e. same as size of block
|
||||
let start_addr = GuestAddress(
|
||||
(start_of_device_area.0 + vm_virtio::VIRTIO_MEM_DEFAULT_BLOCK_SIZE - 1)
|
||||
/ vm_virtio::VIRTIO_MEM_DEFAULT_BLOCK_SIZE
|
||||
* vm_virtio::VIRTIO_MEM_DEFAULT_BLOCK_SIZE,
|
||||
);
|
||||
virtiomem_region = Some(MemoryManager::create_ram_region(
|
||||
&config.file,
|
||||
start_addr,
|
||||
size as usize,
|
||||
false,
|
||||
false,
|
||||
config.shared,
|
||||
config.hugepages,
|
||||
)?);
|
||||
|
||||
virtiomem_resize = Some(vm_virtio::Resize::new().map_err(Error::EventFdFail)?);
|
||||
|
||||
start_of_device_area = start_addr.unchecked_add(size);
|
||||
} else {
|
||||
start_of_device_area = start_of_device_area.unchecked_add(size);
|
||||
}
|
||||
}
|
||||
|
||||
let guest_memory = GuestMemoryAtomic::new(guest_memory);
|
||||
@@ -237,6 +319,23 @@ impl MemoryManager {
|
||||
let mut hotplug_slots = Vec::with_capacity(HOTPLUG_COUNT);
|
||||
hotplug_slots.resize_with(HOTPLUG_COUNT, HotPlugState::default);
|
||||
|
||||
// Let's allocate 64 GiB of addressable MMIO space, starting at 0.
|
||||
let allocator = Arc::new(Mutex::new(
|
||||
SystemAllocator::new(
|
||||
GuestAddress(0),
|
||||
1 << 16 as GuestUsize,
|
||||
GuestAddress(0),
|
||||
1 << get_host_cpu_phys_bits(),
|
||||
layout::MEM_32BIT_RESERVED_START,
|
||||
layout::MEM_32BIT_DEVICES_SIZE,
|
||||
vec![GsiApic::new(
|
||||
X86_64_IRQ_BASE,
|
||||
ioapic::NUM_IOAPIC_PINS as u32 - X86_64_IRQ_BASE,
|
||||
)],
|
||||
)
|
||||
.ok_or(Error::CreateSystemAllocator)?,
|
||||
));
|
||||
|
||||
let memory_manager = Arc::new(Mutex::new(MemoryManager {
|
||||
guest_memory: guest_memory.clone(),
|
||||
next_kvm_memory_slot: 0,
|
||||
@@ -245,11 +344,18 @@ impl MemoryManager {
|
||||
fd,
|
||||
hotplug_slots,
|
||||
selected_slot: 0,
|
||||
backing_file: backing_file.clone(),
|
||||
mergeable,
|
||||
backing_file: config.file.clone(),
|
||||
mergeable: config.mergeable,
|
||||
allocator: allocator.clone(),
|
||||
current_ram: boot_ram,
|
||||
hotplug_method: config.hotplug_method.clone(),
|
||||
boot_ram: config.size,
|
||||
current_ram: config.size,
|
||||
next_hotplug_slot: 0,
|
||||
virtiomem_region: virtiomem_region.clone(),
|
||||
virtiomem_resize,
|
||||
snapshot: Mutex::new(None),
|
||||
shared: config.shared,
|
||||
hugepages: config.hugepages,
|
||||
}));
|
||||
|
||||
guest_memory.memory().with_regions(|_, region| {
|
||||
@@ -257,11 +363,27 @@ impl MemoryManager {
|
||||
region.start_addr().raw_value(),
|
||||
region.len() as u64,
|
||||
region.as_ptr() as u64,
|
||||
mergeable,
|
||||
config.mergeable,
|
||||
false,
|
||||
)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
if let Some(region) = virtiomem_region {
|
||||
memory_manager.lock().unwrap().create_userspace_mapping(
|
||||
region.start_addr().raw_value(),
|
||||
region.len() as u64,
|
||||
region.as_ptr() as u64,
|
||||
config.mergeable,
|
||||
false,
|
||||
)?;
|
||||
allocator
|
||||
.lock()
|
||||
.unwrap()
|
||||
.allocate_mmio_addresses(Some(region.start_addr()), region.len(), None)
|
||||
.ok_or(Error::MemoryRangeAllocation)?;
|
||||
}
|
||||
|
||||
// Allocate RAM and Reserved address ranges.
|
||||
for region in arch_mem_regions.iter() {
|
||||
allocator
|
||||
@@ -274,16 +396,106 @@ impl MemoryManager {
|
||||
Ok(memory_manager)
|
||||
}
|
||||
|
||||
pub fn new_from_snapshot(
|
||||
snapshot: &Snapshot,
|
||||
fd: Arc<VmFd>,
|
||||
config: &MemoryConfig,
|
||||
source_url: &str,
|
||||
prefault: bool,
|
||||
) -> Result<Arc<Mutex<MemoryManager>>, Error> {
|
||||
let url = Url::parse(source_url).unwrap();
|
||||
/* url must be valid dir which is verified in recv_vm_snapshot() */
|
||||
let vm_snapshot_path = url.to_file_path().unwrap();
|
||||
|
||||
if let Some(mem_section) = snapshot
|
||||
.snapshot_data
|
||||
.get(&format!("{}-section", MEMORY_MANAGER_SNAPSHOT_ID))
|
||||
{
|
||||
let mem_snapshot: MemoryManagerSnapshotData =
|
||||
match serde_json::from_slice(&mem_section.snapshot) {
|
||||
Ok(snapshot) => snapshot,
|
||||
Err(error) => {
|
||||
return Err(Error::Restore(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize MemoryManager {}",
|
||||
error
|
||||
))))
|
||||
}
|
||||
};
|
||||
|
||||
let mut ext_regions = mem_snapshot.memory_regions;
|
||||
for region in ext_regions.iter_mut() {
|
||||
let mut memory_region_path = vm_snapshot_path.clone();
|
||||
memory_region_path.push(region.backing_file.clone());
|
||||
region.backing_file = memory_region_path;
|
||||
}
|
||||
|
||||
// In case there was no backing file, we can safely use CoW by
|
||||
// mapping the source files provided for restoring. This case
|
||||
// allows for a faster VM restoration and does not require us to
|
||||
// fill the memory content, hence we can return right away.
|
||||
if config.file.is_none() {
|
||||
return MemoryManager::new(fd, config, Some(ext_regions), prefault);
|
||||
};
|
||||
|
||||
let memory_manager = MemoryManager::new(fd, config, None, false)?;
|
||||
let guest_memory = memory_manager.lock().unwrap().guest_memory();
|
||||
|
||||
// In case the previous config was using a backing file, this means
|
||||
// it was MAP_SHARED, therefore we must copy the content into the
|
||||
// new regions so that we can still use MAP_SHARED when restoring
|
||||
// the VM.
|
||||
guest_memory.memory().with_regions(|index, region| {
|
||||
// Open (read only) the snapshot file for the given region.
|
||||
let mut memory_region_file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&ext_regions[index].backing_file)
|
||||
.map_err(|e| Error::Restore(MigratableError::MigrateReceive(e.into())))?;
|
||||
|
||||
// Fill the region with the file content.
|
||||
region
|
||||
.read_from(
|
||||
MemoryRegionAddress(0),
|
||||
&mut memory_region_file,
|
||||
region.len().try_into().unwrap(),
|
||||
)
|
||||
.map_err(|e| Error::Restore(MigratableError::MigrateReceive(e.into())))?;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(memory_manager)
|
||||
} else {
|
||||
Err(Error::Restore(MigratableError::Restore(anyhow!(
|
||||
"Could not find {}-section from snapshot",
|
||||
MEMORY_MANAGER_SNAPSHOT_ID
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
fn memfd_create(name: &ffi::CStr, flags: u32) -> Result<RawFd, io::Error> {
|
||||
let res = unsafe { libc::syscall(libc::SYS_memfd_create, name.as_ptr(), flags) };
|
||||
|
||||
if res < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(res as RawFd)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_ram_region(
|
||||
backing_file: &Option<PathBuf>,
|
||||
start_addr: GuestAddress,
|
||||
size: usize,
|
||||
copy_on_write: bool,
|
||||
prefault: bool,
|
||||
shared: bool,
|
||||
hugepages: bool,
|
||||
) -> Result<Arc<GuestRegionMmap>, Error> {
|
||||
Ok(Arc::new(match backing_file {
|
||||
Some(ref file) => {
|
||||
let f = if file.is_dir() {
|
||||
let fs_str = format!("{}{}", file.display(), "/tmpfile_XXXXXX");
|
||||
let fs = std::ffi::CString::new(fs_str).unwrap();
|
||||
let fs = ffi::CString::new(fs_str).unwrap();
|
||||
let mut path = fs.as_bytes_with_nul().to_owned();
|
||||
let path_ptr = path.as_mut_ptr() as *mut _;
|
||||
let fd = unsafe { libc::mkstemp(path_ptr) };
|
||||
@@ -299,22 +511,74 @@ impl MemoryManager {
|
||||
|
||||
f.set_len(size as u64).map_err(Error::SharedFileSetLen)?;
|
||||
|
||||
let mut mmap_flags = if copy_on_write {
|
||||
libc::MAP_NORESERVE | libc::MAP_PRIVATE
|
||||
} else {
|
||||
libc::MAP_NORESERVE | libc::MAP_SHARED
|
||||
};
|
||||
if prefault {
|
||||
mmap_flags |= libc::MAP_POPULATE;
|
||||
}
|
||||
GuestRegionMmap::new(
|
||||
MmapRegion::from_file(FileOffset::new(f, 0), size)
|
||||
.map_err(Error::GuestMemoryRegion)?,
|
||||
MmapRegion::build(
|
||||
Some(FileOffset::new(f, 0)),
|
||||
size,
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
mmap_flags,
|
||||
)
|
||||
.map_err(Error::GuestMemoryRegion)?,
|
||||
start_addr,
|
||||
)
|
||||
.map_err(Error::GuestMemory)?
|
||||
}
|
||||
None => {
|
||||
let fd = Self::memfd_create(
|
||||
&ffi::CString::new("ch_ram").unwrap(),
|
||||
if hugepages {
|
||||
libc::MFD_HUGETLB | libc::MAP_HUGE_2MB as u32
|
||||
} else {
|
||||
0
|
||||
},
|
||||
)
|
||||
.map_err(Error::SharedFileCreate)?;
|
||||
|
||||
let f = unsafe { File::from_raw_fd(fd) };
|
||||
f.set_len(size as u64).map_err(Error::SharedFileSetLen)?;
|
||||
|
||||
let mmap_flags = libc::MAP_NORESERVE
|
||||
| if shared {
|
||||
libc::MAP_SHARED
|
||||
} else {
|
||||
libc::MAP_PRIVATE
|
||||
};
|
||||
GuestRegionMmap::new(
|
||||
MmapRegion::build(
|
||||
Some(FileOffset::new(f, 0)),
|
||||
size,
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
mmap_flags,
|
||||
)
|
||||
.map_err(Error::GuestMemoryRegion)?,
|
||||
start_addr,
|
||||
)
|
||||
.map_err(Error::GuestMemory)?
|
||||
}
|
||||
None => GuestRegionMmap::new(
|
||||
MmapRegion::new(size).map_err(Error::GuestMemoryRegion)?,
|
||||
start_addr,
|
||||
)
|
||||
.map_err(Error::GuestMemory)?,
|
||||
}))
|
||||
}
|
||||
|
||||
fn hotplug_ram_region(&mut self, size: usize) -> Result<(), Error> {
|
||||
// Update the GuestMemoryMmap with the new range
|
||||
fn add_region(&mut self, region: Arc<GuestRegionMmap>) -> Result<(), Error> {
|
||||
let guest_memory = self
|
||||
.guest_memory
|
||||
.memory()
|
||||
.insert_region(region)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
self.guest_memory.lock().unwrap().replace(guest_memory);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn hotplug_ram_region(&mut self, size: usize) -> Result<Arc<GuestRegionMmap>, Error> {
|
||||
info!("Hotplugging new RAM: {}", size);
|
||||
|
||||
// Check that there is a free slot
|
||||
@@ -342,7 +606,15 @@ impl MemoryManager {
|
||||
}
|
||||
|
||||
// Allocate memory for the region
|
||||
let region = MemoryManager::create_ram_region(&self.backing_file, start_addr, size)?;
|
||||
let region = MemoryManager::create_ram_region(
|
||||
&self.backing_file,
|
||||
start_addr,
|
||||
size,
|
||||
false,
|
||||
false,
|
||||
self.shared,
|
||||
self.hugepages,
|
||||
)?;
|
||||
|
||||
// Map it into the guest
|
||||
self.create_userspace_mapping(
|
||||
@@ -350,6 +622,7 @@ impl MemoryManager {
|
||||
region.len() as u64,
|
||||
region.as_ptr() as u64,
|
||||
self.mergeable,
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Tell the allocator
|
||||
@@ -368,21 +641,19 @@ impl MemoryManager {
|
||||
|
||||
self.next_hotplug_slot += 1;
|
||||
|
||||
// Update the GuestMemoryMmap with the new range
|
||||
let guest_memory = self
|
||||
.guest_memory
|
||||
.memory()
|
||||
.insert_region(region)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
self.guest_memory.lock().unwrap().replace(guest_memory);
|
||||
self.add_region(Arc::clone(®ion))?;
|
||||
|
||||
Ok(())
|
||||
Ok(region)
|
||||
}
|
||||
|
||||
pub fn guest_memory(&self) -> GuestMemoryAtomic<GuestMemoryMmap> {
|
||||
self.guest_memory.clone()
|
||||
}
|
||||
|
||||
pub fn allocator(&self) -> Arc<Mutex<SystemAllocator>> {
|
||||
self.allocator.clone()
|
||||
}
|
||||
|
||||
pub fn start_of_device_area(&self) -> GuestAddress {
|
||||
self.start_of_device_area
|
||||
}
|
||||
@@ -403,6 +674,7 @@ impl MemoryManager {
|
||||
memory_size: u64,
|
||||
userspace_addr: u64,
|
||||
mergeable: bool,
|
||||
readonly: bool,
|
||||
) -> Result<u32, Error> {
|
||||
let slot = self.allocate_kvm_memory_slot();
|
||||
let mem_region = kvm_userspace_memory_region {
|
||||
@@ -410,7 +682,7 @@ impl MemoryManager {
|
||||
guest_phys_addr,
|
||||
memory_size,
|
||||
userspace_addr,
|
||||
flags: 0,
|
||||
flags: if readonly { KVM_MEM_READONLY } else { 0 },
|
||||
};
|
||||
|
||||
// Safe because the guest regions are guaranteed not to overlap.
|
||||
@@ -450,14 +722,97 @@ impl MemoryManager {
|
||||
Ok(slot)
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, desired_ram: u64) -> Result<bool, Error> {
|
||||
if desired_ram > self.current_ram {
|
||||
self.hotplug_ram_region((desired_ram - self.current_ram) as usize)?;
|
||||
self.current_ram = desired_ram;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
pub fn remove_userspace_mapping(
|
||||
&mut self,
|
||||
guest_phys_addr: u64,
|
||||
memory_size: u64,
|
||||
userspace_addr: u64,
|
||||
mergeable: bool,
|
||||
slot: u32,
|
||||
) -> Result<(), Error> {
|
||||
let mem_region = kvm_userspace_memory_region {
|
||||
slot,
|
||||
guest_phys_addr,
|
||||
memory_size: 0,
|
||||
userspace_addr,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
// Safe to remove because we know the region exist.
|
||||
unsafe { self.fd.set_user_memory_region(mem_region) }
|
||||
.map_err(Error::SetUserMemoryRegion)?;
|
||||
|
||||
// Mark the pages as unmergeable if there were previously marked as
|
||||
// mergeable.
|
||||
if mergeable {
|
||||
// Safe because the address and size are valid as the region was
|
||||
// previously advised.
|
||||
let ret = unsafe {
|
||||
libc::madvise(
|
||||
userspace_addr as *mut libc::c_void,
|
||||
memory_size as libc::size_t,
|
||||
libc::MADV_UNMERGEABLE,
|
||||
)
|
||||
};
|
||||
if ret != 0 {
|
||||
let err = io::Error::last_os_error();
|
||||
// Safe to unwrap because the error is constructed with
|
||||
// last_os_error(), which ensures the output will be Some().
|
||||
let errno = err.raw_os_error().unwrap();
|
||||
if errno == libc::EINVAL {
|
||||
warn!("kernel not configured with CONFIG_KSM");
|
||||
} else {
|
||||
warn!("madvise error: {}", err);
|
||||
}
|
||||
warn!("failed to mark pages as unmergeable");
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
"Removed userspace mapping: {:x} -> {:x} {:x}",
|
||||
guest_phys_addr, userspace_addr, memory_size
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn virtiomem_resize(&mut self, size: u64) -> Result<(), Error> {
|
||||
let region = self.virtiomem_region.take();
|
||||
if let Some(region) = region {
|
||||
self.add_region(region)?;
|
||||
}
|
||||
|
||||
if let Some(resize) = &self.virtiomem_resize {
|
||||
resize.work(size).map_err(Error::VirtioMemResizeFail)?;
|
||||
} else {
|
||||
panic!("should not fail here");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// In case this function resulted in adding a new memory region to the
|
||||
/// guest memory, the new region is returned to the caller. The virtio-mem
|
||||
/// use case never adds a new region as the whole hotpluggable memory has
|
||||
/// already been allocated at boot time.
|
||||
pub fn resize(&mut self, desired_ram: u64) -> Result<Option<Arc<GuestRegionMmap>>, Error> {
|
||||
let mut region: Option<Arc<GuestRegionMmap>> = None;
|
||||
match self.hotplug_method {
|
||||
HotplugMethod::VirtioMem => {
|
||||
if desired_ram >= self.boot_ram {
|
||||
self.virtiomem_resize(desired_ram - self.boot_ram)?;
|
||||
self.current_ram = desired_ram;
|
||||
}
|
||||
}
|
||||
HotplugMethod::Acpi => {
|
||||
if desired_ram >= self.current_ram {
|
||||
region =
|
||||
Some(self.hotplug_ram_region((desired_ram - self.current_ram) as usize)?);
|
||||
self.current_ram = desired_ram;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(region)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -794,3 +1149,128 @@ impl Aml for MemoryManager {
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for MemoryManager {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "GuestAddress")]
|
||||
pub struct GuestAddressDef(pub u64);
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct MemoryRegion {
|
||||
backing_file: PathBuf,
|
||||
#[serde(with = "GuestAddressDef")]
|
||||
start_addr: GuestAddress,
|
||||
size: GuestUsize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct MemoryManagerSnapshotData {
|
||||
memory_regions: Vec<MemoryRegion>,
|
||||
}
|
||||
|
||||
impl Snapshottable for MemoryManager {
|
||||
fn id(&self) -> String {
|
||||
MEMORY_MANAGER_SNAPSHOT_ID.to_string()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> result::Result<Snapshot, MigratableError> {
|
||||
let mut memory_manager_snapshot = Snapshot::new(MEMORY_MANAGER_SNAPSHOT_ID);
|
||||
let guest_memory = self.guest_memory.memory();
|
||||
|
||||
let mut memory_regions: Vec<MemoryRegion> = Vec::with_capacity(10);
|
||||
|
||||
guest_memory.with_regions_mut(|index, region| {
|
||||
if region.len() == 0 {
|
||||
return Err(MigratableError::Snapshot(anyhow!("Zero length region")));
|
||||
}
|
||||
|
||||
memory_regions.push(MemoryRegion {
|
||||
backing_file: PathBuf::from(format!("memory-region-{}", index)),
|
||||
start_addr: region.start_addr(),
|
||||
size: region.len(),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let snapshot_data_section =
|
||||
serde_json::to_vec(&MemoryManagerSnapshotData { memory_regions })
|
||||
.map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
memory_manager_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", MEMORY_MANAGER_SNAPSHOT_ID),
|
||||
snapshot: snapshot_data_section,
|
||||
});
|
||||
|
||||
let mut memory_snapshot = self.snapshot.lock().unwrap();
|
||||
*memory_snapshot = Some(guest_memory);
|
||||
|
||||
Ok(memory_manager_snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
impl Transportable for MemoryManager {
|
||||
fn send(
|
||||
&self,
|
||||
_snapshot: &Snapshot,
|
||||
destination_url: &str,
|
||||
) -> result::Result<(), MigratableError> {
|
||||
let url = Url::parse(destination_url).map_err(|e| {
|
||||
MigratableError::MigrateSend(anyhow!("Could not parse destination URL: {}", e))
|
||||
})?;
|
||||
|
||||
match url.scheme() {
|
||||
"file" => {
|
||||
let vm_memory_snapshot_path = url
|
||||
.to_file_path()
|
||||
.map_err(|_| {
|
||||
MigratableError::MigrateSend(anyhow!(
|
||||
"Could not convert file URL to a file path"
|
||||
))
|
||||
})
|
||||
.and_then(|path| {
|
||||
if !path.is_dir() {
|
||||
return Err(MigratableError::MigrateSend(anyhow!(
|
||||
"Destination is not a directory"
|
||||
)));
|
||||
}
|
||||
Ok(path)
|
||||
})?;
|
||||
|
||||
if let Some(guest_memory) = &*self.snapshot.lock().unwrap() {
|
||||
guest_memory.with_regions_mut(|index, region| {
|
||||
let mut memory_region_path = vm_memory_snapshot_path.clone();
|
||||
memory_region_path.push(format!("memory-region-{}", index));
|
||||
|
||||
// Create the snapshot file for the region
|
||||
let mut memory_region_file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(memory_region_path)
|
||||
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
|
||||
|
||||
guest_memory
|
||||
.write_to(
|
||||
region.start_addr(),
|
||||
&mut memory_region_file,
|
||||
region.len().try_into().unwrap(),
|
||||
)
|
||||
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(MigratableError::MigrateSend(anyhow!(
|
||||
"Unsupported VM transport URL scheme: {}",
|
||||
url.scheme()
|
||||
)))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Migratable for MemoryManager {}
|
||||
|
||||
84
vmm/src/migration.rs
Normal file
84
vmm/src/migration.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::VmConfig;
|
||||
use crate::vm::{VmSnapshot, VM_SNAPSHOT_ID};
|
||||
use anyhow::anyhow;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use url::Url;
|
||||
use vm_migration::{MigratableError, Snapshot};
|
||||
|
||||
pub const VM_SNAPSHOT_FILE: &str = "vm.json";
|
||||
|
||||
pub fn url_to_path(url: &Url) -> std::result::Result<PathBuf, MigratableError> {
|
||||
match url.scheme() {
|
||||
"file" => url
|
||||
.to_file_path()
|
||||
.map_err(|_| {
|
||||
MigratableError::MigrateSend(anyhow!("Could not convert file URL to a file path"))
|
||||
})
|
||||
.and_then(|path| {
|
||||
if !path.is_dir() {
|
||||
return Err(MigratableError::MigrateSend(anyhow!(
|
||||
"Destination is not a directory"
|
||||
)));
|
||||
}
|
||||
Ok(path)
|
||||
}),
|
||||
|
||||
_ => Err(MigratableError::MigrateSend(anyhow!(
|
||||
"URL scheme is not file: {}",
|
||||
url.scheme()
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recv_vm_snapshot(source_url: &str) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let url = Url::parse(source_url).map_err(|e| {
|
||||
MigratableError::MigrateSend(anyhow!("Could not parse destination URL: {}", e))
|
||||
})?;
|
||||
|
||||
match url.scheme() {
|
||||
"file" => {
|
||||
let mut vm_snapshot_path = url_to_path(&url)?;
|
||||
vm_snapshot_path.push(VM_SNAPSHOT_FILE);
|
||||
|
||||
// Try opening the snapshot file
|
||||
let vm_snapshot_file =
|
||||
File::open(vm_snapshot_path).map_err(|e| MigratableError::MigrateSend(e.into()))?;
|
||||
let vm_snapshot_reader = BufReader::new(vm_snapshot_file);
|
||||
let vm_snapshot = serde_json::from_reader(vm_snapshot_reader)
|
||||
.map_err(|e| MigratableError::MigrateReceive(e.into()))?;
|
||||
|
||||
Ok(vm_snapshot)
|
||||
}
|
||||
_ => Err(MigratableError::MigrateSend(anyhow!(
|
||||
"Unsupported VM transport URL scheme: {}",
|
||||
url.scheme()
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vm_config_from_snapshot(
|
||||
snapshot: &Snapshot,
|
||||
) -> std::result::Result<Arc<Mutex<VmConfig>>, MigratableError> {
|
||||
if let Some(vm_section) = snapshot
|
||||
.snapshot_data
|
||||
.get(&format!("{}-section", VM_SNAPSHOT_ID))
|
||||
{
|
||||
let vm_snapshot: VmSnapshot =
|
||||
serde_json::from_slice(&vm_section.snapshot).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not deserialize VM snapshot {}", e))
|
||||
})?;
|
||||
|
||||
return Ok(vm_snapshot.config);
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find VM config snapshot section"
|
||||
)))
|
||||
}
|
||||
322
vmm/src/seccomp_filters.rs
Normal file
322
vmm/src/seccomp_filters.rs
Normal file
@@ -0,0 +1,322 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use libc;
|
||||
use seccomp::{
|
||||
allow_syscall, allow_syscall_if, BpfProgram, Error, SeccompAction, SeccompCmpArgLen as ArgLen,
|
||||
SeccompCmpOp::Eq, SeccompCondition as Cond, SeccompError, SeccompFilter, SeccompLevel,
|
||||
SeccompRule,
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub enum Thread {
|
||||
Vmm,
|
||||
Api,
|
||||
}
|
||||
|
||||
/// Shorthand for chaining `SeccompCondition`s with the `and` operator in a `SeccompRule`.
|
||||
/// The rule will take the `Allow` action if _all_ the conditions are true.
|
||||
///
|
||||
/// [`Allow`]: enum.SeccompAction.html
|
||||
/// [`SeccompCondition`]: struct.SeccompCondition.html
|
||||
/// [`SeccompRule`]: struct.SeccompRule.html
|
||||
macro_rules! and {
|
||||
($($x:expr,)*) => (SeccompRule::new(vec![$($x),*], SeccompAction::Allow));
|
||||
($($x:expr),*) => (SeccompRule::new(vec![$($x),*], SeccompAction::Allow))
|
||||
}
|
||||
|
||||
/// Shorthand for chaining `SeccompRule`s with the `or` operator in a `SeccompFilter`.
|
||||
///
|
||||
/// [`SeccompFilter`]: struct.SeccompFilter.html
|
||||
/// [`SeccompRule`]: struct.SeccompRule.html
|
||||
macro_rules! or {
|
||||
($($x:expr,)*) => (vec![$($x),*]);
|
||||
($($x:expr),*) => (vec![$($x),*])
|
||||
}
|
||||
|
||||
// See include/uapi/asm-generic/ioctls.h in the kernel code.
|
||||
const TCGETS: u64 = 0x5401;
|
||||
const TCSETS: u64 = 0x5402;
|
||||
const TIOCGWINSZ: u64 = 0x5413;
|
||||
const FIOCLEX: u64 = 0x5451;
|
||||
const FIONBIO: u64 = 0x5421;
|
||||
|
||||
// See include/uapi/linux/kvm.h in the kernel code.
|
||||
const KVM_GET_API_VERSION: u64 = 0xae00;
|
||||
const KVM_CREATE_VM: u64 = 0xae01;
|
||||
const KVM_CHECK_EXTENSION: u64 = 0xae03;
|
||||
const KVM_GET_VCPU_MMAP_SIZE: u64 = 0xae04;
|
||||
const KVM_CREATE_VCPU: u64 = 0xae41;
|
||||
const KVM_SET_TSS_ADDR: u64 = 0xae47;
|
||||
const KVM_CREATE_IRQCHIP: u64 = 0xae60;
|
||||
const KVM_RUN: u64 = 0xae80;
|
||||
const KVM_SET_MP_STATE: u64 = 0x4004_ae99;
|
||||
const KVM_SET_GSI_ROUTING: u64 = 0x4008_ae6a;
|
||||
const KVM_SET_MSRS: u64 = 0x4008_ae89;
|
||||
const KVM_SET_CPUID2: u64 = 0x4008_ae90;
|
||||
const KVM_SET_DEVICE_ATTR: u64 = 0x4018_aee1;
|
||||
const KVM_SET_USER_MEMORY_REGION: u64 = 0x4020_ae46;
|
||||
const KVM_IRQFD: u64 = 0x4020_ae76;
|
||||
const KVM_CREATE_PIT2: u64 = 0x4040_ae77;
|
||||
const KVM_IOEVENTFD: u64 = 0x4040_ae79;
|
||||
const KVM_ENABLE_CAP: u64 = 0x4068_aea3;
|
||||
const KVM_SET_REGS: u64 = 0x4090_ae82;
|
||||
const KVM_SET_SREGS: u64 = 0x4138_ae84;
|
||||
const KVM_SET_XCRS: u64 = 0x4188_aea7;
|
||||
const KVM_SET_FPU: u64 = 0x41a0_ae8d;
|
||||
const KVM_SET_LAPIC: u64 = 0x4400_ae8f;
|
||||
const KVM_SET_XSAVE: u64 = 0x5000_aea5;
|
||||
const KVM_GET_MP_STATE: u64 = 0x8004_ae98;
|
||||
const KVM_GET_VCPU_EVENTS: u64 = 0x8040_ae9f;
|
||||
const KVM_GET_REGS: u64 = 0x8090_ae81;
|
||||
const KVM_GET_SREGS: u64 = 0x8138_ae83;
|
||||
const KVM_GET_XCRS: u64 = 0x8188_aea6;
|
||||
const KVM_GET_FPU: u64 = 0x81a0_ae8c;
|
||||
const KVM_GET_LAPIC: u64 = 0x8400_ae8e;
|
||||
const KVM_GET_XSAVE: u64 = 0x9000_aea4;
|
||||
const KVM_GET_SUPPORTED_CPUID: u64 = 0xc008_ae05;
|
||||
const KVM_GET_MSRS: u64 = 0xc008_ae88;
|
||||
const KVM_CREATE_DEVICE: u64 = 0xc00c_aee0;
|
||||
|
||||
// See include/uapi/linux/if_tun.h in the kernel code.
|
||||
const TUNSETIFF: u64 = 0x4004_54ca;
|
||||
const TUNSETOFFLOAD: u64 = 0x4004_54d0;
|
||||
const TUNSETVNETHDRSZ: u64 = 0x4004_54d8;
|
||||
const TUNGETFEATURES: u64 = 0x8004_54cf;
|
||||
|
||||
// See include/uapi/linux/sockios.h in the kernel code.
|
||||
const SIOCSIFFLAGS: u64 = 0x8914;
|
||||
const SIOCSIFADDR: u64 = 0x8916;
|
||||
const SIOCSIFNETMASK: u64 = 0x891c;
|
||||
|
||||
// See include/uapi/linux/vfio.h in the kernel code.
|
||||
const VFIO_GET_API_VERSION: u64 = 0x3b64;
|
||||
const VFIO_CHECK_EXTENSION: u64 = 0x3b65;
|
||||
const VFIO_SET_IOMMU: u64 = 0x3b66;
|
||||
const VFIO_GROUP_GET_STATUS: u64 = 0x3b67;
|
||||
const VFIO_GROUP_SET_CONTAINER: u64 = 0x3b68;
|
||||
const VFIO_GROUP_UNSET_CONTAINER: u64 = 0x3b69;
|
||||
const VFIO_GROUP_GET_DEVICE_FD: u64 = 0x3b6a;
|
||||
const VFIO_DEVICE_GET_INFO: u64 = 0x3b6b;
|
||||
const VFIO_DEVICE_GET_REGION_INFO: u64 = 0x3b6c;
|
||||
const VFIO_DEVICE_GET_IRQ_INFO: u64 = 0x3b6d;
|
||||
const VFIO_DEVICE_SET_IRQS: u64 = 0x3b6e;
|
||||
const VFIO_DEVICE_RESET: u64 = 0x3b6f;
|
||||
const VFIO_IOMMU_MAP_DMA: u64 = 0x3b71;
|
||||
const VFIO_IOMMU_UNMAP_DMA: u64 = 0x3b72;
|
||||
const VFIO_DEVICE_IOEVENTFD: u64 = 0x3b74;
|
||||
|
||||
fn create_vmm_ioctl_seccomp_rule() -> Result<Vec<SeccompRule>, Error> {
|
||||
Ok(or![
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, FIOCLEX)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, FIONBIO)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_CHECK_EXTENSION,)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_CREATE_DEVICE,)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_CREATE_IRQCHIP,)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_CREATE_PIT2)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_CREATE_VCPU)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_CREATE_VM)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_ENABLE_CAP)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_API_VERSION,)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_FPU)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_LAPIC)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_MP_STATE)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_MSRS)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_REGS)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_SREGS)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_SUPPORTED_CPUID,)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_VCPU_EVENTS,)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_VCPU_MMAP_SIZE,)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_XSAVE,)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_GET_XCRS,)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_IOEVENTFD)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_IRQFD)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_RUN)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_CPUID2)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_DEVICE_ATTR,)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_FPU)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_GSI_ROUTING)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_LAPIC)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_MP_STATE)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_MSRS)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_REGS)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_SREGS)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_TSS_ADDR,)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_USER_MEMORY_REGION,)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_XSAVE,)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, KVM_SET_XCRS,)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, SIOCSIFADDR)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, SIOCSIFFLAGS)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, SIOCSIFNETMASK)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, TCSETS)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, TCGETS)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, TIOCGWINSZ)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, TUNGETFEATURES)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, TUNSETIFF)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, TUNSETOFFLOAD)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, TUNSETVNETHDRSZ)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_GET_API_VERSION)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_CHECK_EXTENSION)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_SET_IOMMU)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_GROUP_GET_STATUS)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_GROUP_SET_CONTAINER)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_GROUP_UNSET_CONTAINER)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_GROUP_GET_DEVICE_FD)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_DEVICE_GET_INFO)?],
|
||||
and![Cond::new(
|
||||
1,
|
||||
ArgLen::DWORD,
|
||||
Eq,
|
||||
VFIO_DEVICE_GET_REGION_INFO
|
||||
)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_DEVICE_GET_IRQ_INFO)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_DEVICE_SET_IRQS)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_DEVICE_RESET)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_IOMMU_MAP_DMA)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_IOMMU_UNMAP_DMA)?],
|
||||
and![Cond::new(1, ArgLen::DWORD, Eq, VFIO_DEVICE_IOEVENTFD)?],
|
||||
])
|
||||
}
|
||||
|
||||
fn create_api_ioctl_seccomp_rule() -> Result<Vec<SeccompRule>, Error> {
|
||||
Ok(or![and![Cond::new(1, ArgLen::DWORD, Eq, FIONBIO)?],])
|
||||
}
|
||||
|
||||
/// The filter containing the white listed syscall rules required by the VMM to
|
||||
/// function.
|
||||
pub fn vmm_thread_filter() -> Result<SeccompFilter, Error> {
|
||||
Ok(SeccompFilter::new(
|
||||
vec![
|
||||
allow_syscall(libc::SYS_accept4),
|
||||
allow_syscall(libc::SYS_access),
|
||||
allow_syscall(libc::SYS_arch_prctl),
|
||||
allow_syscall(libc::SYS_bind),
|
||||
allow_syscall(libc::SYS_brk),
|
||||
allow_syscall(libc::SYS_clock_gettime),
|
||||
allow_syscall(libc::SYS_clone),
|
||||
allow_syscall(libc::SYS_close),
|
||||
allow_syscall(libc::SYS_connect),
|
||||
allow_syscall(libc::SYS_dup),
|
||||
allow_syscall(libc::SYS_epoll_create1),
|
||||
allow_syscall(libc::SYS_epoll_ctl),
|
||||
allow_syscall(libc::SYS_epoll_pwait),
|
||||
allow_syscall(libc::SYS_epoll_wait),
|
||||
allow_syscall(libc::SYS_eventfd2),
|
||||
allow_syscall(libc::SYS_execve),
|
||||
allow_syscall(libc::SYS_exit),
|
||||
allow_syscall(libc::SYS_exit_group),
|
||||
allow_syscall(libc::SYS_fallocate),
|
||||
allow_syscall(libc::SYS_fcntl),
|
||||
allow_syscall(libc::SYS_fdatasync),
|
||||
allow_syscall(libc::SYS_fork),
|
||||
allow_syscall(libc::SYS_fstat),
|
||||
allow_syscall(libc::SYS_fsync),
|
||||
allow_syscall(libc::SYS_ftruncate),
|
||||
allow_syscall(libc::SYS_futex),
|
||||
allow_syscall(libc::SYS_getpid),
|
||||
allow_syscall(libc::SYS_getrandom),
|
||||
allow_syscall(libc::SYS_gettid),
|
||||
allow_syscall(libc::SYS_gettimeofday),
|
||||
allow_syscall(libc::SYS_getuid),
|
||||
allow_syscall_if(libc::SYS_ioctl, create_vmm_ioctl_seccomp_rule()?),
|
||||
allow_syscall(libc::SYS_listen),
|
||||
allow_syscall(libc::SYS_lseek),
|
||||
allow_syscall(libc::SYS_madvise),
|
||||
allow_syscall(libc::SYS_memfd_create),
|
||||
allow_syscall(libc::SYS_mmap),
|
||||
allow_syscall(libc::SYS_mprotect),
|
||||
allow_syscall(libc::SYS_mremap),
|
||||
allow_syscall(libc::SYS_munmap),
|
||||
allow_syscall(libc::SYS_nanosleep),
|
||||
allow_syscall(libc::SYS_open),
|
||||
allow_syscall(libc::SYS_openat),
|
||||
allow_syscall(libc::SYS_pipe2),
|
||||
allow_syscall(libc::SYS_prctl),
|
||||
allow_syscall(libc::SYS_pread64),
|
||||
allow_syscall(libc::SYS_prlimit64),
|
||||
allow_syscall(libc::SYS_pwrite64),
|
||||
allow_syscall(libc::SYS_read),
|
||||
allow_syscall(libc::SYS_readlink),
|
||||
allow_syscall(libc::SYS_recvfrom),
|
||||
allow_syscall(libc::SYS_recvmsg),
|
||||
allow_syscall(libc::SYS_restart_syscall),
|
||||
allow_syscall(libc::SYS_rt_sigaction),
|
||||
allow_syscall(libc::SYS_rt_sigprocmask),
|
||||
allow_syscall(libc::SYS_rt_sigreturn),
|
||||
allow_syscall(libc::SYS_sched_getaffinity),
|
||||
allow_syscall(libc::SYS_sendmsg),
|
||||
allow_syscall(libc::SYS_set_robust_list),
|
||||
allow_syscall(libc::SYS_set_tid_address),
|
||||
allow_syscall(libc::SYS_sigaltstack),
|
||||
allow_syscall_if(
|
||||
libc::SYS_socket,
|
||||
or![
|
||||
and![Cond::new(0, ArgLen::DWORD, Eq, libc::AF_UNIX as u64)?],
|
||||
and![Cond::new(0, ArgLen::DWORD, Eq, libc::AF_INET as u64)?],
|
||||
],
|
||||
),
|
||||
allow_syscall(libc::SYS_socketpair),
|
||||
allow_syscall(libc::SYS_stat),
|
||||
allow_syscall(libc::SYS_statx),
|
||||
allow_syscall(libc::SYS_tgkill),
|
||||
allow_syscall(libc::SYS_tkill),
|
||||
allow_syscall(libc::SYS_unlink),
|
||||
allow_syscall(libc::SYS_wait4),
|
||||
allow_syscall(libc::SYS_write),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
SeccompAction::Trap,
|
||||
)?)
|
||||
}
|
||||
|
||||
/// The filter containing the white listed syscall rules required by the API to
|
||||
/// function.
|
||||
pub fn api_thread_filter() -> Result<SeccompFilter, Error> {
|
||||
Ok(SeccompFilter::new(
|
||||
vec![
|
||||
allow_syscall(libc::SYS_accept4),
|
||||
allow_syscall(libc::SYS_bind),
|
||||
allow_syscall(libc::SYS_close),
|
||||
allow_syscall(libc::SYS_dup),
|
||||
allow_syscall(libc::SYS_epoll_create1),
|
||||
allow_syscall(libc::SYS_epoll_ctl),
|
||||
allow_syscall(libc::SYS_epoll_pwait),
|
||||
allow_syscall(libc::SYS_epoll_wait),
|
||||
allow_syscall(libc::SYS_exit),
|
||||
allow_syscall(libc::SYS_futex),
|
||||
allow_syscall(libc::SYS_getrandom),
|
||||
allow_syscall_if(libc::SYS_ioctl, create_api_ioctl_seccomp_rule()?),
|
||||
allow_syscall(libc::SYS_listen),
|
||||
allow_syscall(libc::SYS_madvise),
|
||||
allow_syscall(libc::SYS_munmap),
|
||||
allow_syscall(libc::SYS_recvfrom),
|
||||
allow_syscall(libc::SYS_sigaltstack),
|
||||
allow_syscall(libc::SYS_socket),
|
||||
allow_syscall(libc::SYS_write),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
SeccompAction::Trap,
|
||||
)?)
|
||||
}
|
||||
|
||||
/// Generate a BPF program based on a seccomp level value.
|
||||
pub fn get_seccomp_filter(
|
||||
seccomp_level: &SeccompLevel,
|
||||
thread_type: Thread,
|
||||
) -> Result<BpfProgram, SeccompError> {
|
||||
let filter = match thread_type {
|
||||
Thread::Vmm => vmm_thread_filter(),
|
||||
Thread::Api => api_thread_filter(),
|
||||
};
|
||||
match *seccomp_level {
|
||||
SeccompLevel::None => Ok(vec![]),
|
||||
_ => filter
|
||||
.and_then(|filter| filter.try_into())
|
||||
.map_err(SeccompError::SeccompFilter),
|
||||
}
|
||||
}
|
||||
721
vmm/src/vm.rs
721
vmm/src/vm.rs
@@ -11,7 +11,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
//
|
||||
|
||||
extern crate anyhow;
|
||||
extern crate arch;
|
||||
extern crate devices;
|
||||
extern crate epoll;
|
||||
@@ -26,40 +25,45 @@ extern crate vm_allocator;
|
||||
extern crate vm_memory;
|
||||
extern crate vm_virtio;
|
||||
|
||||
use crate::config::{DeviceConfig, VmConfig};
|
||||
use crate::config::{
|
||||
DeviceConfig, DiskConfig, FsConfig, HotplugMethod, NetConfig, PmemConfig, ValidationError,
|
||||
VmConfig, VsockConfig,
|
||||
};
|
||||
use crate::cpu;
|
||||
use crate::device_manager::{get_win_size, Console, DeviceManager, DeviceManagerError};
|
||||
use crate::memory_manager::{get_host_cpu_phys_bits, Error as MemoryManagerError, MemoryManager};
|
||||
use crate::memory_manager::{Error as MemoryManagerError, MemoryManager};
|
||||
use crate::migration::{url_to_path, vm_config_from_snapshot, VM_SNAPSHOT_FILE};
|
||||
use crate::{CPU_MANAGER_SNAPSHOT_ID, DEVICE_MANAGER_SNAPSHOT_ID, MEMORY_MANAGER_SNAPSHOT_ID};
|
||||
use anyhow::anyhow;
|
||||
use arch::{layout, BootProtocol, EntryPoint};
|
||||
use arch::{BootProtocol, EntryPoint};
|
||||
use devices::{ioapic, HotPlugNotificationFlags};
|
||||
use kvm_bindings::{kvm_enable_cap, kvm_userspace_memory_region, KVM_CAP_SPLIT_IRQCHIP};
|
||||
use kvm_ioctls::*;
|
||||
use linux_loader::cmdline::Cmdline;
|
||||
use linux_loader::loader::elf::Error::InvalidElfMagicNumber;
|
||||
use linux_loader::loader::KernelLoader;
|
||||
use signal_hook::{iterator::Signals, SIGINT, SIGTERM, SIGWINCH};
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::CString;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::io::{self, Seek, SeekFrom};
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::{result, str, thread};
|
||||
use vm_allocator::{GsiApic, SystemAllocator};
|
||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
||||
use url::Url;
|
||||
use vm_memory::{
|
||||
Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryMmap,
|
||||
GuestMemoryRegion, GuestUsize,
|
||||
GuestMemoryRegion,
|
||||
};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::terminal::Terminal;
|
||||
|
||||
const X86_64_IRQ_BASE: u32 = 5;
|
||||
|
||||
// CPUID feature bits
|
||||
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
||||
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
|
||||
|
||||
// 64 bit direct boot entry offset for bzImage
|
||||
const KERNEL_64BIT_ENTRY_OFFSET: u64 = 0x200;
|
||||
|
||||
@@ -78,9 +82,15 @@ pub enum Error {
|
||||
/// Cannot open the kernel image
|
||||
KernelFile(io::Error),
|
||||
|
||||
/// Cannot open the initramfs image
|
||||
InitramfsFile(io::Error),
|
||||
|
||||
/// Cannot load the kernel in memory
|
||||
KernelLoad(linux_loader::loader::Error),
|
||||
|
||||
/// Cannot load the initramfs in memory
|
||||
InitramfsLoad,
|
||||
|
||||
/// Cannot load the command line in memory
|
||||
LoadCmdLine(linux_loader::loader::Error),
|
||||
|
||||
@@ -107,9 +117,6 @@ pub enum Error {
|
||||
/// Cannot setup terminal in canonical mode.
|
||||
SetTerminalCanon(vmm_sys_util::errno::Error),
|
||||
|
||||
/// Cannot create the system allocator
|
||||
CreateSystemAllocator,
|
||||
|
||||
/// Failed parsing network parameters
|
||||
ParseNetworkParameters,
|
||||
|
||||
@@ -131,6 +138,9 @@ pub enum Error {
|
||||
/// VM is not created
|
||||
VmNotCreated,
|
||||
|
||||
/// VM is already created
|
||||
VmAlreadyCreated,
|
||||
|
||||
/// VM is not running
|
||||
VmNotRunning,
|
||||
|
||||
@@ -169,6 +179,27 @@ pub enum Error {
|
||||
|
||||
/// No PCI support
|
||||
NoPciSupport,
|
||||
|
||||
/// Eventfd write error
|
||||
EventfdError(std::io::Error),
|
||||
|
||||
/// Cannot snapshot VM
|
||||
Snapshot(MigratableError),
|
||||
|
||||
/// Cannot restore VM
|
||||
Restore(MigratableError),
|
||||
|
||||
/// Cannot send VM snapshot
|
||||
SnapshotSend(MigratableError),
|
||||
|
||||
/// Cannot convert source URL from Path into &str
|
||||
RestoreSourceUrlPathToStr,
|
||||
|
||||
/// Failed to validate config
|
||||
ConfigValidation(ValidationError),
|
||||
|
||||
/// No more that one virtio-vsock device
|
||||
TooManyVsockDevices,
|
||||
}
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
@@ -216,6 +247,7 @@ impl VmState {
|
||||
|
||||
pub struct Vm {
|
||||
kernel: File,
|
||||
initramfs: Option<File>,
|
||||
threads: Vec<thread::JoinHandle<()>>,
|
||||
device_manager: Arc<Mutex<DeviceManager>>,
|
||||
config: Arc<Mutex<VmConfig>>,
|
||||
@@ -227,12 +259,7 @@ pub struct Vm {
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
pub fn new(
|
||||
config: Arc<Mutex<VmConfig>>,
|
||||
exit_evt: EventFd,
|
||||
reset_evt: EventFd,
|
||||
vmm_path: PathBuf,
|
||||
) -> Result<Self> {
|
||||
fn kvm_new() -> Result<(Kvm, Arc<VmFd>)> {
|
||||
let kvm = Kvm::new().map_err(Error::KvmNew)?;
|
||||
|
||||
// Check required capabilities:
|
||||
@@ -248,9 +275,6 @@ impl Vm {
|
||||
return Err(Error::CapabilityMissing(Cap::SplitIrqchip));
|
||||
}
|
||||
|
||||
let kernel = File::open(&config.lock().unwrap().kernel.as_ref().unwrap().path)
|
||||
.map_err(Error::KernelFile)?;
|
||||
|
||||
let fd: VmFd;
|
||||
loop {
|
||||
match kvm.create_vm() {
|
||||
@@ -274,7 +298,6 @@ impl Vm {
|
||||
fd.set_tss_address(arch::x86_64::layout::KVM_TSS_ADDRESS.raw_value() as usize)
|
||||
.map_err(Error::VmSetup)?;
|
||||
|
||||
let mut cpuid_patches = Vec::new();
|
||||
// Create split irqchip
|
||||
// Only the local APIC is emulated in kernel, both PICs and IOAPIC
|
||||
// are not.
|
||||
@@ -283,72 +306,27 @@ impl Vm {
|
||||
cap.args[0] = ioapic::NUM_IOAPIC_PINS as u64;
|
||||
fd.enable_cap(&cap).map_err(Error::VmSetup)?;
|
||||
|
||||
// Patch tsc deadline timer bit
|
||||
cpuid_patches.push(cpu::CpuidPatch {
|
||||
function: 1,
|
||||
index: 0,
|
||||
flags_bit: None,
|
||||
eax_bit: None,
|
||||
ebx_bit: None,
|
||||
ecx_bit: Some(TSC_DEADLINE_TIMER_ECX_BIT),
|
||||
edx_bit: None,
|
||||
});
|
||||
Ok((kvm, fd))
|
||||
}
|
||||
|
||||
// Patch hypervisor bit
|
||||
cpuid_patches.push(cpu::CpuidPatch {
|
||||
function: 1,
|
||||
index: 0,
|
||||
flags_bit: None,
|
||||
eax_bit: None,
|
||||
ebx_bit: None,
|
||||
ecx_bit: Some(HYPERVISOR_ECX_BIT),
|
||||
edx_bit: None,
|
||||
});
|
||||
|
||||
// Supported CPUID
|
||||
let mut cpuid = kvm
|
||||
.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
|
||||
.map_err(Error::VmSetup)?;
|
||||
|
||||
cpu::CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches);
|
||||
|
||||
let ioapic = GsiApic::new(
|
||||
X86_64_IRQ_BASE,
|
||||
ioapic::NUM_IOAPIC_PINS as u32 - X86_64_IRQ_BASE,
|
||||
);
|
||||
|
||||
// Let's allocate 64 GiB of addressable MMIO space, starting at 0.
|
||||
let allocator = Arc::new(Mutex::new(
|
||||
SystemAllocator::new(
|
||||
GuestAddress(0),
|
||||
1 << 16 as GuestUsize,
|
||||
GuestAddress(0),
|
||||
1 << get_host_cpu_phys_bits(),
|
||||
layout::MEM_32BIT_RESERVED_START,
|
||||
layout::MEM_32BIT_DEVICES_SIZE,
|
||||
vec![ioapic],
|
||||
)
|
||||
.ok_or(Error::CreateSystemAllocator)?,
|
||||
));
|
||||
|
||||
let memory_config = config.lock().unwrap().memory.clone();
|
||||
|
||||
let memory_manager = MemoryManager::new(
|
||||
allocator.clone(),
|
||||
fd.clone(),
|
||||
memory_config.size,
|
||||
memory_config.hotplug_size,
|
||||
&memory_config.file,
|
||||
memory_config.mergeable,
|
||||
)
|
||||
.map_err(Error::MemoryManager)?;
|
||||
|
||||
let guest_memory = memory_manager.lock().unwrap().guest_memory();
|
||||
fn new_from_memory_manager(
|
||||
config: Arc<Mutex<VmConfig>>,
|
||||
memory_manager: Arc<Mutex<MemoryManager>>,
|
||||
fd: Arc<VmFd>,
|
||||
kvm: Kvm,
|
||||
exit_evt: EventFd,
|
||||
reset_evt: EventFd,
|
||||
vmm_path: PathBuf,
|
||||
) -> Result<Self> {
|
||||
config
|
||||
.lock()
|
||||
.unwrap()
|
||||
.validate()
|
||||
.map_err(Error::ConfigValidation)?;
|
||||
|
||||
let device_manager = DeviceManager::new(
|
||||
fd.clone(),
|
||||
config.clone(),
|
||||
allocator,
|
||||
memory_manager.clone(),
|
||||
&exit_evt,
|
||||
&reset_evt,
|
||||
@@ -356,23 +334,32 @@ impl Vm {
|
||||
)
|
||||
.map_err(Error::DeviceManager)?;
|
||||
|
||||
let on_tty = unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0;
|
||||
|
||||
let boot_vcpus = config.lock().unwrap().cpus.boot_vcpus;
|
||||
let max_vcpus = config.lock().unwrap().cpus.max_vcpus;
|
||||
let cpu_manager = cpu::CpuManager::new(
|
||||
boot_vcpus,
|
||||
max_vcpus,
|
||||
&config.lock().unwrap().cpus.clone(),
|
||||
&device_manager,
|
||||
guest_memory,
|
||||
memory_manager.lock().unwrap().guest_memory(),
|
||||
&kvm,
|
||||
fd,
|
||||
cpuid,
|
||||
reset_evt,
|
||||
)
|
||||
.map_err(Error::CpuManager)?;
|
||||
|
||||
let on_tty = unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0;
|
||||
let kernel = File::open(&config.lock().unwrap().kernel.as_ref().unwrap().path)
|
||||
.map_err(Error::KernelFile)?;
|
||||
|
||||
let initramfs = config
|
||||
.lock()
|
||||
.unwrap()
|
||||
.initramfs
|
||||
.as_ref()
|
||||
.map(|i| File::open(&i.path))
|
||||
.transpose()
|
||||
.map_err(Error::InitramfsFile)?;
|
||||
|
||||
Ok(Vm {
|
||||
kernel,
|
||||
initramfs,
|
||||
device_manager,
|
||||
config,
|
||||
on_tty,
|
||||
@@ -384,6 +371,93 @@ impl Vm {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
config: Arc<Mutex<VmConfig>>,
|
||||
exit_evt: EventFd,
|
||||
reset_evt: EventFd,
|
||||
vmm_path: PathBuf,
|
||||
) -> Result<Self> {
|
||||
let (kvm, fd) = Vm::kvm_new()?;
|
||||
let memory_manager = MemoryManager::new(
|
||||
fd.clone(),
|
||||
&config.lock().unwrap().memory.clone(),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.map_err(Error::MemoryManager)?;
|
||||
|
||||
Vm::new_from_memory_manager(
|
||||
config,
|
||||
memory_manager,
|
||||
fd,
|
||||
kvm,
|
||||
exit_evt,
|
||||
reset_evt,
|
||||
vmm_path,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_from_snapshot(
|
||||
snapshot: &Snapshot,
|
||||
exit_evt: EventFd,
|
||||
reset_evt: EventFd,
|
||||
vmm_path: PathBuf,
|
||||
source_url: &str,
|
||||
prefault: bool,
|
||||
) -> Result<Self> {
|
||||
let (kvm, fd) = Vm::kvm_new()?;
|
||||
let config = vm_config_from_snapshot(snapshot).map_err(Error::Restore)?;
|
||||
|
||||
let memory_manager = if let Some(memory_manager_snapshot) =
|
||||
snapshot.snapshots.get(MEMORY_MANAGER_SNAPSHOT_ID)
|
||||
{
|
||||
MemoryManager::new_from_snapshot(
|
||||
memory_manager_snapshot,
|
||||
fd.clone(),
|
||||
&config.lock().unwrap().memory.clone(),
|
||||
source_url,
|
||||
prefault,
|
||||
)
|
||||
.map_err(Error::MemoryManager)?
|
||||
} else {
|
||||
return Err(Error::Restore(MigratableError::Restore(anyhow!(
|
||||
"Missing memory manager snapshot"
|
||||
))));
|
||||
};
|
||||
|
||||
Vm::new_from_memory_manager(
|
||||
config,
|
||||
memory_manager,
|
||||
fd,
|
||||
kvm,
|
||||
exit_evt,
|
||||
reset_evt,
|
||||
vmm_path,
|
||||
)
|
||||
}
|
||||
|
||||
fn load_initramfs(&mut self, guest_mem: &GuestMemoryMmap) -> Result<arch::InitramfsConfig> {
|
||||
let mut initramfs = self.initramfs.as_ref().unwrap();
|
||||
let size: usize = initramfs
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(|_| Error::InitramfsLoad)?
|
||||
.try_into()
|
||||
.unwrap();
|
||||
initramfs
|
||||
.seek(SeekFrom::Start(0))
|
||||
.map_err(|_| Error::InitramfsLoad)?;
|
||||
|
||||
let address =
|
||||
arch::initramfs_load_addr(guest_mem, size).map_err(|_| Error::InitramfsLoad)?;
|
||||
let address = GuestAddress(address);
|
||||
|
||||
guest_mem
|
||||
.read_from(address, &mut initramfs, size)
|
||||
.map_err(|_| Error::InitramfsLoad)?;
|
||||
|
||||
Ok(arch::InitramfsConfig { address, size })
|
||||
}
|
||||
|
||||
fn load_kernel(&mut self) -> Result<EntryPoint> {
|
||||
let mut cmdline = Cmdline::new(arch::CMDLINE_MAX_SIZE);
|
||||
cmdline
|
||||
@@ -396,15 +470,15 @@ impl Vm {
|
||||
let cmdline_cstring = CString::new(cmdline).map_err(Error::CmdLineCString)?;
|
||||
let guest_memory = self.memory_manager.lock().as_ref().unwrap().guest_memory();
|
||||
let mem = guest_memory.memory();
|
||||
let entry_addr = match linux_loader::loader::Elf::load(
|
||||
let entry_addr = match linux_loader::loader::elf::Elf::load(
|
||||
mem.deref(),
|
||||
None,
|
||||
&mut self.kernel,
|
||||
Some(arch::layout::HIGH_RAM_START),
|
||||
) {
|
||||
Ok(entry_addr) => entry_addr,
|
||||
Err(linux_loader::loader::Error::InvalidElfMagicNumber) => {
|
||||
linux_loader::loader::BzImage::load(
|
||||
Err(linux_loader::loader::Error::Elf(InvalidElfMagicNumber)) => {
|
||||
linux_loader::loader::bzimage::BzImage::load(
|
||||
mem.deref(),
|
||||
None,
|
||||
&mut self.kernel,
|
||||
@@ -412,7 +486,9 @@ impl Vm {
|
||||
)
|
||||
.map_err(Error::KernelLoad)?
|
||||
}
|
||||
_ => panic!("Invalid elf file"),
|
||||
Err(e) => {
|
||||
return Err(Error::KernelLoad(e));
|
||||
}
|
||||
};
|
||||
|
||||
linux_loader::loader::load_cmdline(
|
||||
@@ -421,6 +497,12 @@ impl Vm {
|
||||
&cmdline_cstring,
|
||||
)
|
||||
.map_err(Error::LoadCmdLine)?;
|
||||
|
||||
let initramfs_config = match self.initramfs {
|
||||
Some(_) => Some(self.load_initramfs(mem.deref())?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let boot_vcpus = self.cpu_manager.lock().unwrap().boot_vcpus();
|
||||
let _max_vcpus = self.cpu_manager.lock().unwrap().max_vcpus();
|
||||
|
||||
@@ -443,6 +525,7 @@ impl Vm {
|
||||
&mem,
|
||||
arch::layout::CMDLINE_START,
|
||||
cmdline_cstring.to_bytes().len() + 1,
|
||||
&initramfs_config,
|
||||
boot_vcpus,
|
||||
Some(hdr),
|
||||
rsdp_addr,
|
||||
@@ -462,21 +545,24 @@ impl Vm {
|
||||
})
|
||||
}
|
||||
None => {
|
||||
// Assume by default Linux boot protocol is used and not PVH
|
||||
let mut entry_point_addr: GuestAddress = entry_addr.kernel_load;
|
||||
let entry_point_addr: GuestAddress;
|
||||
let boot_prot: BootProtocol;
|
||||
|
||||
let boot_prot = if entry_addr.pvh_entry_addr.is_some() {
|
||||
// entry_addr.pvh_entry_addr field is safe to unwrap here
|
||||
entry_point_addr = entry_addr.pvh_entry_addr.unwrap();
|
||||
BootProtocol::PvhBoot
|
||||
if let Some(pvh_entry_addr) = entry_addr.pvh_entry_addr {
|
||||
// Use the PVH kernel entry point to boot the guest
|
||||
entry_point_addr = pvh_entry_addr;
|
||||
boot_prot = BootProtocol::PvhBoot;
|
||||
} else {
|
||||
BootProtocol::LinuxBoot
|
||||
};
|
||||
// Use the Linux 64-bit boot protocol
|
||||
entry_point_addr = entry_addr.kernel_load;
|
||||
boot_prot = BootProtocol::LinuxBoot;
|
||||
}
|
||||
|
||||
arch::configure_system(
|
||||
&mem,
|
||||
arch::layout::CMDLINE_START,
|
||||
cmdline_cstring.to_bytes().len() + 1,
|
||||
&initramfs_config,
|
||||
boot_vcpus,
|
||||
None,
|
||||
rsdp_addr,
|
||||
@@ -493,10 +579,15 @@ impl Vm {
|
||||
}
|
||||
|
||||
pub fn shutdown(&mut self) -> Result<()> {
|
||||
let mut state = self.state.try_write().map_err(|_| Error::PoisonedState)?;
|
||||
let current_state = self.get_state()?;
|
||||
let new_state = VmState::Shutdown;
|
||||
|
||||
state.valid_transition(new_state)?;
|
||||
current_state.valid_transition(new_state)?;
|
||||
// If the current state is paused that means most of the handles got killed by pthread_kill
|
||||
// We need to unpark those threads by calling resume
|
||||
if current_state == VmState::Paused {
|
||||
self.resume().map_err(Error::Resume)?;
|
||||
}
|
||||
|
||||
if self.on_tty {
|
||||
// Don't forget to set the terminal in canonical mode
|
||||
@@ -522,6 +613,8 @@ impl Vm {
|
||||
for thread in self.threads.drain(..) {
|
||||
thread.join().map_err(Error::ThreadCleanup)?
|
||||
}
|
||||
|
||||
let mut state = self.state.try_write().map_err(|_| Error::PoisonedState)?;
|
||||
*state = new_state;
|
||||
|
||||
Ok(())
|
||||
@@ -546,23 +639,36 @@ impl Vm {
|
||||
}
|
||||
|
||||
if let Some(desired_memory) = desired_memory {
|
||||
if self
|
||||
let new_region = self
|
||||
.memory_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.resize(desired_memory)
|
||||
.map_err(Error::MemoryManager)?
|
||||
{
|
||||
.map_err(Error::MemoryManager)?;
|
||||
|
||||
if let Some(new_region) = &new_region {
|
||||
self.device_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.notify_hotplug(HotPlugNotificationFlags::MEMORY_DEVICES_CHANGED)
|
||||
.update_memory(&new_region)
|
||||
.map_err(Error::DeviceManager)?;
|
||||
|
||||
let memory_config = &self.config.lock().unwrap().memory;
|
||||
match memory_config.hotplug_method {
|
||||
HotplugMethod::Acpi => {
|
||||
self.device_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.notify_hotplug(HotPlugNotificationFlags::MEMORY_DEVICES_CHANGED)
|
||||
.map_err(Error::DeviceManager)?;
|
||||
}
|
||||
HotplugMethod::VirtioMem => {}
|
||||
}
|
||||
}
|
||||
|
||||
// We update the VM config regardless of the actual guest resize operation
|
||||
// result (true or false, happened or not), so that if the VM reboots it
|
||||
// will be running with the last configure memory size.
|
||||
// We update the VM config regardless of the actual guest resize
|
||||
// operation result (happened or not), so that if the VM reboots
|
||||
// it will be running with the last configure memory size.
|
||||
self.config.lock().unwrap().memory.size = desired_memory;
|
||||
}
|
||||
Ok(())
|
||||
@@ -615,15 +721,198 @@ impl Vm {
|
||||
// ensure the device would not be created in case of a reboot.
|
||||
{
|
||||
let mut config = self.config.lock().unwrap();
|
||||
|
||||
// Remove if VFIO device
|
||||
if let Some(devices) = config.devices.as_mut() {
|
||||
devices.retain(|dev| {
|
||||
if let Some(dev_id) = &dev.id {
|
||||
*dev_id != _id
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
devices.retain(|dev| dev.id.as_ref() != Some(&_id));
|
||||
}
|
||||
|
||||
// Remove if disk device
|
||||
if let Some(disks) = config.disks.as_mut() {
|
||||
disks.retain(|dev| dev.id.as_ref() != Some(&_id));
|
||||
}
|
||||
|
||||
// Remove if net device
|
||||
if let Some(net) = config.net.as_mut() {
|
||||
net.retain(|dev| dev.id.as_ref() != Some(&_id));
|
||||
}
|
||||
|
||||
// Remove if pmem device
|
||||
if let Some(pmem) = config.pmem.as_mut() {
|
||||
pmem.retain(|dev| dev.id.as_ref() != Some(&_id));
|
||||
}
|
||||
|
||||
// Remove if vsock device
|
||||
if let Some(vsock) = config.vsock.as_ref() {
|
||||
if vsock.id.as_ref() == Some(&_id) {
|
||||
config.vsock = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.device_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.notify_hotplug(HotPlugNotificationFlags::PCI_DEVICES_CHANGED)
|
||||
.map_err(Error::DeviceManager)?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NoPciSupport)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_disk(&mut self, mut _disk_cfg: DiskConfig) -> Result<()> {
|
||||
if cfg!(feature = "pci_support") {
|
||||
#[cfg(feature = "pci_support")]
|
||||
{
|
||||
self.device_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.add_disk(&mut _disk_cfg)
|
||||
.map_err(Error::DeviceManager)?;
|
||||
|
||||
// Update VmConfig by adding the new device. This is important to
|
||||
// ensure the device would be created in case of a reboot.
|
||||
{
|
||||
let mut config = self.config.lock().unwrap();
|
||||
if let Some(disks) = config.disks.as_mut() {
|
||||
disks.push(_disk_cfg);
|
||||
} else {
|
||||
config.disks = Some(vec![_disk_cfg]);
|
||||
}
|
||||
}
|
||||
|
||||
self.device_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.notify_hotplug(HotPlugNotificationFlags::PCI_DEVICES_CHANGED)
|
||||
.map_err(Error::DeviceManager)?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NoPciSupport)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_fs(&mut self, mut _fs_cfg: FsConfig) -> Result<()> {
|
||||
if cfg!(feature = "pci_support") {
|
||||
#[cfg(feature = "pci_support")]
|
||||
{
|
||||
self.device_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.add_fs(&mut _fs_cfg)
|
||||
.map_err(Error::DeviceManager)?;
|
||||
|
||||
// Update VmConfig by adding the new device. This is important to
|
||||
// ensure the device would be created in case of a reboot.
|
||||
{
|
||||
let mut config = self.config.lock().unwrap();
|
||||
if let Some(fs_config) = config.fs.as_mut() {
|
||||
fs_config.push(_fs_cfg);
|
||||
} else {
|
||||
config.fs = Some(vec![_fs_cfg]);
|
||||
}
|
||||
}
|
||||
|
||||
self.device_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.notify_hotplug(HotPlugNotificationFlags::PCI_DEVICES_CHANGED)
|
||||
.map_err(Error::DeviceManager)?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NoPciSupport)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_pmem(&mut self, mut _pmem_cfg: PmemConfig) -> Result<()> {
|
||||
if cfg!(feature = "pci_support") {
|
||||
#[cfg(feature = "pci_support")]
|
||||
{
|
||||
self.device_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.add_pmem(&mut _pmem_cfg)
|
||||
.map_err(Error::DeviceManager)?;
|
||||
|
||||
// Update VmConfig by adding the new device. This is important to
|
||||
// ensure the device would be created in case of a reboot.
|
||||
{
|
||||
let mut config = self.config.lock().unwrap();
|
||||
if let Some(pmem) = config.pmem.as_mut() {
|
||||
pmem.push(_pmem_cfg);
|
||||
} else {
|
||||
config.pmem = Some(vec![_pmem_cfg]);
|
||||
}
|
||||
}
|
||||
|
||||
self.device_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.notify_hotplug(HotPlugNotificationFlags::PCI_DEVICES_CHANGED)
|
||||
.map_err(Error::DeviceManager)?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NoPciSupport)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_net(&mut self, mut _net_cfg: NetConfig) -> Result<()> {
|
||||
if cfg!(feature = "pci_support") {
|
||||
#[cfg(feature = "pci_support")]
|
||||
{
|
||||
self.device_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.add_net(&mut _net_cfg)
|
||||
.map_err(Error::DeviceManager)?;
|
||||
|
||||
// Update VmConfig by adding the new device. This is important to
|
||||
// ensure the device would be created in case of a reboot.
|
||||
{
|
||||
let mut config = self.config.lock().unwrap();
|
||||
if let Some(net) = config.net.as_mut() {
|
||||
net.push(_net_cfg);
|
||||
} else {
|
||||
config.net = Some(vec![_net_cfg]);
|
||||
}
|
||||
}
|
||||
|
||||
self.device_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.notify_hotplug(HotPlugNotificationFlags::PCI_DEVICES_CHANGED)
|
||||
.map_err(Error::DeviceManager)?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NoPciSupport)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_vsock(&mut self, mut _vsock_cfg: VsockConfig) -> Result<()> {
|
||||
if cfg!(feature = "pci_support") {
|
||||
#[cfg(feature = "pci_support")]
|
||||
{
|
||||
if self.config.lock().unwrap().vsock.is_some() {
|
||||
return Err(Error::TooManyVsockDevices);
|
||||
}
|
||||
|
||||
self.device_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.add_vsock(&mut _vsock_cfg)
|
||||
.map_err(Error::DeviceManager)?;
|
||||
|
||||
// Update VmConfig by adding the new device. This is important to
|
||||
// ensure the device would be created in case of a reboot.
|
||||
{
|
||||
let mut config = self.config.lock().unwrap();
|
||||
config.vsock = Some(_vsock_cfg);
|
||||
}
|
||||
|
||||
self.device_manager
|
||||
@@ -794,7 +1083,187 @@ impl Pausable for Vm {
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshotable for Vm {}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VmSnapshot {
|
||||
pub config: Arc<Mutex<VmConfig>>,
|
||||
}
|
||||
|
||||
pub const VM_SNAPSHOT_ID: &str = "vm";
|
||||
impl Snapshottable for Vm {
|
||||
fn id(&self) -> String {
|
||||
VM_SNAPSHOT_ID.to_string()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let current_state = self.get_state().unwrap();
|
||||
if current_state != VmState::Paused {
|
||||
return Err(MigratableError::Snapshot(anyhow!(
|
||||
"Trying to snapshot while VM is running"
|
||||
)));
|
||||
}
|
||||
|
||||
let mut vm_snapshot = Snapshot::new(VM_SNAPSHOT_ID);
|
||||
let vm_snapshot_data = serde_json::to_vec(&VmSnapshot {
|
||||
config: self.get_config(),
|
||||
})
|
||||
.map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
vm_snapshot.add_snapshot(self.cpu_manager.lock().unwrap().snapshot()?);
|
||||
vm_snapshot.add_snapshot(self.memory_manager.lock().unwrap().snapshot()?);
|
||||
vm_snapshot.add_snapshot(self.device_manager.lock().unwrap().snapshot()?);
|
||||
vm_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", VM_SNAPSHOT_ID),
|
||||
snapshot: vm_snapshot_data,
|
||||
});
|
||||
|
||||
Ok(vm_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
let current_state = self
|
||||
.get_state()
|
||||
.map_err(|e| MigratableError::Restore(anyhow!("Could not get VM state: {:#?}", e)))?;
|
||||
let new_state = VmState::Running;
|
||||
current_state.valid_transition(new_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not restore VM state: {:#?}", e))
|
||||
})?;
|
||||
|
||||
if let Some(memory_manager_snapshot) = snapshot.snapshots.get(MEMORY_MANAGER_SNAPSHOT_ID) {
|
||||
self.memory_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.restore(*memory_manager_snapshot.clone())?;
|
||||
} else {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Missing memory manager snapshot"
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(device_manager_snapshot) = snapshot.snapshots.get(DEVICE_MANAGER_SNAPSHOT_ID) {
|
||||
self.device_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.restore(*device_manager_snapshot.clone())?;
|
||||
} else {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Missing device manager snapshot"
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(cpu_manager_snapshot) = snapshot.snapshots.get(CPU_MANAGER_SNAPSHOT_ID) {
|
||||
self.cpu_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.restore(*cpu_manager_snapshot.clone())?;
|
||||
} else {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Missing CPU manager snapshot"
|
||||
)));
|
||||
}
|
||||
|
||||
if self
|
||||
.device_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.console()
|
||||
.input_enabled()
|
||||
{
|
||||
let console = self.device_manager.lock().unwrap().console().clone();
|
||||
let signals = Signals::new(&[SIGWINCH, SIGINT, SIGTERM]);
|
||||
match signals {
|
||||
Ok(signals) => {
|
||||
self.signals = Some(signals.clone());
|
||||
|
||||
let on_tty = self.on_tty;
|
||||
self.threads.push(
|
||||
thread::Builder::new()
|
||||
.name("signal_handler".to_string())
|
||||
.spawn(move || Vm::os_signal_handler(signals, console, on_tty))
|
||||
.map_err(|e| {
|
||||
MigratableError::Restore(anyhow!(
|
||||
"Could not start console signal thread: {:#?}",
|
||||
e
|
||||
))
|
||||
})?,
|
||||
);
|
||||
}
|
||||
Err(e) => error!("Signal not found {}", e),
|
||||
}
|
||||
|
||||
if self.on_tty {
|
||||
io::stdin().lock().set_raw_mode().map_err(|e| {
|
||||
MigratableError::Restore(anyhow!(
|
||||
"Could not set terminal in raw mode: {:#?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut state = self
|
||||
.state
|
||||
.try_write()
|
||||
.map_err(|e| MigratableError::Restore(anyhow!("Could not set VM state: {:#?}", e)))?;
|
||||
*state = new_state;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Transportable for Vm {
|
||||
fn send(
|
||||
&self,
|
||||
snapshot: &Snapshot,
|
||||
destination_url: &str,
|
||||
) -> std::result::Result<(), MigratableError> {
|
||||
let url = Url::parse(destination_url).map_err(|e| {
|
||||
MigratableError::MigrateSend(anyhow!("Could not parse destination URL: {}", e))
|
||||
})?;
|
||||
|
||||
match url.scheme() {
|
||||
"file" => {
|
||||
let mut vm_snapshot_path = url_to_path(&url)?;
|
||||
vm_snapshot_path.push(VM_SNAPSHOT_FILE);
|
||||
|
||||
// Create the snapshot file
|
||||
let mut vm_snapshot_file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(vm_snapshot_path)
|
||||
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
|
||||
|
||||
// Serialize and write the snapshot
|
||||
let vm_snapshot = serde_json::to_vec(snapshot)
|
||||
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
|
||||
|
||||
vm_snapshot_file
|
||||
.write(&vm_snapshot)
|
||||
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
|
||||
|
||||
// Tell the memory manager to also send/write its own snapshot.
|
||||
if let Some(memory_manager_snapshot) =
|
||||
snapshot.snapshots.get(MEMORY_MANAGER_SNAPSHOT_ID)
|
||||
{
|
||||
self.memory_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send(&*memory_manager_snapshot.clone(), destination_url)?;
|
||||
} else {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Missing memory manager snapshot"
|
||||
)));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(MigratableError::MigrateSend(anyhow!(
|
||||
"Unsupported VM transport URL scheme: {}",
|
||||
url.scheme()
|
||||
)))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Migratable for Vm {}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user