mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
420 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e8a7b6d07 | ||
|
|
2d7bfdd920 | ||
|
|
5822969afa | ||
|
|
defc33927f | ||
|
|
efbafdf9ed | ||
|
|
2b60452195 | ||
|
|
e8a25c22f6 | ||
|
|
b8be1dc24a | ||
|
|
83631599a3 | ||
|
|
78e2f7a99a | ||
|
|
205b8c1cd5 | ||
|
|
3acf9dfcf3 | ||
|
|
63c30a6e79 | ||
|
|
c65ead5de8 | ||
|
|
837bcbc6ba | ||
|
|
3598e603d5 | ||
|
|
34bb31791b | ||
|
|
9085a39c7d | ||
|
|
5fc3f37c9b | ||
|
|
3bb51d4d5e | ||
|
|
cc72ed1cc9 | ||
|
|
8663b429b3 | ||
|
|
df336ade57 | ||
|
|
9e78c2e686 | ||
|
|
786e33931f | ||
|
|
90d1083bda | ||
|
|
5ca068a068 | ||
|
|
2a0ba7aef8 | ||
|
|
097b30669f | ||
|
|
ef090cf37d | ||
|
|
db3ece8ef2 | ||
|
|
af41d6fc88 | ||
|
|
d2d3abb13c | ||
|
|
dbbd04a4cf | ||
|
|
4ac0cb9cff | ||
|
|
80c3fd922a | ||
|
|
37a7000fdd | ||
|
|
6e9e24ec0f | ||
|
|
1fc8ee945a | ||
|
|
cb59f826ad | ||
|
|
688ec0eb2d | ||
|
|
8e018d6feb | ||
|
|
c446b9d510 | ||
|
|
6df7cd0e4b | ||
|
|
3a04db5936 | ||
|
|
ad7d02cd74 | ||
|
|
1298b508bf | ||
|
|
a95fa1c4e8 | ||
|
|
228adebc32 | ||
|
|
14eb071b29 | ||
|
|
5652cc7a05 | ||
|
|
2c50c963f5 | ||
|
|
46848fdc43 | ||
|
|
b918220b49 | ||
|
|
278ab05cbc | ||
|
|
32d07e40cc | ||
|
|
63869bde75 | ||
|
|
fb4769388b | ||
|
|
20c4ed829a | ||
|
|
4b8d7e718d | ||
|
|
6e0aa56f06 | ||
|
|
03352f45f9 | ||
|
|
f40adff2a1 | ||
|
|
0acb1e329d | ||
|
|
6566c739e1 | ||
|
|
9ab00dcb75 | ||
|
|
ee1899c6f6 | ||
|
|
392f1ec155 | ||
|
|
9fad680db1 | ||
|
|
9ebb1a55bc | ||
|
|
85e1865cb5 | ||
|
|
4b1328a29c | ||
|
|
8225d4cd6e | ||
|
|
dac7737919 | ||
|
|
3e750de43f | ||
|
|
eb91bc812b | ||
|
|
59b4aaba87 | ||
|
|
8288cb2ac8 | ||
|
|
2a466132a0 | ||
|
|
fc5c210498 | ||
|
|
8dbb16df4d | ||
|
|
7abbad0a62 | ||
|
|
7328ecdb3b | ||
|
|
f9daf2e247 | ||
|
|
43b3642955 | ||
|
|
42758244a0 | ||
|
|
27af983ec9 | ||
|
|
b70344158b | ||
|
|
7e0cb078ed | ||
|
|
9a93f4f0a6 | ||
|
|
c505cfae2b | ||
|
|
8a5e47f989 | ||
|
|
46cde1a38e | ||
|
|
ce0b475ef7 | ||
|
|
f674019ea1 | ||
|
|
f2de4d0315 | ||
|
|
6a722e5c0b | ||
|
|
aa31748781 | ||
|
|
b14fd37db9 | ||
|
|
f27aa21e3f | ||
|
|
fe5561df50 | ||
|
|
2371325f9c | ||
|
|
e50f4418a2 | ||
|
|
8916dad2da | ||
|
|
1b66a2fa60 | ||
|
|
8ea4145f98 | ||
|
|
47367eb61b | ||
|
|
df3e5c874f | ||
|
|
8ec6cda0c5 | ||
|
|
8c33eb3069 | ||
|
|
f6d1a9d9b8 | ||
|
|
d724511a91 | ||
|
|
9ff42060e0 | ||
|
|
db151819f1 | ||
|
|
9356af80c6 | ||
|
|
6a4a931b9e | ||
|
|
038f198129 | ||
|
|
833a3d456c | ||
|
|
c0ca3b6b8e | ||
|
|
a9eb352aea | ||
|
|
09392f0530 | ||
|
|
527dd68ce1 | ||
|
|
4a1af7f63c | ||
|
|
c4309515c9 | ||
|
|
347611b0c7 | ||
|
|
f14ab872ec | ||
|
|
cfc8c39446 | ||
|
|
1aab372a06 | ||
|
|
c1b26b1fab | ||
|
|
d80ac43ef1 | ||
|
|
5f076923d3 | ||
|
|
36de390caf | ||
|
|
d4f7f73bc8 | ||
|
|
4ed81894aa | ||
|
|
2e2cad91ae | ||
|
|
b5ee9212c1 | ||
|
|
162791b571 | ||
|
|
8207b2e97d | ||
|
|
a0455167d0 | ||
|
|
1bc47507b7 | ||
|
|
5ba61f6d5e | ||
|
|
6d6e290000 | ||
|
|
0e7a1fc923 | ||
|
|
f63cb85f93 | ||
|
|
ff1cb11946 | ||
|
|
df2516f229 | ||
|
|
2ae3919181 | ||
|
|
8188074300 | ||
|
|
e235c6de4f | ||
|
|
151f96e454 | ||
|
|
2f1ff23066 | ||
|
|
4671a5831f | ||
|
|
03ab6839c1 | ||
|
|
6710a39b5a | ||
|
|
feb1c33084 | ||
|
|
47167a658e | ||
|
|
ea7abc6c80 | ||
|
|
e6ef9ece2c | ||
|
|
2e9d815701 | ||
|
|
2e0f1c2afe | ||
|
|
e869283b4e | ||
|
|
bdfd1a3f38 | ||
|
|
9c5135da7a | ||
|
|
b79c1f7722 | ||
|
|
acc60b0ad5 | ||
|
|
3dc7aff00e | ||
|
|
5f8a62f3d0 | ||
|
|
36137232f0 | ||
|
|
79a02f9171 | ||
|
|
00674cd850 | ||
|
|
5323da031c | ||
|
|
0688bec298 | ||
|
|
675e46355c | ||
|
|
036890e5be | ||
|
|
9c5bfb8e13 | ||
|
|
dbff0e94b8 | ||
|
|
0c8f9d2768 | ||
|
|
f06b2aaaa7 | ||
|
|
a2f3da3488 | ||
|
|
2cd406ba50 | ||
|
|
29b3848ffb | ||
|
|
d723b7dae8 | ||
|
|
0a229ef4f5 | ||
|
|
6b06cec611 | ||
|
|
360980d93c | ||
|
|
39083d705b | ||
|
|
a949ab21f7 | ||
|
|
4164853ec6 | ||
|
|
c7559bb7a4 | ||
|
|
397d388710 | ||
|
|
b232de9963 | ||
|
|
927148dd3c | ||
|
|
c347f84d74 | ||
|
|
0a0c7358a2 | ||
|
|
5f0337c21d | ||
|
|
b488d4859b | ||
|
|
5b3ca78dac | ||
|
|
180e6d1e78 | ||
|
|
f0360c92d9 | ||
|
|
f9b0875a60 | ||
|
|
bf4f3db6c4 | ||
|
|
eb60106159 | ||
|
|
67ef4f0d74 | ||
|
|
f622a76597 | ||
|
|
3bc11a4a2e | ||
|
|
4df5ebea12 | ||
|
|
0739c2c7fd | ||
|
|
7358144f09 | ||
|
|
3567206059 | ||
|
|
ea7f4d7f0b | ||
|
|
c0fede0774 | ||
|
|
1097afbaff | ||
|
|
ccb83282e9 | ||
|
|
3ad4b8486f | ||
|
|
1099f0726b | ||
|
|
26974c7625 | ||
|
|
c042483953 | ||
|
|
6d27ac9dfc | ||
|
|
3df1680888 | ||
|
|
f5a44ea1ad | ||
|
|
e950aa6b9a | ||
|
|
05b5115e67 | ||
|
|
7edc46f492 | ||
|
|
7c6ef7fd63 | ||
|
|
bf37b960ec | ||
|
|
4d86359a09 | ||
|
|
5e5c2f2c48 | ||
|
|
a9b2207bcf | ||
|
|
40fc6c3f0f | ||
|
|
389f9e3779 | ||
|
|
8f37dec498 | ||
|
|
c00b58de9b | ||
|
|
5f752dade7 | ||
|
|
8e7d67fcfe | ||
|
|
b515d48eca | ||
|
|
eb46aa2b22 | ||
|
|
7975394901 | ||
|
|
5a3472847d | ||
|
|
475e487ac3 | ||
|
|
434a5d0edf | ||
|
|
df61a8fea2 | ||
|
|
22f91ab3a2 | ||
|
|
4ccc81fdf9 | ||
|
|
11e7ece9f5 | ||
|
|
c48ca61417 | ||
|
|
69e27288a2 | ||
|
|
d2db34edf2 | ||
|
|
d089ee4e25 | ||
|
|
2f4de81175 | ||
|
|
9ac967e3d8 | ||
|
|
f740a3568d | ||
|
|
e21bc972b0 | ||
|
|
d446a8217a | ||
|
|
2432ad07ea | ||
|
|
9661e8da5d | ||
|
|
7688e6e231 | ||
|
|
d784ac2982 | ||
|
|
8c2a9a75ec | ||
|
|
b8622b5c69 | ||
|
|
fe9398fe87 | ||
|
|
1f06c5907f | ||
|
|
5dd675710b | ||
|
|
037807f949 | ||
|
|
47ca277690 | ||
|
|
f59cad15a3 | ||
|
|
7ce0db90f0 | ||
|
|
9e764fc091 | ||
|
|
bb2e7bb942 | ||
|
|
40f9da524f | ||
|
|
8308e1bf25 | ||
|
|
ad128bf72d | ||
|
|
7205700c5f | ||
|
|
3af5619256 | ||
|
|
614eb68f16 | ||
|
|
5a187ee2c2 | ||
|
|
ae66a44d26 | ||
|
|
ebe8edd423 | ||
|
|
011496bda0 | ||
|
|
2610f4353d | ||
|
|
15387cd96a | ||
|
|
638bf0378c | ||
|
|
451502b50b | ||
|
|
98f81c36ec | ||
|
|
ee83c2d44e | ||
|
|
eea6f1dc9e | ||
|
|
3e99098bf3 | ||
|
|
e05de4514d | ||
|
|
6fb7c3bbc2 | ||
|
|
a44a903587 | ||
|
|
b4187a1b9d | ||
|
|
8718043dfc | ||
|
|
add0471120 | ||
|
|
772191b409 | ||
|
|
97699a521f | ||
|
|
a4ebcf486d | ||
|
|
cdfe576eb1 | ||
|
|
bc42420583 | ||
|
|
b7d3ad9063 | ||
|
|
56cad00f2e | ||
|
|
cc7a96e9d3 | ||
|
|
f21d54f6b0 | ||
|
|
584a2cccee | ||
|
|
633f51af9c | ||
|
|
51306555e7 | ||
|
|
ce60ff16c4 | ||
|
|
3dd329052c | ||
|
|
b2f85cbdc4 | ||
|
|
18a8bb0072 | ||
|
|
151637b647 | ||
|
|
c316c161a6 | ||
|
|
808fcaa43b | ||
|
|
bc87c9f19b | ||
|
|
66a7a94a12 | ||
|
|
dfb18ef14a | ||
|
|
0b8856d148 | ||
|
|
ec0b5567c8 | ||
|
|
f4d41d600b | ||
|
|
582fc7f989 | ||
|
|
44d8ab06ac | ||
|
|
5f52dd2d1e | ||
|
|
aface5bca2 | ||
|
|
dc31db478a | ||
|
|
b528e99b18 | ||
|
|
e84179994e | ||
|
|
1d9ad9e7b0 | ||
|
|
26a210a83a | ||
|
|
55f01b2180 | ||
|
|
49a129f3d3 | ||
|
|
1c23a09cc6 | ||
|
|
f0082fecb9 | ||
|
|
8b78e1221e | ||
|
|
f5a6e3c1ca | ||
|
|
d6e3b703ab | ||
|
|
71154d8362 | ||
|
|
7245cf7c5e | ||
|
|
7adb9d55ec | ||
|
|
760791abbe | ||
|
|
7b718f3029 | ||
|
|
08ed88c8d1 | ||
|
|
ab6a8f19f0 | ||
|
|
567eda45ec | ||
|
|
0affdd0df7 | ||
|
|
658c076eb2 | ||
|
|
c8364172a3 | ||
|
|
c52e276a5c | ||
|
|
48a9300667 | ||
|
|
10abfd4448 | ||
|
|
6678cbfb79 | ||
|
|
513d2fdcf6 | ||
|
|
76e3a30c31 | ||
|
|
0d53a5890d | ||
|
|
b3c809a78c | ||
|
|
af9a72eab6 | ||
|
|
2e0508cdc6 | ||
|
|
3c29c47783 | ||
|
|
74225ab5b3 | ||
|
|
f30ba069b7 | ||
|
|
e0fda0611c | ||
|
|
e2b38cc050 | ||
|
|
d97079d793 | ||
|
|
d180deb679 | ||
|
|
c6feb03dc0 | ||
|
|
ef2e8b6bc2 | ||
|
|
3645bf8d0f | ||
|
|
021e8d9e13 | ||
|
|
4b3fd906f7 | ||
|
|
bc5b72ff73 | ||
|
|
6c06420a11 | ||
|
|
b608671031 | ||
|
|
97c964891c | ||
|
|
df5058ec0a | ||
|
|
d9a355f85a | ||
|
|
f910476dd7 | ||
|
|
aa44726658 | ||
|
|
c0e2bbb23f | ||
|
|
4c9759e4fc | ||
|
|
b7ed71b012 | ||
|
|
8fcaf91d3b | ||
|
|
87195c9ccc | ||
|
|
fca911e5f3 | ||
|
|
91ce39e2a6 | ||
|
|
6cc3e88742 | ||
|
|
d1cd3c89c6 | ||
|
|
91c7f271b3 | ||
|
|
87f148cbff | ||
|
|
778c60f21d | ||
|
|
846505d360 | ||
|
|
9caad7394d | ||
|
|
ac950d9a97 | ||
|
|
a1f408a247 | ||
|
|
dfd44a6080 | ||
|
|
7d6bf75138 | ||
|
|
20f8d8d700 | ||
|
|
4ba1d2274e | ||
|
|
bd612b6e53 | ||
|
|
b713737f81 | ||
|
|
35a3b47554 | ||
|
|
f927d1a2d7 | ||
|
|
0c9547618a | ||
|
|
6abd50f4b1 | ||
|
|
c7f8498571 | ||
|
|
8a7cfe8ec4 | ||
|
|
49ef201cd1 | ||
|
|
a548a01423 | ||
|
|
baec27698e | ||
|
|
1a484a82f9 | ||
|
|
532f6a96f3 | ||
|
|
c0756c429d | ||
|
|
8c4c162109 | ||
|
|
d52684450f | ||
|
|
facc3b303a | ||
|
|
09aced9ed1 | ||
|
|
56c4b7000a | ||
|
|
d18c8d4c8c | ||
|
|
d217089b54 | ||
|
|
347f8a036b | ||
|
|
b6ae2ccda4 | ||
|
|
f86b9dd95e | ||
|
|
be199e5560 | ||
|
|
98d7955e34 |
11
.travis.yml
11
.travis.yml
@@ -8,10 +8,17 @@ before_script:
|
||||
- rustup component add rustfmt
|
||||
|
||||
script:
|
||||
- cargo build --release
|
||||
- cargo rustc --bin cloud-hypervisor -- -D warnings
|
||||
- cargo rustc --bin cloud-hypervisor --no-default-features --features "pci" -- -D warnings
|
||||
- cargo rustc --bin cloud-hypervisor --no-default-features --features "pci,acpi" -- -D warnings
|
||||
- cargo rustc --bin cloud-hypervisor --no-default-features --features "mmio" -- -D warnings
|
||||
- cargo rustc --bin vhost_user_net -- -D warnings
|
||||
- cargo rustc --bin vhost_user_net --no-default-features --features "pci" -- -D warnings
|
||||
- cargo rustc --bin vhost_user_net --no-default-features --features "pci,acpi" -- -D warnings
|
||||
- cargo rustc --bin vhost_user_net --no-default-features --features "mmio" -- -D warnings
|
||||
- cargo test
|
||||
- cargo clippy --all-targets --all-features -- -D warnings
|
||||
- find . -name "*.rs" | grep -v "vendor/" | xargs rustfmt --check
|
||||
- find . -name "*.rs" | xargs rustfmt --check
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
|
||||
759
Cargo.lock
generated
759
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
35
Cargo.toml
35
Cargo.toml
@@ -5,20 +5,43 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
clap = "=2.27.1"
|
||||
clap = "2.33.0"
|
||||
epoll = "4.1.0"
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.65"
|
||||
log = { version = "0.4.8", features = ["std"] }
|
||||
net_gen = { path = "net_gen" }
|
||||
net_util = { path = "net_util" }
|
||||
vhost_user_backend = { path = "vhost_user_backend"}
|
||||
virtio-bindings = "0.1.0"
|
||||
vmm = { path = "vmm" }
|
||||
vm-device = { path = "vm-device" }
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
vm-virtio = { path = "vm-virtio" }
|
||||
|
||||
[dev-dependencies]
|
||||
ssh2 = "=0.3.3"
|
||||
dirs = "2.0.0"
|
||||
ssh2 = "0.4.0"
|
||||
dirs = "2.0.2"
|
||||
credibility = "0.1.3"
|
||||
tempdir="0.3.7"
|
||||
lazy_static=">=1.1.0"
|
||||
tempdir= "0.3.7"
|
||||
lazy_static= "1.4.0"
|
||||
|
||||
[dependencies.vhost_rs]
|
||||
path = "vhost_rs"
|
||||
features = ["vhost-user-slave"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["acpi", "pci", "cmos"]
|
||||
acpi = ["vmm/acpi"]
|
||||
pci = ["vmm/pci_support"]
|
||||
mmio = ["vmm/mmio_support"]
|
||||
cmos = ["vmm/cmos"]
|
||||
|
||||
# Integration tests require a special environment to run in
|
||||
integration_tests = []
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"vhost_user_backend",
|
||||
]
|
||||
|
||||
5
Jenkinsfile
vendored
5
Jenkinsfile
vendored
@@ -5,17 +5,16 @@ stage ("Builds") {
|
||||
}
|
||||
stage ('Install system packages') {
|
||||
sh "sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq build-essential mtools libssl-dev pkg-config"
|
||||
sh "sudo apt-get install -yq flex bison libelf-dev qemu-utils qemu-system"
|
||||
sh "sudo apt-get install -yq flex bison libelf-dev qemu-utils qemu-system libglib2.0-dev libpixman-1-dev libseccomp-dev socat"
|
||||
}
|
||||
stage ('Install Rust') {
|
||||
sh "nohup curl https://sh.rustup.rs -sSf | sh -s -- -y"
|
||||
}
|
||||
stage ('Run unit tests') {
|
||||
sh "sudo chmod a+rw /dev/kvm"
|
||||
sh "scripts/run_unit_tests.sh"
|
||||
}
|
||||
stage ('Run integration tests') {
|
||||
sh "sudo mount -t tmpfs tmpfs /tmp"
|
||||
sh "sudo mount -t tmpfs tmpfs /tmp"
|
||||
sh "scripts/run_integration_tests.sh"
|
||||
}
|
||||
}
|
||||
|
||||
12
README.md
12
README.md
@@ -104,7 +104,7 @@ We need to get the latest `rust-hypervisor-firmware` release and also a working
|
||||
$ pushd $CLOUDH
|
||||
$ wget https://download.clearlinux.org/releases/29160/clear/clear-29160-kvm.img.xz
|
||||
$ unxz clear-29160-kvm.img.xz
|
||||
$ wget https://github.com/intel/rust-hypervisor-firmware/releases/download/0.1.0/hypervisor-fw
|
||||
$ wget https://github.com/intel/rust-hypervisor-firmware/releases/download/0.2.0/hypervisor-fw
|
||||
$ popd
|
||||
```
|
||||
|
||||
@@ -113,7 +113,7 @@ $ pushd $CLOUDH
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel ./hypervisor-fw \
|
||||
--disk ./clear-29160-kvm.img \
|
||||
--disk path=clear-29160-kvm.img \
|
||||
--cpus 4 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
@@ -121,7 +121,7 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
$ popd
|
||||
```
|
||||
|
||||
Multiple arguments can be given to the `--disk` parameter, currently the firmware requires that the bootable image is on the first disk.
|
||||
Multiple arguments can be given to the `--disk` parameter.
|
||||
|
||||
### Custom kernel and disk image
|
||||
|
||||
@@ -167,7 +167,7 @@ $ pushd $CLOUDH
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--disk ./clear-29160-kvm.img \
|
||||
--disk path=clear-29160-kvm.img \
|
||||
--cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||
--cpus 4 \
|
||||
--memory size=1024M \
|
||||
@@ -187,7 +187,7 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--console off \
|
||||
--serial tty \
|
||||
--disk ./clear-29160-kvm.img \
|
||||
--disk path=clear-29160-kvm.img \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||
--cpus 4 \
|
||||
--memory size=1024M \
|
||||
@@ -255,7 +255,7 @@ etc, are all equal and welcome means of contribution. See the [CONTRIBUTING](CON
|
||||
|
||||
## Join us
|
||||
|
||||
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LTc0YzlmYzQxZDkxNDVhYzZjZjA5MTkxMGY3NTI3YzMzYTFkM2IyY2E0YTIxMzkyYTEwYzdlMzBhMWYxYzVmNDI)
|
||||
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LWQ1MTA1ZDVmODkwMWQ1MTRhYzk4ZGNlN2UwNTI3ZmFlODU0OTcwOWZjMTkwZDExYWE3YjFmNzgzY2FmNDAyMjI)
|
||||
and [join us on Slack](https://cloud-hypervisor.slack.com/).
|
||||
|
||||
# 6. Security
|
||||
|
||||
9
acpi_tables/Cargo.toml
Normal file
9
acpi_tables/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "acpi_tables"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
|
||||
11
acpi_tables/src/lib.rs
Normal file
11
acpi_tables/src/lib.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
pub mod rsdp;
|
||||
pub mod sdt;
|
||||
|
||||
fn generate_checksum(data: &[u8]) -> u8 {
|
||||
(255 - data.iter().fold(0u8, |acc, x| acc.wrapping_add(*x))).wrapping_add(1)
|
||||
}
|
||||
67
acpi_tables/src/rsdp.rs
Normal file
67
acpi_tables/src/rsdp.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use vm_memory::ByteValued;
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct RSDP {
|
||||
pub signature: [u8; 8],
|
||||
pub checksum: u8,
|
||||
pub oem_id: [u8; 6],
|
||||
pub revision: u8,
|
||||
_rsdt_addr: u32,
|
||||
pub length: u32,
|
||||
pub xsdt_addr: u64,
|
||||
pub extended_checksum: u8,
|
||||
_reserved: [u8; 3],
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for RSDP {}
|
||||
|
||||
impl RSDP {
|
||||
pub fn new(oem_id: [u8; 6], xsdt_addr: u64) -> Self {
|
||||
let mut rsdp = RSDP {
|
||||
signature: *b"RSD PTR ",
|
||||
checksum: 0,
|
||||
oem_id,
|
||||
revision: 2,
|
||||
_rsdt_addr: 0,
|
||||
length: std::mem::size_of::<RSDP>() as u32,
|
||||
xsdt_addr,
|
||||
extended_checksum: 0,
|
||||
_reserved: [0; 3],
|
||||
};
|
||||
|
||||
rsdp.checksum = super::generate_checksum(&rsdp.as_slice()[0..19]);
|
||||
rsdp.extended_checksum = super::generate_checksum(&rsdp.as_slice());;
|
||||
rsdp
|
||||
}
|
||||
|
||||
pub fn len() -> usize {
|
||||
std::mem::size_of::<RSDP>()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::RSDP;
|
||||
use vm_memory::bytes::ByteValued;
|
||||
|
||||
#[test]
|
||||
fn test_rsdp() {
|
||||
let rsdp = RSDP::new(*b"CHYPER", 0xdead_beef);
|
||||
let sum = rsdp
|
||||
.as_slice()
|
||||
.iter()
|
||||
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||
assert_eq!(sum, 0);
|
||||
let sum: u8 = rsdp
|
||||
.as_slice()
|
||||
.iter()
|
||||
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||
assert_eq!(sum, 0);
|
||||
}
|
||||
}
|
||||
129
acpi_tables/src/sdt.rs
Normal file
129
acpi_tables/src/sdt.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct GenericAddress {
|
||||
pub address_space_id: u8,
|
||||
pub register_bit_width: u8,
|
||||
pub register_bit_offset: u8,
|
||||
pub access_size: u8,
|
||||
pub address: u64,
|
||||
}
|
||||
|
||||
impl GenericAddress {
|
||||
pub fn io_port_address(address: u16) -> Self {
|
||||
GenericAddress {
|
||||
address_space_id: 1,
|
||||
register_bit_width: 8,
|
||||
register_bit_offset: 0,
|
||||
access_size: 1,
|
||||
address: u64::from(address),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SDT {
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
impl SDT {
|
||||
pub fn new(
|
||||
signature: [u8; 4],
|
||||
length: u32,
|
||||
revision: u8,
|
||||
oem_id: [u8; 6],
|
||||
oem_table: [u8; 8],
|
||||
oem_revision: u32,
|
||||
) -> Self {
|
||||
assert!(length >= 36);
|
||||
|
||||
let mut data = Vec::with_capacity(length as usize);
|
||||
data.extend_from_slice(&signature);
|
||||
data.extend_from_slice(&length.to_le_bytes());
|
||||
data.push(revision);
|
||||
data.push(0); // checksum
|
||||
data.extend_from_slice(&oem_id);
|
||||
data.extend_from_slice(&oem_table);
|
||||
data.extend_from_slice(&oem_revision.to_le_bytes());
|
||||
data.extend_from_slice(b"CLDH");
|
||||
data.extend_from_slice(&0u32.to_le_bytes());
|
||||
assert_eq!(data.len(), 36);
|
||||
|
||||
data.resize(length as usize, 0);
|
||||
let mut sdt = SDT { data };
|
||||
|
||||
sdt.update_checksum();
|
||||
sdt
|
||||
}
|
||||
|
||||
pub fn update_checksum(&mut self) {
|
||||
self.data[9] = 0;
|
||||
let checksum = super::generate_checksum(self.data.as_slice());
|
||||
self.data[9] = checksum
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
&self.data.as_slice()
|
||||
}
|
||||
|
||||
pub fn append<T>(&mut self, value: T) {
|
||||
let orig_length = self.data.len();
|
||||
let new_length = orig_length + std::mem::size_of::<T>();
|
||||
self.data.resize(new_length, 0);
|
||||
self.write_u32(4, new_length as u32);
|
||||
self.write(orig_length, value);
|
||||
}
|
||||
|
||||
/// Write a value at the given offset
|
||||
pub fn write<T>(&mut self, offset: usize, value: T) {
|
||||
assert!((offset + (std::mem::size_of::<T>() - 1)) < self.data.len());
|
||||
unsafe {
|
||||
*(((self.data.as_mut_ptr() as usize) + offset) as *mut T) = value;
|
||||
}
|
||||
self.update_checksum();
|
||||
}
|
||||
|
||||
pub fn write_u8(&mut self, offset: usize, val: u8) {
|
||||
self.write(offset, val);
|
||||
}
|
||||
|
||||
pub fn write_u16(&mut self, offset: usize, val: u16) {
|
||||
self.write(offset, val);
|
||||
}
|
||||
|
||||
pub fn write_u32(&mut self, offset: usize, val: u32) {
|
||||
self.write(offset, val);
|
||||
}
|
||||
|
||||
pub fn write_u64(&mut self, offset: usize, val: u64) {
|
||||
self.write(offset, val);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SDT;
|
||||
|
||||
#[test]
|
||||
fn test_sdt() {
|
||||
let mut sdt = SDT::new(*b"TEST", 40, 1, *b"CLOUDH", *b"TESTTEST", 1);
|
||||
let sum: u8 = sdt
|
||||
.as_slice()
|
||||
.iter()
|
||||
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||
assert_eq!(sum, 0);
|
||||
sdt.write_u32(36, 0x12345678);
|
||||
let sum: u8 = sdt
|
||||
.as_slice()
|
||||
.iter()
|
||||
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||
assert_eq!(sum, 0);
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,17 @@ name = "arch"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "=1.2.1"
|
||||
kvm-bindings = "0.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = ">=0.2.39"
|
||||
[features]
|
||||
default = []
|
||||
acpi = ["acpi_tables"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3.2"
|
||||
kvm-bindings = "0.1.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = "0.2.60"
|
||||
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
arch_gen = { path = "../arch_gen" }
|
||||
|
||||
[dependencies.vm-memory]
|
||||
@@ -20,4 +25,4 @@ git = "https://github.com/rust-vmm/linux-loader"
|
||||
features = ["elf", "bzimage"]
|
||||
|
||||
[dev-dependencies]
|
||||
rand = ">=0.5.5"
|
||||
rand = "0.7.0"
|
||||
|
||||
@@ -16,6 +16,8 @@ pub fn configure_system(
|
||||
_cmdline_addr: GuestAddress,
|
||||
_cmdline_size: usize,
|
||||
_num_cpus: u8,
|
||||
_serial_enabled: bool,
|
||||
_virt_iommu: Option<(u32, &[u32])>,
|
||||
) -> super::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#![allow(
|
||||
clippy::unreadable_literal,
|
||||
clippy::const_static_lifetime,
|
||||
clippy::redundant_static_lifetimes,
|
||||
clippy::cast_lossless,
|
||||
clippy::transmute_ptr_to_ptr,
|
||||
clippy::cast_ptr_alignment
|
||||
@@ -13,13 +13,14 @@ extern crate byteorder;
|
||||
extern crate kvm_bindings;
|
||||
extern crate libc;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
extern crate acpi_tables;
|
||||
extern crate arch_gen;
|
||||
extern crate kvm_ioctls;
|
||||
extern crate linux_loader;
|
||||
extern crate vm_memory;
|
||||
|
||||
use std::result;
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
@@ -51,9 +52,6 @@ pub enum RegionType {
|
||||
Reserved,
|
||||
}
|
||||
|
||||
// 1MB. We don't put anything above here except the kernel itself.
|
||||
pub const HIMEM_START: GuestAddress = GuestAddress(0x100000);
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod aarch64;
|
||||
|
||||
@@ -68,6 +66,5 @@ pub mod x86_64;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::{
|
||||
arch_memory_regions, configure_system, get_32bit_gap_start as get_reserved_mem_addr,
|
||||
layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START,
|
||||
arch_memory_regions, configure_system, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START,
|
||||
};
|
||||
|
||||
484
arch/src/x86_64/acpi.rs
Normal file
484
arch/src/x86_64/acpi.rs
Normal file
@@ -0,0 +1,484 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
use acpi_tables::{
|
||||
rsdp::RSDP,
|
||||
sdt::{GenericAddress, SDT},
|
||||
};
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
|
||||
use vm_memory::{Address, ByteValued, Bytes};
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use super::layout;
|
||||
|
||||
#[repr(packed)]
|
||||
struct LocalAPIC {
|
||||
pub r#type: u8,
|
||||
pub length: u8,
|
||||
pub processor_id: u8,
|
||||
pub apic_id: u8,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct IOAPIC {
|
||||
pub r#type: u8,
|
||||
pub length: u8,
|
||||
pub ioapic_id: u8,
|
||||
_reserved: u8,
|
||||
pub apic_address: u32,
|
||||
pub gsi_base: u32,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct InterruptSourceOverride {
|
||||
pub r#type: u8,
|
||||
pub length: u8,
|
||||
pub bus: u8,
|
||||
pub source: u8,
|
||||
pub gsi: u32,
|
||||
pub flags: u16,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct PCIRangeEntry {
|
||||
pub base_address: u64,
|
||||
pub segment: u16,
|
||||
pub start: u8,
|
||||
pub end: u8,
|
||||
_reserved: u32,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct IortParavirtIommuNode {
|
||||
pub type_: u8,
|
||||
pub length: u16,
|
||||
pub revision: u8,
|
||||
_reserved1: u32,
|
||||
pub num_id_mappings: u32,
|
||||
pub ref_id_mappings: u32,
|
||||
pub device_id: u32,
|
||||
_reserved2: [u32; 3],
|
||||
pub model: u32,
|
||||
pub flags: u32,
|
||||
_reserved3: [u32; 4],
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct IortPciRootComplexNode {
|
||||
pub type_: u8,
|
||||
pub length: u16,
|
||||
pub revision: u8,
|
||||
_reserved1: u32,
|
||||
pub num_id_mappings: u32,
|
||||
pub ref_id_mappings: u32,
|
||||
pub mem_access_props: IortMemoryAccessProperties,
|
||||
pub ats_attr: u32,
|
||||
pub pci_seg_num: u32,
|
||||
pub mem_addr_size_limit: u8,
|
||||
_reserved2: [u8; 3],
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct IortMemoryAccessProperties {
|
||||
pub cca: u32,
|
||||
pub ah: u8,
|
||||
_reserved: u16,
|
||||
pub maf: u8,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct IortIdMapping {
|
||||
pub input_base: u32,
|
||||
pub num_of_ids: u32,
|
||||
pub ouput_base: u32,
|
||||
pub output_ref: u32,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
pub fn create_dsdt_table(
|
||||
serial_enabled: bool,
|
||||
start_of_device_area: GuestAddress,
|
||||
end_of_device_area: GuestAddress,
|
||||
) -> SDT {
|
||||
/*
|
||||
The hex tables in this file are generated from the ASL below with:
|
||||
"iasl -tc <dsdt.asl>"
|
||||
|
||||
As the output contains a table header that is not required the first 36 bytes
|
||||
should be disregarded.
|
||||
*/
|
||||
|
||||
/*
|
||||
Device (_SB.PCI0)
|
||||
{
|
||||
Name (_HID, EisaId ("PNP0A08") /* PCI Express Bus */) // _HID: Hardware ID
|
||||
Name (_CID, EisaId ("PNP0A03") /* PCI Bus */) // _CID: Compatible ID
|
||||
Name (_ADR, Zero) // _ADR: Address
|
||||
Name (_SEG, Zero) // _SEG: PCI Segment
|
||||
Name (_UID, Zero) // _UID: Unique ID
|
||||
Name (SUPP, Zero)
|
||||
}
|
||||
|
||||
Scope (_SB.PCI0)
|
||||
{
|
||||
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
|
||||
{
|
||||
WordBusNumber (ResourceProducer, MinFixed, MaxFixed, PosDecode,
|
||||
0x0000, // Granularity
|
||||
0x0000, // Range Minimum
|
||||
0x00FF, // Range Maximum
|
||||
0x0000, // Translation Offset
|
||||
0x0100, // Length
|
||||
,, )
|
||||
IO (Decode16,
|
||||
0x0CF8, // Range Minimum
|
||||
0x0CF8, // Range Maximum
|
||||
0x01, // Alignment
|
||||
0x08, // Length
|
||||
)
|
||||
WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange,
|
||||
0x0000, // Granularity
|
||||
0x0000, // Range Minimum
|
||||
0x0CF7, // Range Maximum
|
||||
0x0000, // Translation Offset
|
||||
0x0CF8, // Length
|
||||
,, , TypeStatic, DenseTranslation)
|
||||
WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange,
|
||||
0x0000, // Granularity
|
||||
0x0D00, // Range Minimum
|
||||
0xFFFF, // Range Maximum
|
||||
0x0000, // Translation Offset
|
||||
0xF300, // Length
|
||||
,, , TypeStatic, DenseTranslation)
|
||||
DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
|
||||
0x00000000, // Granularity
|
||||
0x000A0000, // Range Minimum
|
||||
0x000BFFFF, // Range Maximum
|
||||
0x00000000, // Translation Offset
|
||||
0x00020000, // Length
|
||||
,, , AddressRangeMemory, TypeStatic)
|
||||
DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, NonCacheable, ReadWrite,
|
||||
0x00000000, // Granularity
|
||||
0xC0000000, // Range Minimum
|
||||
0xFEBFFFFF, // Range Maximum
|
||||
0x00000000, // Translation Offset
|
||||
0x3EC00000, // Length
|
||||
,, , AddressRangeMemory, TypeStatic)
|
||||
QWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
|
||||
0x0000000000000000, // Granularity
|
||||
0x0000000800000000, // Range Minimum
|
||||
0x0000000FFFFFFFFF, // Range Maximum
|
||||
0x0000000000000000, // Translation Offset
|
||||
0x0000000800000000, // Length
|
||||
,, , AddressRangeMemory, TypeStatic)
|
||||
})
|
||||
}
|
||||
*/
|
||||
let mut pci_dsdt_data = [
|
||||
0x5Bu8, 0x82, 0x36, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x50, 0x43, 0x49, 0x30, 0x08, 0x5F, 0x48,
|
||||
0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0A, 0x08, 0x08, 0x5F, 0x43, 0x49, 0x44, 0x0C, 0x41, 0xD0,
|
||||
0x0A, 0x03, 0x08, 0x5F, 0x41, 0x44, 0x52, 0x00, 0x08, 0x5F, 0x53, 0x45, 0x47, 0x00, 0x08,
|
||||
0x5F, 0x55, 0x49, 0x44, 0x00, 0x08, 0x53, 0x55, 0x50, 0x50, 0x00, 0x10, 0x41, 0x0B, 0x2E,
|
||||
0x5F, 0x53, 0x42, 0x5F, 0x50, 0x43, 0x49, 0x30, 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11, 0x40,
|
||||
0x0A, 0x0A, 0x9C, 0x88, 0x0D, 0x00, 0x02, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x47, 0x01, 0xF8, 0x0C, 0xF8, 0x0C, 0x01, 0x08, 0x88, 0x0D, 0x00,
|
||||
0x01, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00, 0xF7, 0x0C, 0x00, 0x00, 0xF8, 0x0C, 0x88, 0x0D,
|
||||
0x00, 0x01, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xF3, 0x87,
|
||||
0x17, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xFF, 0xFF,
|
||||
0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x87, 0x17, 0x00, 0x00, 0x0C,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0xBF, 0xFE, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xC0, 0x3E, 0x8A, 0x2B, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x79, 0x00,
|
||||
];
|
||||
|
||||
// Patch Range Minimum/Range Maximum/Length for the the 64-bit device area
|
||||
pci_dsdt_data[170..174].copy_from_slice(&layout::MEM_32BIT_DEVICES_START.0.to_le_bytes()[0..4]);
|
||||
pci_dsdt_data[174..178].copy_from_slice(
|
||||
&(layout::MEM_32BIT_DEVICES_START.0 + layout::MEM_32BIT_DEVICES_SIZE - 1).to_le_bytes()
|
||||
[0..4],
|
||||
);
|
||||
pci_dsdt_data[182..186].copy_from_slice(&layout::MEM_32BIT_DEVICES_SIZE.to_le_bytes()[0..4]);
|
||||
|
||||
// Patch the Range Minimum/Range Maximum/Length for the the 64-bit device area
|
||||
pci_dsdt_data[200..208].copy_from_slice(&(start_of_device_area.0).to_le_bytes());
|
||||
pci_dsdt_data[208..216].copy_from_slice(&end_of_device_area.0.to_le_bytes());
|
||||
pci_dsdt_data[224..232].copy_from_slice(
|
||||
&(end_of_device_area.unchecked_offset_from(start_of_device_area) + 1).to_le_bytes(),
|
||||
);
|
||||
|
||||
/*
|
||||
Device (_SB.MBRD)
|
||||
{
|
||||
Name (_HID, EisaId ("PNP0C02") /* PNP Motherboard Resources */) // _HID: Hardware ID
|
||||
Name (_UID, Zero) // _UID: Unique ID
|
||||
}
|
||||
|
||||
Scope (_SB.MBRD)
|
||||
{
|
||||
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
|
||||
{
|
||||
Memory32Fixed (ReadWrite,
|
||||
0xE8000000, // Address Base
|
||||
0x10000000, // Address Length
|
||||
)
|
||||
})
|
||||
}
|
||||
*/
|
||||
let mut mbrd_dsdt_data = [
|
||||
0x5Bu8, 0x82, 0x1A, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x4D, 0x42, 0x52, 0x44, 0x08, 0x5F, 0x48,
|
||||
0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0C, 0x02, 0x08, 0x5F, 0x55, 0x49, 0x44, 0x00, 0x10, 0x21,
|
||||
0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x4D, 0x42, 0x52, 0x44, 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11,
|
||||
0x11, 0x0A, 0x0E, 0x86, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00, 0xE8, 0x00, 0x00, 0x00, 0x10,
|
||||
0x79, 0x00,
|
||||
];
|
||||
|
||||
mbrd_dsdt_data[52..56].copy_from_slice(&layout::PCI_MMCONFIG_START.0.to_le_bytes()[0..4]);
|
||||
mbrd_dsdt_data[56..60].copy_from_slice(&layout::PCI_MMCONFIG_SIZE.to_le_bytes()[0..4]);
|
||||
|
||||
/*
|
||||
Device (_SB.COM1)
|
||||
{
|
||||
Name (_HID, EisaId ("PNP0501") /* 16550A-compatible COM Serial Port */) // _HID: Hardware ID
|
||||
Name (_UID, Zero) // _UID: Unique ID
|
||||
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
|
||||
{
|
||||
Interrupt (ResourceConsumer, Edge, ActiveHigh, Exclusive, ,, )
|
||||
{
|
||||
0x00000004,
|
||||
}
|
||||
IO (Decode16,
|
||||
0x03F8, // Range Minimum
|
||||
0x03F8, // Range Maximum
|
||||
0x00, // Alignment
|
||||
0x08, // Length
|
||||
)
|
||||
})
|
||||
}
|
||||
*/
|
||||
let com1_dsdt_data = [
|
||||
0x5Bu8, 0x82, 0x36, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x43, 0x4F, 0x4D, 0x31, 0x08, 0x5F, 0x48,
|
||||
0x49, 0x44, 0x0C, 0x41, 0xD0, 0x05, 0x01, 0x08, 0x5F, 0x55, 0x49, 0x44, 0x00, 0x08, 0x5F,
|
||||
0x43, 0x52, 0x53, 0x11, 0x16, 0x0A, 0x13, 0x89, 0x06, 0x00, 0x03, 0x01, 0x04, 0x00, 0x00,
|
||||
0x00, 0x47, 0x01, 0xF8, 0x03, 0xF8, 0x03, 0x00, 0x08, 0x79, 0x00,
|
||||
];
|
||||
|
||||
/*
|
||||
Name (\_S5, Package (0x01) // _S5_: S5 System State
|
||||
{
|
||||
0x05
|
||||
})
|
||||
*/
|
||||
let s5_sleep_data = [0x08u8, 0x5F, 0x53, 0x35, 0x5F, 0x12, 0x04, 0x01, 0x0A, 0x05];
|
||||
|
||||
// DSDT
|
||||
let mut dsdt = SDT::new(*b"DSDT", 36, 6, *b"CLOUDH", *b"CHDSDT ", 1);
|
||||
dsdt.append(pci_dsdt_data);
|
||||
dsdt.append(mbrd_dsdt_data);
|
||||
if serial_enabled {
|
||||
dsdt.append(com1_dsdt_data);
|
||||
}
|
||||
dsdt.append(s5_sleep_data);
|
||||
|
||||
dsdt
|
||||
}
|
||||
pub fn create_acpi_tables(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
num_cpus: u8,
|
||||
serial_enabled: bool,
|
||||
start_of_device_area: GuestAddress,
|
||||
end_of_device_area: GuestAddress,
|
||||
virt_iommu: Option<(u32, &[u32])>,
|
||||
) -> GuestAddress {
|
||||
// RSDP is at the EBDA
|
||||
let rsdp_offset = layout::RSDP_POINTER;
|
||||
let mut tables: Vec<u64> = Vec::new();
|
||||
|
||||
// DSDT
|
||||
let dsdt = create_dsdt_table(serial_enabled, start_of_device_area, end_of_device_area);
|
||||
let dsdt_offset = rsdp_offset.checked_add(RSDP::len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(dsdt.as_slice(), dsdt_offset)
|
||||
.expect("Error writing DSDT table");
|
||||
|
||||
// FACP aka FADT
|
||||
// Revision 6 of the ACPI FADT table is 276 bytes long
|
||||
let mut facp = SDT::new(*b"FACP", 276, 6, *b"CLOUDH", *b"CHFACP ", 1);
|
||||
|
||||
// HW_REDUCED_ACPI and RESET_REG_SUP
|
||||
let fadt_flags: u32 = 1 << 20 | 1 << 10;
|
||||
facp.write(112, fadt_flags);
|
||||
|
||||
// RESET_REG
|
||||
facp.write(116, GenericAddress::io_port_address(0x3c0));
|
||||
// RESET_VALUE
|
||||
facp.write(128, 1u8);
|
||||
|
||||
facp.write(131, 3u8); // FADT minor version
|
||||
facp.write(140, dsdt_offset.0); // X_DSDT
|
||||
|
||||
// SLEEP_CONTROL_REG
|
||||
facp.write(244, GenericAddress::io_port_address(0x3c0));
|
||||
// SLEEP_STATUS_REG
|
||||
facp.write(256, GenericAddress::io_port_address(0x3c0));
|
||||
|
||||
facp.write(268, b"CLOUDHYP"); // Hypervisor Vendor Identity
|
||||
|
||||
facp.update_checksum();
|
||||
let facp_offset = dsdt_offset.checked_add(dsdt.len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(facp.as_slice(), facp_offset)
|
||||
.expect("Error writing FACP table");
|
||||
tables.push(facp_offset.0);
|
||||
|
||||
// MADT
|
||||
let mut madt = SDT::new(*b"APIC", 44, 5, *b"CLOUDH", *b"CHMADT ", 1);
|
||||
madt.write(36, layout::APIC_START);
|
||||
|
||||
for cpu in 0..num_cpus {
|
||||
let lapic = LocalAPIC {
|
||||
r#type: 0,
|
||||
length: 8,
|
||||
processor_id: cpu,
|
||||
apic_id: cpu,
|
||||
flags: 1,
|
||||
};
|
||||
madt.append(lapic);
|
||||
}
|
||||
|
||||
madt.append(IOAPIC {
|
||||
r#type: 1,
|
||||
length: 12,
|
||||
ioapic_id: 0,
|
||||
apic_address: layout::IOAPIC_START.0 as u32,
|
||||
gsi_base: 0,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
madt.append(InterruptSourceOverride {
|
||||
r#type: 2,
|
||||
length: 10,
|
||||
bus: 0,
|
||||
source: 4,
|
||||
gsi: 4,
|
||||
flags: 0,
|
||||
});
|
||||
|
||||
let madt_offset = facp_offset.checked_add(facp.len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(madt.as_slice(), madt_offset)
|
||||
.expect("Error writing MADT table");
|
||||
tables.push(madt_offset.0);
|
||||
|
||||
// MCFG
|
||||
let mut mcfg = SDT::new(*b"MCFG", 36, 1, *b"CLOUDH", *b"CHMCFG ", 1);
|
||||
|
||||
// MCFG reserved 8 bytes
|
||||
mcfg.append(0u64);
|
||||
|
||||
// 32-bit PCI enhanced configuration mechanism
|
||||
mcfg.append(PCIRangeEntry {
|
||||
base_address: layout::PCI_MMCONFIG_START.0,
|
||||
segment: 0,
|
||||
start: 0,
|
||||
end: 0xff,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mcfg_offset = madt_offset.checked_add(madt.len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(mcfg.as_slice(), mcfg_offset)
|
||||
.expect("Error writing MCFG table");
|
||||
tables.push(mcfg_offset.0);
|
||||
|
||||
let (prev_tbl_len, prev_tbl_off) = if let Some((iommu_id, dev_ids)) = &virt_iommu {
|
||||
// IORT
|
||||
let mut iort = SDT::new(*b"IORT", 36, 1, *b"CLOUDH", *b"CHIORT ", 1);
|
||||
// IORT number of nodes
|
||||
iort.append(2u32);
|
||||
// IORT offset to array of IORT nodes
|
||||
iort.append(48u32);
|
||||
// IORT reserved 4 bytes
|
||||
iort.append(0u32);
|
||||
// IORT paravirtualized IOMMU node
|
||||
iort.append(IortParavirtIommuNode {
|
||||
type_: 128,
|
||||
length: 56,
|
||||
revision: 0,
|
||||
num_id_mappings: 0,
|
||||
ref_id_mappings: 56,
|
||||
device_id: *iommu_id,
|
||||
model: 1,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let num_entries = dev_ids.len();
|
||||
let length: u16 = (36 + (20 * num_entries)).try_into().unwrap();
|
||||
|
||||
// IORT PCI root complex node
|
||||
iort.append(IortPciRootComplexNode {
|
||||
type_: 2,
|
||||
length,
|
||||
revision: 0,
|
||||
num_id_mappings: num_entries as u32,
|
||||
ref_id_mappings: 36,
|
||||
ats_attr: 0,
|
||||
pci_seg_num: 0,
|
||||
mem_addr_size_limit: 255,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
for dev_id in dev_ids.iter() {
|
||||
// IORT ID mapping
|
||||
iort.append(IortIdMapping {
|
||||
input_base: *dev_id,
|
||||
num_of_ids: 1,
|
||||
ouput_base: *dev_id,
|
||||
output_ref: 48,
|
||||
flags: 0,
|
||||
});
|
||||
}
|
||||
|
||||
let iort_offset = mcfg_offset.checked_add(mcfg.len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(iort.as_slice(), iort_offset)
|
||||
.expect("Error writing IORT table");
|
||||
tables.push(iort_offset.0);
|
||||
|
||||
(iort.len(), iort_offset)
|
||||
} else {
|
||||
(mcfg.len(), mcfg_offset)
|
||||
};
|
||||
|
||||
// XSDT
|
||||
let mut xsdt = SDT::new(*b"XSDT", 36, 1, *b"CLOUDH", *b"CHXSDT ", 1);
|
||||
for table in tables {
|
||||
xsdt.append(table);
|
||||
}
|
||||
xsdt.update_checksum();
|
||||
|
||||
let xsdt_offset = prev_tbl_off.checked_add(prev_tbl_len as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(xsdt.as_slice(), xsdt_offset)
|
||||
.expect("Error writing XSDT table");
|
||||
|
||||
// RSDP
|
||||
let rsdp = RSDP::new(*b"CLOUDH", xsdt_offset.0);
|
||||
guest_mem
|
||||
.write_slice(rsdp.as_slice(), rsdp_offset)
|
||||
.expect("Error writing RSDP");
|
||||
|
||||
rsdp_offset
|
||||
}
|
||||
@@ -5,21 +5,90 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
|
||||
/// Magic addresses externally used to lay out x86_64 VMs.
|
||||
/*
|
||||
|
||||
Memory layout documentation and constants
|
||||
~~~~~~ ~~~~~~ ~~~~~~~~~~~~~ ~~~ ~~~~~~~~~
|
||||
|
||||
Constants are in order and grouped by range. Take care to update all references
|
||||
when making changes and keep them in order.
|
||||
|
||||
*/
|
||||
|
||||
// ** Low RAM (start: 0, length: 640KiB) **
|
||||
pub const LOW_RAM_START: GuestAddress = GuestAddress(0x0);
|
||||
|
||||
// == Fixed addresses within the "Low RAM" range: ==
|
||||
|
||||
// Initial GDT/IDT needed to boot kernel
|
||||
pub const BOOT_GDT_START: GuestAddress = GuestAddress(0x500);
|
||||
pub const BOOT_IDT_START: GuestAddress = GuestAddress(0x520);
|
||||
|
||||
/// The 'zero page', a.k.a linux kernel bootparams.
|
||||
pub const ZERO_PAGE_START: GuestAddress = GuestAddress(0x7000);
|
||||
|
||||
/// Initial stack for the boot CPU.
|
||||
pub const BOOT_STACK_START: GuestAddress = GuestAddress(0x8000);
|
||||
pub const BOOT_STACK_POINTER: GuestAddress = GuestAddress(0x8ff0);
|
||||
|
||||
// Initial pagetables.
|
||||
pub const PML4_START: GuestAddress = GuestAddress(0x9000);
|
||||
pub const PDPTE_START: GuestAddress = GuestAddress(0xa000);
|
||||
pub const PDE_START: GuestAddress = GuestAddress(0xb000);
|
||||
|
||||
/// Kernel command line start address.
|
||||
pub const CMDLINE_START: GuestAddress = GuestAddress(0x20000);
|
||||
/// Kernel command line start address maximum size.
|
||||
pub const CMDLINE_MAX_SIZE: usize = 0x10000;
|
||||
|
||||
/// Address for the TSS setup.
|
||||
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffbd000);
|
||||
// MPTABLE, describing VCPUS.
|
||||
pub const MPTABLE_START: GuestAddress = GuestAddress(0x9fc00);
|
||||
|
||||
/// The 'zero page', a.k.a linux kernel bootparams.
|
||||
pub const ZERO_PAGE_START: GuestAddress = GuestAddress(0x7000);
|
||||
// == End of "Low RAM" range. ==
|
||||
|
||||
// ** EBDA reserved area (start: 640KiB, length: 384KiB) **
|
||||
pub const EBDA_START: GuestAddress = GuestAddress(0xa0000);
|
||||
|
||||
// == Fixed constants within the "EBDA" range ==
|
||||
|
||||
// ACPI RSDP table
|
||||
pub const RSDP_POINTER: GuestAddress = EBDA_START;
|
||||
|
||||
// == End of "EBDA" range ==
|
||||
|
||||
// ** High RAM (start: 1MiB, length: 3071MiB) **
|
||||
pub const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
|
||||
|
||||
// == No fixed addresses in the "High RAM" range ==
|
||||
|
||||
// ** 32-bit reserved area (start: 3GiB, length: 1GiB) **
|
||||
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xc000_0000);
|
||||
pub const MEM_32BIT_RESERVED_SIZE: GuestUsize = (1024 << 20);
|
||||
|
||||
// == Fixed constants within the "32-bit reserved" range ==
|
||||
|
||||
// Sub range: 32-bit PCI devices (start: 3GiB, length: 640Mib)
|
||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = MEM_32BIT_RESERVED_START;
|
||||
pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = (640 << 20);
|
||||
|
||||
// PCI MMCONFIG space (start: after the device space, length: 256MiB)
|
||||
pub const PCI_MMCONFIG_START: GuestAddress =
|
||||
GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE);
|
||||
pub const PCI_MMCONFIG_SIZE: GuestUsize = (256 << 20);
|
||||
|
||||
// IOAPIC
|
||||
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
|
||||
pub const IOAPIC_SIZE: GuestUsize = 0x20;
|
||||
|
||||
// APIC
|
||||
pub const APIC_START: GuestAddress = GuestAddress(0xfee0_0000);
|
||||
|
||||
/// Address for the TSS setup.
|
||||
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffb_d000);
|
||||
|
||||
// == End of "32-bit reserved" range. ==
|
||||
|
||||
// ** 64-bit RAM start (start: 4GiB, length: varies) **
|
||||
pub const RAM_64BIT_START: GuestAddress = GuestAddress(0x1_0000_0000);
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
mod acpi;
|
||||
|
||||
mod gdt;
|
||||
pub mod interrupts;
|
||||
pub mod layout;
|
||||
@@ -12,12 +15,15 @@ mod mptable;
|
||||
pub mod regs;
|
||||
|
||||
use crate::RegionType;
|
||||
use linux_loader::loader::bootparam::{boot_params, setup_header, E820_RAM};
|
||||
use linux_loader::loader::bootparam::{boot_params, setup_header};
|
||||
use std::mem;
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
|
||||
};
|
||||
|
||||
const E820_RAM: u32 = 1;
|
||||
const E820_RESERVED: u32 = 2;
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `DataInit`) where:
|
||||
// * the type that is implementing the trait is foreign or
|
||||
@@ -43,73 +49,53 @@ impl From<Error> for super::Error {
|
||||
}
|
||||
}
|
||||
|
||||
// Where BIOS/VGA magic would live on a real PC.
|
||||
const EBDA_START: GuestAddress = GuestAddress(0x9fc00);
|
||||
const FIRST_ADDR_PAST_32BITS: GuestAddress = GuestAddress(1 << 32);
|
||||
|
||||
// Our 32-bit memory gap starts at 3G.
|
||||
const MEM_32BIT_GAP_START: GuestAddress = GuestAddress(0xc000_0000);
|
||||
|
||||
// Our 32-bit memory gap size is 1GB.
|
||||
const MEM_32BIT_GAP_SIZE: GuestUsize = (1024 << 20);
|
||||
|
||||
// We reserve 768MB in our memory gap for 32-bit devices (e.g. 32-bit PCI BARs).
|
||||
const MEM_32BIT_DEVICES_GAP_SIZE: GuestUsize = (768 << 20);
|
||||
|
||||
/// Returns a Vec of the valid memory addresses.
|
||||
/// These should be used to configure the GuestMemory structure for the platform.
|
||||
/// For x86_64 all addresses are valid from the start of the kernel except a
|
||||
/// carve out at the end of 32bit address space.
|
||||
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
|
||||
let reserved_memory_gap_start = MEM_32BIT_GAP_START
|
||||
.checked_add(MEM_32BIT_DEVICES_GAP_SIZE)
|
||||
let reserved_memory_gap_start = layout::MEM_32BIT_RESERVED_START
|
||||
.checked_add(layout::MEM_32BIT_DEVICES_SIZE)
|
||||
.expect("32-bit reserved region is too large");
|
||||
|
||||
let requested_memory_size = GuestAddress(size as u64);
|
||||
let mut regions = Vec::new();
|
||||
|
||||
// case1: guest memory fits before the gap
|
||||
if size as u64 <= MEM_32BIT_GAP_START.raw_value() {
|
||||
if size as u64 <= layout::MEM_32BIT_RESERVED_START.raw_value() {
|
||||
regions.push((GuestAddress(0), size as usize, RegionType::Ram));
|
||||
// case2: guest memory extends beyond the gap
|
||||
} else {
|
||||
// push memory before the gap
|
||||
regions.push((
|
||||
GuestAddress(0),
|
||||
MEM_32BIT_GAP_START.raw_value() as usize,
|
||||
layout::MEM_32BIT_RESERVED_START.raw_value() as usize,
|
||||
RegionType::Ram,
|
||||
));
|
||||
regions.push((
|
||||
FIRST_ADDR_PAST_32BITS,
|
||||
requested_memory_size.unchecked_offset_from(MEM_32BIT_GAP_START) as usize,
|
||||
layout::RAM_64BIT_START,
|
||||
requested_memory_size.unchecked_offset_from(layout::MEM_32BIT_RESERVED_START) as usize,
|
||||
RegionType::Ram,
|
||||
));
|
||||
}
|
||||
|
||||
// Add the 32-bit device memory hole as a sub region.
|
||||
regions.push((
|
||||
MEM_32BIT_GAP_START,
|
||||
MEM_32BIT_DEVICES_GAP_SIZE as usize,
|
||||
layout::MEM_32BIT_RESERVED_START,
|
||||
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
||||
RegionType::SubRegion,
|
||||
));
|
||||
|
||||
// Add the 32-bit reserved memory hole as a sub region.
|
||||
regions.push((
|
||||
reserved_memory_gap_start,
|
||||
(MEM_32BIT_GAP_SIZE - MEM_32BIT_DEVICES_GAP_SIZE) as usize,
|
||||
(layout::MEM_32BIT_RESERVED_SIZE - layout::MEM_32BIT_DEVICES_SIZE) as usize,
|
||||
RegionType::Reserved,
|
||||
));
|
||||
|
||||
regions
|
||||
}
|
||||
|
||||
/// X86 specific memory hole/memory mapped devices/reserved area.
|
||||
pub fn get_32bit_gap_start() -> GuestAddress {
|
||||
FIRST_ADDR_PAST_32BITS
|
||||
.checked_sub(MEM_32BIT_GAP_SIZE)
|
||||
.expect("32-bit hole is too large")
|
||||
}
|
||||
|
||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -118,29 +104,29 @@ pub fn get_32bit_gap_start() -> GuestAddress {
|
||||
/// * `cmdline_addr` - Address in `guest_mem` where the kernel command line was loaded.
|
||||
/// * `cmdline_size` - Size of the kernel command line in bytes including the null terminator.
|
||||
/// * `num_cpus` - Number of virtual CPUs the guest will have.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn configure_system(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
cmdline_size: usize,
|
||||
num_cpus: u8,
|
||||
setup_hdr: Option<setup_header>,
|
||||
_serial_enabled: bool,
|
||||
_end_of_range: GuestAddress,
|
||||
_virt_iommu: Option<(u32, &[u32])>,
|
||||
) -> super::Result<()> {
|
||||
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
|
||||
const KERNEL_HDR_MAGIC: u32 = 0x53726448;
|
||||
const KERNEL_LOADER_OTHER: u8 = 0xff;
|
||||
const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; // Must be non-zero.
|
||||
let first_addr_past_32bits = FIRST_ADDR_PAST_32BITS;
|
||||
let end_32bit_gap_start = get_32bit_gap_start();
|
||||
|
||||
let himem_start = super::HIMEM_START;
|
||||
|
||||
// Note that this puts the mptable at the last 1k of Linux's 640k base RAM
|
||||
mptable::setup_mptable(guest_mem, num_cpus).map_err(Error::MpTableSetup)?;
|
||||
|
||||
let mut params: BootParamsWrapper = BootParamsWrapper(boot_params::default());
|
||||
|
||||
if setup_hdr.is_some() {
|
||||
params.0.hdr = setup_hdr.unwrap();
|
||||
if let Some(hdr) = setup_hdr {
|
||||
params.0.hdr = hdr;
|
||||
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||
params.0.hdr.cmdline_size = cmdline_size as u32;
|
||||
} else {
|
||||
@@ -152,33 +138,58 @@ pub fn configure_system(
|
||||
params.0.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
|
||||
};
|
||||
|
||||
add_e820_entry(&mut params.0, 0, EBDA_START.raw_value(), E820_RAM)?;
|
||||
add_e820_entry(&mut params.0, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
|
||||
|
||||
let mem_end = guest_mem.end_addr();
|
||||
if mem_end < end_32bit_gap_start {
|
||||
if mem_end < layout::MEM_32BIT_RESERVED_START {
|
||||
add_e820_entry(
|
||||
&mut params.0,
|
||||
himem_start.raw_value(),
|
||||
mem_end.unchecked_offset_from(himem_start),
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::HIGH_RAM_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
} else {
|
||||
add_e820_entry(
|
||||
&mut params.0,
|
||||
himem_start.raw_value(),
|
||||
end_32bit_gap_start.unchecked_offset_from(himem_start),
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::HIGH_RAM_START),
|
||||
E820_RAM,
|
||||
)?;
|
||||
if mem_end > first_addr_past_32bits {
|
||||
if mem_end > layout::RAM_64BIT_START {
|
||||
add_e820_entry(
|
||||
&mut params.0,
|
||||
first_addr_past_32bits.raw_value(),
|
||||
mem_end.unchecked_offset_from(first_addr_past_32bits),
|
||||
layout::RAM_64BIT_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::RAM_64BIT_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
add_e820_entry(
|
||||
&mut params.0,
|
||||
layout::PCI_MMCONFIG_START.0,
|
||||
layout::PCI_MMCONFIG_SIZE,
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
{
|
||||
let start_of_device_area = if mem_end < layout::MEM_32BIT_RESERVED_START {
|
||||
layout::RAM_64BIT_START
|
||||
} else {
|
||||
guest_mem.end_addr().unchecked_add(1)
|
||||
};
|
||||
let rsdp_addr = acpi::create_acpi_tables(
|
||||
guest_mem,
|
||||
num_cpus,
|
||||
_serial_enabled,
|
||||
start_of_device_area,
|
||||
_end_of_range,
|
||||
_virt_iommu,
|
||||
);
|
||||
params.0.acpi_rsdp_addr = rsdp_addr.0;
|
||||
}
|
||||
|
||||
let zero_page_addr = layout::ZERO_PAGE_START;
|
||||
guest_mem
|
||||
.checked_offset(zero_page_addr, mem::size_of::<boot_params>())
|
||||
@@ -198,13 +209,13 @@ fn add_e820_entry(
|
||||
size: u64,
|
||||
mem_type: u32,
|
||||
) -> Result<(), Error> {
|
||||
if params.e820_entries >= params.e820_map.len() as u8 {
|
||||
if params.e820_entries >= params.e820_table.len() as u8 {
|
||||
return Err(Error::E820Configuration);
|
||||
}
|
||||
|
||||
params.e820_map[params.e820_entries as usize].addr = addr;
|
||||
params.e820_map[params.e820_entries as usize].size = size;
|
||||
params.e820_map[params.e820_entries as usize].type_ = mem_type;
|
||||
params.e820_table[params.e820_entries as usize].addr = addr;
|
||||
params.e820_table[params.e820_entries as usize].size = size;
|
||||
params.e820_table[params.e820_entries as usize].type_ = mem_type;
|
||||
params.e820_entries += 1;
|
||||
|
||||
Ok(())
|
||||
@@ -213,7 +224,7 @@ fn add_e820_entry(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use linux_loader::loader::bootparam::e820entry;
|
||||
use linux_loader::loader::bootparam::boot_e820_entry;
|
||||
|
||||
#[test]
|
||||
fn regions_lt_4gb() {
|
||||
@@ -231,21 +242,20 @@ mod tests {
|
||||
assert_eq!(GuestAddress(1 << 32), regions[1].0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_32bit_gap() {
|
||||
assert_eq!(
|
||||
get_32bit_gap_start(),
|
||||
FIRST_ADDR_PAST_32BITS
|
||||
.checked_sub(MEM_32BIT_GAP_SIZE as u64)
|
||||
.expect("32-bit hole is too large")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_configuration() {
|
||||
let no_vcpus = 4;
|
||||
let gm = GuestMemoryMmap::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let config_err = configure_system(&gm, GuestAddress(0), 0, 1, None);
|
||||
let config_err = configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
1,
|
||||
None,
|
||||
false,
|
||||
GuestAddress((1 << 36) - 1),
|
||||
None,
|
||||
);
|
||||
assert!(config_err.is_err());
|
||||
assert_eq!(
|
||||
config_err.unwrap_err(),
|
||||
@@ -263,7 +273,17 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::new(&ram_regions).unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
no_vcpus,
|
||||
None,
|
||||
false,
|
||||
GuestAddress((1 << 36) - 1),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Now assigning some memory that is equal to the start of the 32bit memory hole.
|
||||
let mem_size = 3328 << 20;
|
||||
@@ -274,7 +294,17 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::new(&ram_regions).unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
no_vcpus,
|
||||
None,
|
||||
false,
|
||||
GuestAddress((1 << 36) - 1),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Now assigning some memory that falls after the 32bit memory hole.
|
||||
let mem_size = 3330 << 20;
|
||||
@@ -285,19 +315,29 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::new(&ram_regions).unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
no_vcpus,
|
||||
None,
|
||||
false,
|
||||
GuestAddress((1 << 36) - 1),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_e820_entry() {
|
||||
let e820_map = [(e820entry {
|
||||
let e820_table = [(boot_e820_entry {
|
||||
addr: 0x1,
|
||||
size: 4,
|
||||
type_: 1,
|
||||
}); 128];
|
||||
|
||||
let expected_params = boot_params {
|
||||
e820_map,
|
||||
e820_table,
|
||||
e820_entries: 1,
|
||||
..Default::default()
|
||||
};
|
||||
@@ -305,25 +345,25 @@ mod tests {
|
||||
let mut params: boot_params = Default::default();
|
||||
add_e820_entry(
|
||||
&mut params,
|
||||
e820_map[0].addr,
|
||||
e820_map[0].size,
|
||||
e820_map[0].type_,
|
||||
e820_table[0].addr,
|
||||
e820_table[0].size,
|
||||
e820_table[0].type_,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
format!("{:?}", params.e820_map[0]),
|
||||
format!("{:?}", expected_params.e820_map[0])
|
||||
format!("{:?}", params.e820_table[0]),
|
||||
format!("{:?}", expected_params.e820_table[0])
|
||||
);
|
||||
assert_eq!(params.e820_entries, expected_params.e820_entries);
|
||||
|
||||
// Exercise the scenario where the field storing the length of the e820 entry table is
|
||||
// is bigger than the allocated memory.
|
||||
params.e820_entries = params.e820_map.len() as u8 + 1;
|
||||
params.e820_entries = params.e820_table.len() as u8 + 1;
|
||||
assert!(add_e820_entry(
|
||||
&mut params,
|
||||
e820_map[0].addr,
|
||||
e820_map[0].size,
|
||||
e820_map[0].type_
|
||||
e820_table[0].addr,
|
||||
e820_table[0].size,
|
||||
e820_table[0].type_
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ use std::slice;
|
||||
use libc::c_char;
|
||||
|
||||
use arch_gen::x86::mpspec;
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap};
|
||||
use layout::{APIC_START, IOAPIC_START, MPTABLE_START};
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestMemory, GuestMemoryMmap};
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `ByteValued`) where:
|
||||
@@ -44,9 +45,6 @@ unsafe impl ByteValued for MpcTableWrapper {}
|
||||
unsafe impl ByteValued for MpcLintsrcWrapper {}
|
||||
unsafe impl ByteValued for MpfIntelWrapper {}
|
||||
|
||||
// MPTABLE, describing VCPUS.
|
||||
const MPTABLE_START: GuestAddress = GuestAddress(0x9fc00);
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// There was too little guest memory to store the entire MP table.
|
||||
@@ -92,8 +90,6 @@ const MPC_SPEC: i8 = 4;
|
||||
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'F', 'C', ' ', ' ', ' ', ' ', ' ', ' ');
|
||||
const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
|
||||
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
|
||||
const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000; // source: linux/arch/x86/include/asm/apicdef.h
|
||||
const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee00000; // source: linux/arch/x86/include/asm/apicdef.h
|
||||
const APIC_VERSION: u8 = 0x14;
|
||||
const CPU_STEPPING: u32 = 0x600;
|
||||
const CPU_FEATURE_APIC: u32 = 0x200;
|
||||
@@ -208,7 +204,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
mpc_ioapic.0.apicid = ioapicid;
|
||||
mpc_ioapic.0.apicver = APIC_VERSION;
|
||||
mpc_ioapic.0.flags = mpspec::MPC_APIC_USABLE as u8;
|
||||
mpc_ioapic.0.apicaddr = IO_APIC_DEFAULT_PHYS_BASE;
|
||||
mpc_ioapic.0.apicaddr = IOAPIC_START.0 as u32;
|
||||
mem.write_obj(mpc_ioapic, base_mp)
|
||||
.map_err(|_| Error::WriteMpcIoapic)?;
|
||||
base_mp = base_mp.unchecked_add(size as u64);
|
||||
@@ -271,7 +267,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
mpc_table.0.spec = MPC_SPEC;
|
||||
mpc_table.0.oem = MPC_OEM;
|
||||
mpc_table.0.productid = MPC_PRODUCT_ID;
|
||||
mpc_table.0.lapic = APIC_DEFAULT_PHYS_BASE;
|
||||
mpc_table.0.lapic = APIC_START.0 as u32;
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
||||
mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8;
|
||||
mem.write_obj(mpc_table, table_base)
|
||||
@@ -284,7 +280,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use vm_memory::GuestUsize;
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
|
||||
fn table_entry_size(type_: u8) -> usize {
|
||||
match type_ as u32 {
|
||||
|
||||
@@ -11,12 +11,12 @@ use super::gdt::{gdt_entry, kvm_segment_from_gdt};
|
||||
use arch_gen::x86::msr_index;
|
||||
use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_msrs, kvm_regs, kvm_sregs};
|
||||
use kvm_ioctls::VcpuFd;
|
||||
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap};
|
||||
use layout::{BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryMmap};
|
||||
|
||||
// Initial pagetables.
|
||||
const PML4_START: GuestAddress = GuestAddress(0x9000);
|
||||
const PDPTE_START: GuestAddress = GuestAddress(0xa000);
|
||||
const PDE_START: GuestAddress = GuestAddress(0xb000);
|
||||
// MTRR constants
|
||||
const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11
|
||||
const MTRR_MEM_TYPE_WB: u64 = 0x6;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -123,9 +123,6 @@ pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &VcpuFd) -> Result<()> {
|
||||
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
||||
}
|
||||
|
||||
const BOOT_GDT_OFFSET: GuestAddress = GuestAddress(0x500);
|
||||
const BOOT_IDT_OFFSET: GuestAddress = GuestAddress(0x520);
|
||||
|
||||
const BOOT_GDT_MAX: usize = 4;
|
||||
|
||||
const EFER_LMA: u64 = 0x400;
|
||||
@@ -136,7 +133,7 @@ const X86_CR0_PG: u64 = 0x80000000;
|
||||
const X86_CR4_PAE: u64 = 0x20;
|
||||
|
||||
fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
let boot_gdt_addr = BOOT_GDT_OFFSET;
|
||||
let boot_gdt_addr = BOOT_GDT_START;
|
||||
for (index, entry) in table.iter().enumerate() {
|
||||
let addr = guest_mem
|
||||
.checked_offset(boot_gdt_addr, index * mem::size_of::<u64>())
|
||||
@@ -149,7 +146,7 @@ fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
}
|
||||
|
||||
fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
let boot_idt_addr = BOOT_IDT_OFFSET;
|
||||
let boot_idt_addr = BOOT_IDT_START;
|
||||
guest_mem
|
||||
.write_obj(val, boot_idt_addr)
|
||||
.map_err(|_| Error::WriteIDT)
|
||||
@@ -169,11 +166,11 @@ fn configure_segments_and_sregs(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) ->
|
||||
|
||||
// Write segments
|
||||
write_gdt_table(&gdt_table[..], mem)?;
|
||||
sregs.gdt.base = BOOT_GDT_OFFSET.raw_value();
|
||||
sregs.gdt.base = BOOT_GDT_START.raw_value();
|
||||
sregs.gdt.limit = mem::size_of_val(&gdt_table) as u16 - 1;
|
||||
|
||||
write_idt_value(0, mem)?;
|
||||
sregs.idt.base = BOOT_IDT_OFFSET.raw_value();
|
||||
sregs.idt.base = BOOT_IDT_START.raw_value();
|
||||
sregs.idt.limit = mem::size_of::<u64>() as u16 - 1;
|
||||
|
||||
sregs.cs = code_seg;
|
||||
@@ -269,6 +266,11 @@ fn create_msr_entries() -> Vec<kvm_msr_entry> {
|
||||
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()
|
||||
});
|
||||
|
||||
entries
|
||||
}
|
||||
@@ -296,20 +298,20 @@ mod tests {
|
||||
let gm = create_guest_mem();
|
||||
configure_segments_and_sregs(&gm, &mut sregs).unwrap();
|
||||
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_OFFSET));
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||
assert_eq!(
|
||||
0xaf9b000000ffff,
|
||||
read_u64(&gm, BOOT_GDT_OFFSET.unchecked_add(8))
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(8))
|
||||
);
|
||||
assert_eq!(
|
||||
0xcf93000000ffff,
|
||||
read_u64(&gm, BOOT_GDT_OFFSET.unchecked_add(16))
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(16))
|
||||
);
|
||||
assert_eq!(
|
||||
0x8f8b000000ffff,
|
||||
read_u64(&gm, BOOT_GDT_OFFSET.unchecked_add(24))
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(24))
|
||||
);
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_OFFSET));
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_START));
|
||||
|
||||
assert_eq!(0, sregs.cs.base);
|
||||
assert_eq!(0xfffff, sregs.ds.limit);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(
|
||||
clippy::unreadable_literal,
|
||||
clippy::const_static_lifetime,
|
||||
clippy::redundant_static_lifetimes,
|
||||
clippy::trivially_copy_pass_by_ref,
|
||||
clippy::useless_transmute,
|
||||
clippy::should_implement_trait,
|
||||
@@ -19,8 +19,8 @@
|
||||
pub mod bootparam;
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(clippy::unreadable_literal, clippy::const_static_lifetime)]
|
||||
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]
|
||||
pub mod mpspec;
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(clippy::unreadable_literal, clippy::const_static_lifetime)]
|
||||
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]
|
||||
pub mod msr_index;
|
||||
|
||||
@@ -4,14 +4,19 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = ">=1.2.1"
|
||||
epoll = "=4.0.1"
|
||||
kvm-bindings = "0.1"
|
||||
byteorder = "1.3.2"
|
||||
epoll = "4.1.0"
|
||||
kvm-bindings = "0.1.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = ">=0.2.39"
|
||||
log = "*"
|
||||
libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = ">=3.0.2"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
acpi = []
|
||||
cmos = []
|
||||
|
||||
52
devices/src/acpi.rs
Normal file
52
devices/src/acpi.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use BusDevice;
|
||||
|
||||
/// A device for handling ACPI shutdown and reboot
|
||||
pub struct AcpiShutdownDevice {
|
||||
exit_evt: EventFd,
|
||||
reset_evt: EventFd,
|
||||
}
|
||||
|
||||
impl AcpiShutdownDevice {
|
||||
/// Constructs a device that will signal the given event when the guest requests it.
|
||||
pub fn new(exit_evt: EventFd, reset_evt: EventFd) -> AcpiShutdownDevice {
|
||||
AcpiShutdownDevice {
|
||||
exit_evt,
|
||||
reset_evt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Same I/O port used for shutdown and reboot
|
||||
impl BusDevice for AcpiShutdownDevice {
|
||||
// Spec has all fields as zero
|
||||
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||
for i in data.iter_mut() {
|
||||
*i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) {
|
||||
if data[0] == 1 {
|
||||
debug!("ACPI Reboot signalled");
|
||||
if let Err(e) = self.reset_evt.write(1) {
|
||||
error!("Error triggering ACPI reset event: {}", e);
|
||||
}
|
||||
}
|
||||
// The ACPI DSDT table specifies the S5 sleep state (shutdown) as value 5
|
||||
const S5_SLEEP_VALUE: u8 = 5;
|
||||
const SLEEP_STATUS_EN_BIT: u8 = 5;
|
||||
const SLEEP_VALUE_BIT: u8 = 2;
|
||||
if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) {
|
||||
debug!("ACPI Shutdown signalled");
|
||||
if let Err(e) = self.exit_evt.write(1) {
|
||||
error!("Error triggering ACPI shutdown event: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ impl PartialOrd for BusRange {
|
||||
/// only restriction is that no two devices can overlap in this address space.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Bus {
|
||||
devices: BTreeMap<BusRange, Arc<Mutex<BusDevice>>>,
|
||||
devices: BTreeMap<BusRange, Arc<Mutex<dyn BusDevice>>>,
|
||||
}
|
||||
|
||||
impl Bus {
|
||||
@@ -88,7 +88,7 @@ impl Bus {
|
||||
}
|
||||
}
|
||||
|
||||
fn first_before(&self, addr: u64) -> Option<(BusRange, &Mutex<BusDevice>)> {
|
||||
fn first_before(&self, addr: u64) -> Option<(BusRange, &Mutex<dyn BusDevice>)> {
|
||||
let (range, dev) = self
|
||||
.devices
|
||||
.range(..=BusRange { base: addr, len: 1 })
|
||||
@@ -97,7 +97,7 @@ impl Bus {
|
||||
Some((*range, dev))
|
||||
}
|
||||
|
||||
pub fn resolve(&self, addr: u64) -> Option<(u64, u64, &Mutex<BusDevice>)> {
|
||||
pub fn resolve(&self, addr: u64) -> Option<(u64, u64, &Mutex<dyn BusDevice>)> {
|
||||
if let Some((range, dev)) = self.first_before(addr) {
|
||||
let offset = addr - range.base;
|
||||
if offset < range.len {
|
||||
@@ -108,7 +108,7 @@ impl Bus {
|
||||
}
|
||||
|
||||
/// Puts the given device at the given address space.
|
||||
pub fn insert(&mut self, device: Arc<Mutex<BusDevice>>, base: u64, len: u64) -> Result<()> {
|
||||
pub fn insert(&mut self, device: Arc<Mutex<dyn BusDevice>>, base: u64, len: u64) -> Result<()> {
|
||||
if len == 0 {
|
||||
return Err(Error::Overlap);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use kvm_bindings::kvm_msi;
|
||||
use kvm_ioctls::VmFd;
|
||||
use std::sync::Arc;
|
||||
use std::{io, result};
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -156,6 +157,7 @@ pub struct Ioapic {
|
||||
reg_sel: u32,
|
||||
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
|
||||
vm_fd: Arc<VmFd>,
|
||||
apic_address: GuestAddress,
|
||||
}
|
||||
|
||||
impl BusDevice for Ioapic {
|
||||
@@ -188,19 +190,19 @@ impl BusDevice for Ioapic {
|
||||
IOWIN_OFF => self.ioapic_write(value),
|
||||
_ => {
|
||||
error!("IOAPIC: failed writing at offset {}", offset);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ioapic {
|
||||
pub fn new(vm_fd: Arc<VmFd>) -> Ioapic {
|
||||
pub fn new(vm_fd: Arc<VmFd>, apic_address: GuestAddress) -> Ioapic {
|
||||
Ioapic {
|
||||
id: 0,
|
||||
reg_sel: 0,
|
||||
reg_entries: [0; NUM_IOAPIC_PINS],
|
||||
vm_fd,
|
||||
apic_address,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +242,7 @@ impl Ioapic {
|
||||
let redirection_hint: u8 = 1;
|
||||
|
||||
// Generate MSI message address
|
||||
let address_lo: u32 = 0xfee0_0000
|
||||
let address_lo: u32 = self.apic_address.0 as u32
|
||||
| u32::from(destination_id) << 12
|
||||
| u32::from(redirection_hint) << 3
|
||||
| u32::from(destination_mode) << 2;
|
||||
|
||||
115
devices/src/legacy/cmos.rs
Normal file
115
devices/src/legacy/cmos.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use libc::{gmtime_r, time, time_t, tm};
|
||||
use std::cmp::min;
|
||||
use std::mem;
|
||||
|
||||
use crate::BusDevice;
|
||||
|
||||
const INDEX_MASK: u8 = 0x7f;
|
||||
const INDEX_OFFSET: u64 = 0x0;
|
||||
const DATA_OFFSET: u64 = 0x1;
|
||||
const DATA_LEN: usize = 128;
|
||||
|
||||
/// A CMOS/RTC device commonly seen on x86 I/O port 0x70/0x71.
|
||||
pub struct Cmos {
|
||||
index: u8,
|
||||
data: [u8; DATA_LEN],
|
||||
}
|
||||
|
||||
impl Cmos {
|
||||
/// Constructs a CMOS/RTC device with initial data.
|
||||
/// `mem_below_4g` is the size of memory in bytes below the 32-bit gap.
|
||||
/// `mem_above_4g` is the size of memory in bytes above the 32-bit gap.
|
||||
pub fn new(mem_below_4g: u64, mem_above_4g: u64) -> Cmos {
|
||||
let mut data = [0u8; DATA_LEN];
|
||||
|
||||
// Extended memory from 16 MB to 4 GB in units of 64 KB
|
||||
let ext_mem = min(
|
||||
0xFFFF,
|
||||
mem_below_4g.saturating_sub(16 * 1024 * 1024) / (64 * 1024),
|
||||
);
|
||||
data[0x34] = ext_mem as u8;
|
||||
data[0x35] = (ext_mem >> 8) as u8;
|
||||
|
||||
// High memory (> 4GB) in units of 64 KB
|
||||
let high_mem = min(0x00FF_FFFF, mem_above_4g / (64 * 1024));
|
||||
data[0x5b] = high_mem as u8;
|
||||
data[0x5c] = (high_mem >> 8) as u8;
|
||||
data[0x5d] = (high_mem >> 16) as u8;
|
||||
|
||||
Cmos { index: 0, data }
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for Cmos {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
if data.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
match offset {
|
||||
INDEX_OFFSET => self.index = data[0] & INDEX_MASK,
|
||||
DATA_OFFSET => self.data[self.index as usize] = data[0],
|
||||
o => panic!("bad write offset on CMOS device: {}", o),
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
fn to_bcd(v: u8) -> u8 {
|
||||
assert!(v < 100);
|
||||
((v / 10) << 4) | (v % 10)
|
||||
}
|
||||
|
||||
if data.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
data[0] = match offset {
|
||||
INDEX_OFFSET => self.index,
|
||||
DATA_OFFSET => {
|
||||
let seconds;
|
||||
let minutes;
|
||||
let hours;
|
||||
let week_day;
|
||||
let day;
|
||||
let month;
|
||||
let year;
|
||||
// The time and gmtime_r calls are safe as long as the structs they are given are
|
||||
// large enough, and neither of them fail. It is safe to zero initialize the tm
|
||||
// struct because it contains only plain data.
|
||||
unsafe {
|
||||
let mut tm: tm = mem::zeroed();
|
||||
let mut now: time_t = 0;
|
||||
time(&mut now as *mut _);
|
||||
gmtime_r(&now, &mut tm as *mut _);
|
||||
// The following lines of code are safe but depend on tm being in scope.
|
||||
seconds = tm.tm_sec;
|
||||
minutes = tm.tm_min;
|
||||
hours = tm.tm_hour;
|
||||
week_day = tm.tm_wday + 1;
|
||||
day = tm.tm_mday;
|
||||
month = tm.tm_mon + 1;
|
||||
year = tm.tm_year;
|
||||
};
|
||||
match self.index {
|
||||
0x00 => to_bcd(seconds as u8),
|
||||
0x02 => to_bcd(minutes as u8),
|
||||
0x04 => to_bcd(hours as u8),
|
||||
0x06 => to_bcd(week_day as u8),
|
||||
0x07 => to_bcd(day as u8),
|
||||
0x08 => to_bcd(month as u8),
|
||||
0x09 => to_bcd((year % 100) as u8),
|
||||
0x32 => to_bcd(((year + 1900) / 100) as u8),
|
||||
_ => {
|
||||
// self.index is always guaranteed to be in range via INDEX_MASK.
|
||||
self.data[(self.index & INDEX_MASK) as usize]
|
||||
}
|
||||
}
|
||||
}
|
||||
o => panic!("bad read offset on CMOS device: {}", o),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use BusDevice;
|
||||
|
||||
@@ -34,8 +34,9 @@ impl BusDevice for I8042Device {
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
if data.len() == 1 && data[0] == 0xfe && offset == 3 {
|
||||
debug!("i8042 reset signalled");
|
||||
if let Err(e) = self.reset_evt.write(1) {
|
||||
println!("Error triggering i8042 reset event: {}", e);
|
||||
error!("Error triggering i8042 reset event: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,12 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
#[cfg(feature = "cmos")]
|
||||
mod cmos;
|
||||
mod i8042;
|
||||
mod serial;
|
||||
|
||||
#[cfg(feature = "cmos")]
|
||||
pub use self::cmos::Cmos;
|
||||
pub use self::i8042::I8042Device;
|
||||
pub use self::serial::Serial;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
use crate::{BusDevice, Interrupt};
|
||||
use std::collections::VecDeque;
|
||||
use std::{io, result};
|
||||
use vmm_sys_util::Result;
|
||||
use vmm_sys_util::errno::Result;
|
||||
|
||||
const LOOP_SIZE: usize = 0x40;
|
||||
|
||||
@@ -55,7 +55,7 @@ const DEFAULT_BAUD_DIVISOR: u16 = 12; // 9600 bps
|
||||
pub struct Serial {
|
||||
interrupt_enable: u8,
|
||||
interrupt_identification: u8,
|
||||
interrupt: Box<Interrupt>,
|
||||
interrupt: Box<dyn Interrupt>,
|
||||
line_control: u8,
|
||||
line_status: u8,
|
||||
modem_control: u8,
|
||||
@@ -63,11 +63,11 @@ pub struct Serial {
|
||||
scratch: u8,
|
||||
baud_divisor: u16,
|
||||
in_buffer: VecDeque<u8>,
|
||||
out: Option<Box<io::Write + Send>>,
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
}
|
||||
|
||||
impl Serial {
|
||||
fn new(interrupt: Box<Interrupt>, out: Option<Box<io::Write + Send>>) -> Serial {
|
||||
pub fn new(interrupt: Box<dyn Interrupt>, out: Option<Box<dyn io::Write + Send>>) -> Serial {
|
||||
Serial {
|
||||
interrupt_enable: 0,
|
||||
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
|
||||
@@ -84,12 +84,12 @@ impl Serial {
|
||||
}
|
||||
|
||||
/// Constructs a Serial port ready for output.
|
||||
pub fn new_out(interrupt: Box<Interrupt>, out: Box<io::Write + Send>) -> Serial {
|
||||
pub fn new_out(interrupt: Box<dyn Interrupt>, out: Box<dyn io::Write + Send>) -> Serial {
|
||||
Self::new(interrupt, Some(out))
|
||||
}
|
||||
|
||||
/// Constructs a Serial port with no connected output.
|
||||
pub fn new_sink(interrupt: Box<Interrupt>) -> Serial {
|
||||
pub fn new_sink(interrupt: Box<dyn Interrupt>) -> Serial {
|
||||
Self::new(interrupt, None)
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ mod tests {
|
||||
use super::*;
|
||||
use std::io;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
struct TestInterrupt {
|
||||
event_fd: EventFd,
|
||||
|
||||
@@ -19,10 +19,14 @@ extern crate vmm_sys_util;
|
||||
use std::fs::File;
|
||||
use std::{io, result};
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
mod acpi;
|
||||
mod bus;
|
||||
pub mod ioapic;
|
||||
pub mod legacy;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
pub use self::acpi::AcpiShutdownDevice;
|
||||
pub use self::bus::{Bus, BusDevice, Error as BusError};
|
||||
|
||||
pub type DeviceEventT = u16;
|
||||
@@ -63,6 +67,6 @@ pub enum Error {
|
||||
IoError(io::Error),
|
||||
}
|
||||
|
||||
pub trait Interrupt: Send {
|
||||
pub trait Interrupt: Send + Sync {
|
||||
fn deliver(&self) -> result::Result<(), std::io::Error>;
|
||||
}
|
||||
|
||||
96
docs/custom-image.md
Normal file
96
docs/custom-image.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# How to create a custom Clear Linux image
|
||||
|
||||
In the context of adding more utility to the cloudguest image being used
|
||||
for integration testing, this is a quick guide on how to achieve the creation
|
||||
of your own Clear Linux image using the official Clear Linux tooling.
|
||||
|
||||
## Prepare the environment
|
||||
|
||||
From the host, the goal is run a Clear Linux VM that will allow us to build
|
||||
the custom image we want.
|
||||
|
||||
```bash
|
||||
# Get latest CL version:
|
||||
IMG_VERSION=$(curl https://download.clearlinux.org/latest)
|
||||
# Get latest clear-kvm image:
|
||||
wget -P $HOME/workloads/ https://download.clearlinux.org/current/clear-${IMG_VERSION}-kvm.img.xz
|
||||
# Extract the image
|
||||
unxz $HOME/workloads/clear-${IMG_VERSION}-kvm.img.xz
|
||||
# Make sure cloud-hypervisor binary has CAP_NET_ADMIN capability set
|
||||
sudo setcap cap_net_admin+ep cloud-hypervisor
|
||||
# Boot cloud-hypervisor VM with the downloaded image
|
||||
./cloud-hypervisor -v --kernel $HOME/workloads/vmlinux --disk path=clear-${IMG_VERSION}-kvm.img --cmdline "console=ttyS0 console=hvc0 reboot=k panic=1 nomodules root=/dev/vda3 rw" --cpus 1 --memory size=4G --net tap=,mac=
|
||||
# Setup connectivity
|
||||
# First make sure to enable IP forwarding (disabled on Linux by default)
|
||||
sudo bash -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
|
||||
# Retrieve the interface name and the gateway IP
|
||||
IFACE=$(ip route | grep default | awk -F 'dev' '{print $2}' | awk -F ' ' '{print $1}')
|
||||
GW=$(ip route | grep vmtap0 | awk -F ' ' '{print $1}')
|
||||
# Create a new masquerade rule to tag the packets going out
|
||||
sudo iptables -t nat -A POSTROUTING -s ${GW} -o ${IFACE} -j MASQUERADE
|
||||
```
|
||||
|
||||
## Create the image
|
||||
|
||||
From the guest, we can now create the image.
|
||||
|
||||
```bash
|
||||
# Setup connectivity
|
||||
sudo ip addr add 192.168.249.2/24 dev enp0s3
|
||||
sudo ip route add default via 192.168.249.1
|
||||
# Install necessary bundles
|
||||
sudo swupd bundle-add clr-installer
|
||||
sudo swupd bundle-add os-installer
|
||||
# Download and update cloudguest image configuration
|
||||
wget https://download.clearlinux.org/current/config/image/cloudguest.yaml
|
||||
sed -i '/size: \"864M\"/d' cloudguest.yaml
|
||||
sed -i 's/\"800M\"/\"2G\"/g' cloudguest.yaml
|
||||
sed -i 's/bootloader,/bootloader,\n iperf,/g' cloudguest.yaml
|
||||
sed -i 's/systemd-networkd-autostart/sysadmin-basic,\n systemd-networkd-autostart/g' cloudguest.yaml
|
||||
# Create the custom cloudguest image
|
||||
clr-installer -c cloudguest.yaml
|
||||
# Make the guest accessible through ssh
|
||||
sudo mkdir -p /etc/ssh
|
||||
sudo bash -c "echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config"
|
||||
```
|
||||
|
||||
### Retrieve the image
|
||||
|
||||
Once the new image has been created and the guest is accessible through
|
||||
`ssh`, it is time to retrieve the image from the host.
|
||||
|
||||
```bash
|
||||
# Retrieve new image (this is a raw image)
|
||||
scp root@192.168.249.2:cloudguest.img .
|
||||
mv cloudguest.img clear-cloudguest-raw.img
|
||||
# Create the QCOW image from the RAW image
|
||||
qemu-img convert -p -f raw -O qcow2 clear-cloudguest-raw.img clear-cloudguest.img
|
||||
# Compress the QCOW image
|
||||
xz -k -T $(nproc) clear-cloudguest.img
|
||||
```
|
||||
|
||||
## Switch CI to use the new image
|
||||
|
||||
### Upload to Azure storage
|
||||
|
||||
The next step is to update the image stored as part of the Azure storage
|
||||
account, replacing it with the newly created image. This will make this
|
||||
new image available from the integration tests.
|
||||
This is usually achieved through the web interface.
|
||||
|
||||
### Update integration tests
|
||||
|
||||
Last step is about updating the integration tests to work with this new image.
|
||||
The key point is to identify the UUID of this new image so that it can be used
|
||||
directly from the tests.
|
||||
|
||||
Proceed as follow to determine this UUID:
|
||||
|
||||
```bash
|
||||
# Mount the image
|
||||
sudo mount -o loop,offset=$((2048 * 512)) clear-cloudguest-raw.img /mnt/
|
||||
# Identify UUID
|
||||
sudo cat /mnt/loader/entries/Clear-linux-kvm-*.conf | grep "root=PARTUUID="
|
||||
# Unmount the image
|
||||
sudo umount /mnt
|
||||
```
|
||||
61
docs/debug-port.md
Normal file
61
docs/debug-port.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# `cloud-hypervisor` debug IO port
|
||||
|
||||
`cloud-hypervisor` uses the [`0x80`](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
|
||||
I/O port to trace user defined guest events.
|
||||
|
||||
Whenever the guest write one byte between `0x0` and `0xF` on this particular
|
||||
I/O port, `cloud-hypervisor` will log and timestamp that event at the `debug`
|
||||
log level.
|
||||
|
||||
It is up to the guest stack to decide when and what to write to the 0x80 port
|
||||
in order to signal the host about specific events and have `cloud-hypervisor`
|
||||
log it.
|
||||
|
||||
`cloud-hypervisor` defines several debug port code ranges that should be used
|
||||
for debugging specific components of the guest software stack. When logging a
|
||||
write of one of those codes to the debug port, `cloud-hypervisor` adds a
|
||||
pre-defined string to the logs.
|
||||
|
||||
| Code Range | Component | Log string |
|
||||
| ---------------- | ----------- | ------------ |
|
||||
| `0x00` to `0x1f` | Firmware | `Firmware` |
|
||||
| `0x20` to `0x3f` | Bootloader | `Bootloader` |
|
||||
| `0x40` to `0x5f` | Kernel | `Kernel` |
|
||||
| `0x60` to `0x7f` | Userspace | `Userspace` |
|
||||
| `0x80` to `0xff` | Custom | `Custom` |
|
||||
|
||||
One typical use case is guest boot time measurement and tracing. By writing
|
||||
different values to the debug I/O port at different boot process steps, the
|
||||
guest will have `cloud-hypervisor` generate timestamped logs of all those steps.
|
||||
That provides a basic but convenient way of measuring not only the overall guest
|
||||
boot time but all intermediate steps as well.
|
||||
|
||||
## Logging
|
||||
|
||||
Assuming parts of the guest software stack have been instrumented to use the
|
||||
`cloud-hypervisor` debug I/O port, we may want to gather the related logs.
|
||||
|
||||
To do so we need to start `cloud-hypervisor` with the right debug level
|
||||
(`-vvv`). It is also recommended to have it log into a dedicated file in order
|
||||
to easily grep for the tracing logs (e.g.
|
||||
`--log-file /tmp/cloud-hypervisor.log`):
|
||||
|
||||
```
|
||||
./target/debug/cloud-hypervisor \
|
||||
--kernel ~/rust-hypervisor-firmware/target/target/release/hypervisor-fw \
|
||||
--disk path=~/hypervisor/images/clear-30080-kvm.img \
|
||||
--cpus 4 \
|
||||
--memory size=1024M \
|
||||
--rng \
|
||||
--log-file /tmp/ch-fw.log \
|
||||
-vvv
|
||||
```
|
||||
|
||||
After booting the guest, we then have to grep for the debug I/O port traces in
|
||||
the log file:
|
||||
|
||||
```Shell
|
||||
$ grep "Debug I/O port" /tmp/ch-fw.log
|
||||
cloud-hypervisor: 19.762449ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firmware code 0x0] 0.019004 seconds
|
||||
cloud-hypervisor: 403.499628ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firmware code 0x1] 0.402744 seconds
|
||||
```
|
||||
@@ -53,7 +53,7 @@ Assuming you have `clear-kvm.img` and `custom-vmlinux.bin` on your system, here
|
||||
./cloud-hypervisor \
|
||||
--cpus 4 \
|
||||
--memory "size=512,file=/dev/shm" \
|
||||
--disk clear-kvm.img \
|
||||
--disk path=clear-kvm.img \
|
||||
--kernel custom-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
|
||||
--fs tag=virtiofs,sock=/tmp/virtiofs,num_queues=1,queue_size=512
|
||||
|
||||
209
docs/iommu.md
Normal file
209
docs/iommu.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# Virtual IOMMU
|
||||
|
||||
## Rationales
|
||||
|
||||
Having the possibility to expose a virtual IOMMU to the guest can be
|
||||
interesting to support specific use cases. That being said, it is always
|
||||
important to keep in mind a virtual IOMMU can impact the performance of the
|
||||
attached devices, which is the reason why one should be careful when enabling
|
||||
this feature.
|
||||
|
||||
### Protect nested virtual machines
|
||||
|
||||
The first reason why one might want to expose a virtual IOMMU to the guest is
|
||||
to increase the security regarding the memory accesses performed by the virtual
|
||||
devices (VIRTIO devices), on behalf of the guest drivers.
|
||||
|
||||
With a virtual IOMMU, the VMM stands between the guest driver and its device
|
||||
counterpart, validating and translating every address before to try accessing
|
||||
the guest memory. This is standard interposition that is performed here by the
|
||||
VMM.
|
||||
|
||||
The increased security does not apply for a simple case where we have one VM
|
||||
per VMM. Because the guest cannot be trusted, as we always consider it could
|
||||
be malicious and gain unauthorized privileges inside the VM, preventing some
|
||||
devices from accessing the entire guest memory is pointless.
|
||||
|
||||
But let's take the interesting case of nested virtualization, and let's assume
|
||||
we have a VMM running a first layer VM. This L1 guest is fully trusted as the
|
||||
user intends to run multiple VMs from this L1. We can end up with multiple L2
|
||||
VMs running on a single L1 VM. In this particular case, and without exposing a
|
||||
virtual IOMMU to the L1 guest, it would be possible for any L2 guest to use the
|
||||
device implementation from the host VMM to access the entire guest L1 memory.
|
||||
The virtual IOMMU prevents from this kind of trouble as it will validate the
|
||||
addresses the device is authorized to access.
|
||||
|
||||
### Achieve VFIO nested
|
||||
|
||||
Another reason for having a virtual IOMMU is to allow passing physical devices
|
||||
from the host through multiple layers of virtualization. Let's take as example
|
||||
a system with a physical IOMMU running a VM with a virtual IOMMU. The
|
||||
implementation of the virtual IOMMU is responsible for updating the physical
|
||||
DMA Remapping table (DMAR) everytime the DMA mapping changes. This must happen
|
||||
through the VFIO framework on the host as this is the only userspace interface
|
||||
to interact with a physical IOMMU.
|
||||
|
||||
Relying on this update mechanism, it is possible to attach physical devices to
|
||||
the virtual IOMMU, which allows these devices to be passed from L1 to another
|
||||
layer of virtualization.
|
||||
|
||||
## Why virtio-iommu?
|
||||
|
||||
The Cloud Hypervisor project decided to implement the brand new virtio-iommu
|
||||
device in order to provide a virtual IOMMU to its users. The reason being the
|
||||
simplicity brought by the paravirtualization solution. By having one side
|
||||
handled from the guest itself, it removes the complexity of trapping memory
|
||||
page accesses and shadowing them. This is why the project will not try to
|
||||
implement a full emulation of a physical IOMMU.
|
||||
|
||||
## Pre-requisites
|
||||
|
||||
### Kernel
|
||||
|
||||
Since virtio-iommu has landed partially into the version 5.3 of the Linux
|
||||
kernel, a special branch is needed to get things working with Cloud Hypervisor.
|
||||
By partially, we are talking about x86 specifically, as it is already fully
|
||||
functional for ARM architectures.
|
||||
|
||||
## Usage
|
||||
|
||||
In order to expose a virtual IOMMU to the guest, it is required to create a
|
||||
virtio-iommu device and expose it through the ACPI IORT table. This can be
|
||||
simply achieved by attaching at least one device to the virtual IOMMU.
|
||||
|
||||
The way to expose to the guest a specific device as sitting behind this IOMMU
|
||||
is to explicitly tag it from the command line with the option `iommu=on`.
|
||||
|
||||
Not all devices support this extra option, and the default value will always
|
||||
be `off` since we want to avoid the performance impact for most users who don't
|
||||
need this.
|
||||
|
||||
Refer to the command line `--help` to find out which device support to be
|
||||
attached to the virtual IOMMU.
|
||||
|
||||
Below is a simple example exposing the `virtio-blk` device as attached to the
|
||||
virtual IOMMU:
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus 1 \
|
||||
--memory size=512M \
|
||||
--disk path=clear-kvm.img,iommu=on \
|
||||
--kernel custom-bzImage \
|
||||
--cmdline "console=ttyS0 root=/dev/vda3" \
|
||||
```
|
||||
|
||||
From a guest perspective, it is easy to verify if the device is protected by
|
||||
the virtual IOMMU. Check the directories listed under
|
||||
`/sys/kernel/iommu_groups`:
|
||||
|
||||
```bash
|
||||
ls /sys/kernel/iommu_groups
|
||||
0
|
||||
```
|
||||
|
||||
In this case, only one IOMMU group should be created. Under this group, it is
|
||||
possible to find out the b/d/f of the device(s) part of this group.
|
||||
|
||||
```bash
|
||||
ls /sys/kernel/iommu_groups/0/devices/
|
||||
0000:00:03.0
|
||||
```
|
||||
|
||||
And you can validate the device is the one we expect running `lspci`:
|
||||
|
||||
```bash
|
||||
lspci
|
||||
00:00.0 Host bridge: Intel Corporation Device 0d57
|
||||
00:01.0 Unassigned class [ffff]: Red Hat, Inc. Device 1057
|
||||
00:02.0 Unassigned class [ffff]: Red Hat, Inc. Virtio console
|
||||
00:03.0 Mass storage controller: Red Hat, Inc. Virtio block device
|
||||
00:04.0 Unassigned class [ffff]: Red Hat, Inc. Virtio RNG
|
||||
```
|
||||
|
||||
## Faster mappings
|
||||
|
||||
By default, the guest memory is mapped with 4k pages and no huge pages, which
|
||||
causes the virtual IOMMU device to be asked for 4k mappings only. This
|
||||
configuration slows down the setup of the physical IOMMU as an important number
|
||||
of requests need to be issued in order to create large mappings.
|
||||
|
||||
One use case is even more impacted by the slowdown, the nested VFIO case. When
|
||||
passing a device through a L2 guest, the VFIO driver running in L1 will update
|
||||
the DMAR entries for the specific device. Because VFIO pins the entire guest
|
||||
memory, this means the entire mapping of the L2 guest need to be stored into
|
||||
multiple 4k mappings. Obviously, the bigger the L2 guest RAM is, the longer the
|
||||
update of the mappings will last. There is an additional problem happening in
|
||||
this case, if the L2 guest RAM is quite large, it will require a large number
|
||||
of mappings, which might exceed the VFIO limit set on the host. The default
|
||||
value is 65536, which can simply be reached with a 256MiB sized RAM.
|
||||
|
||||
The way to solve both problems, the slowdown and the limit being exceeded, is
|
||||
to reduce the amount of requests to describe those same large mappings. This
|
||||
can be achieved by using 2MiB pages, known as huge pages. By seeing the guest
|
||||
RAM as larger pages, and because the virtual IOMMU device supports it, the
|
||||
guest will require less mappings, which will prevent the limit from being
|
||||
exceeded, but also will take less time to process them on the host. That's
|
||||
how using huge pages as much as possible can speed up VM boot time.
|
||||
|
||||
### Basic usage
|
||||
|
||||
Let's look at an example of how to run a guest with huge pages.
|
||||
|
||||
First, make sure your system has enough pages to cover the entire guest RAM:
|
||||
```bash
|
||||
# This example creates 4096 hugepages
|
||||
echo 4096 > /proc/sys/vm/nr_hugepages
|
||||
```
|
||||
|
||||
Next step is simply to create the VM. Two things are important, first we want
|
||||
the VM RAM to be mapped on huge pages by backing it with `/dev/hugepages`. And
|
||||
second thing, we need to create some huge pages in the guest itself so they can
|
||||
be consumed.
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus 1 \
|
||||
--memory size=8G,file=/dev/hugepages \
|
||||
--disk path=clear-kvm.img \
|
||||
--kernel custom-bzImage \
|
||||
--cmdline "console=ttyS0 root=/dev/vda3 hugepagesz=2M hugepages=2048" \
|
||||
--net tap=,mac=,iommu=on
|
||||
```
|
||||
|
||||
### Nested usage
|
||||
|
||||
Let's now look at the specific example of nested virtualization. In order to
|
||||
reach optimized performances, the L2 guest also need to be mapped based on
|
||||
huge pages. Here is how to achieve this, assuming the physical device you are
|
||||
passing through is `0000:00:01.0`.
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus 1 \
|
||||
--memory size=8G,file=/dev/hugepages \
|
||||
--disk path=clear-kvm.img \
|
||||
--kernel custom-bzImage \
|
||||
--cmdline "console=ttyS0 root=/dev/vda3 kvm-intel.nested=1 vfio_iommu_type1.allow_unsafe_interrupts rw hugepagesz=2M hugepages=2048" \
|
||||
--device path=/sys/bus/pci/devices/0000:00:01.0,iommu=on
|
||||
```
|
||||
|
||||
Once the L1 VM is running, unbind the device from the default driver in the
|
||||
guest, and bind it to VFIO (it should appear as `0000:00:04.0`).
|
||||
|
||||
```bash
|
||||
echo 0000:00:04.0 > /sys/bus/pci/devices/0000\:00\:04.0/driver/unbind
|
||||
echo 8086 1502 > /sys/bus/pci/drivers/vfio-pci/new_id
|
||||
```
|
||||
|
||||
Last thing is to start the L2 guest with the huge pages memory backend.
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus 1 \
|
||||
--memory size=4G,file=/dev/hugepages \
|
||||
--disk path=clear-kvm.img \
|
||||
--kernel custom-bzImage \
|
||||
--cmdline "console=ttyS0 root=/dev/vda3" \
|
||||
--device path=/sys/bus/pci/devices/0000:00:04.0
|
||||
```
|
||||
175
docs/networking.md
Normal file
175
docs/networking.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# How to use networking
|
||||
|
||||
cloud-hypervisor can emulate one or more virtual network interfaces, represented at the hypervisor host by [tap devices](https://www.kernel.org/doc/Documentation/networking/tuntap.txt"). This guide briefly describes, in a manual and distribution neutral way, how to setup and use networking with cloud-hypevisor.
|
||||
|
||||
## Start cloud-hypervisor with net devices
|
||||
|
||||
Use one `--net` command-line argument from cloud-hypervisor to specify the emulation of one or more virtual NIC's. The example below instructs cloud-hypervisor to emulate for instance 2 virtual NIC's:
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus 4 \
|
||||
--memory "size=512M" \
|
||||
--disk path=my-root-disk.img \
|
||||
--kernel my-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
|
||||
--net tap=ich0,mac=a4:a1:c2:00:00:01,ip=192.168.4.2,mask=255.255.255.0 \
|
||||
tap=ich1,mac=a4:a1:c2:00:00:02,ip=10.0.1.2,mask=255.255.255.0
|
||||
```
|
||||
|
||||
The `--net` argument takes 1 or more space-separated strings of key value pairs containing the following 4 keys or fields:
|
||||
|
||||
| Name | Purpose | Optional |
|
||||
| -------- |----------------------------| ----------|
|
||||
| tap | tap device name | Yes |
|
||||
| mac | vNIC mac address | Yes |
|
||||
| ip | tap IP IP address | yes |
|
||||
| mask | tap IP netmask | Yes |
|
||||
|
||||
## Configure the tap devices
|
||||
|
||||
After starting cloud-hypervisor as shown above, 2 tap devices with state down will become available at the host:
|
||||
|
||||
```bash
|
||||
root@host:~# ip link show ich0
|
||||
78: ich0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 72:54:12:ff:ce:6f brd ff:ff:ff:ff:ff:ff
|
||||
root@host:~# ip link show ich1
|
||||
79: ich1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 06:7a:fc:1b:9a:67 brd ff:ff:ff:ff:ff:ff
|
||||
```
|
||||
|
||||
Set the tap devices to up state:
|
||||
|
||||
```bash
|
||||
root@host:~# ip link set up ich0
|
||||
root@host:~# ip link set up ich1
|
||||
|
||||
root@host:~# ip link show ich0
|
||||
78: ich0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 72:54:12:ff:ce:6f brd ff:ff:ff:ff:ff:ff
|
||||
root@host:~# ip link show ich1
|
||||
79: ich1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 06:7a:fc:1b:9a:67 brd ff:ff:ff:ff:ff:ff
|
||||
```
|
||||
|
||||
## Connect tap devices
|
||||
|
||||
Different networking models can be used to provide external connectivity. In this example we will
|
||||
use 2 linux bridges emulating 2 different networks. The integration bridge (ich-int) in this example will also be used
|
||||
for external connectivity.
|
||||
|
||||
Create the bridges and connect the cloud-hypervisor tap devices to the bridges:
|
||||
|
||||
```bash
|
||||
root@host:~# brctl addbr ich-int
|
||||
root@host:~# brctl addbr ich-dpl
|
||||
root@host:~# ip link set up ich-int
|
||||
root@host:~# ip link set up ich-dpl
|
||||
root@host:~# brctl addif ich-int ich0
|
||||
root@host:~# brctl addif ich-dpl ich1
|
||||
root@host:~# brctl show
|
||||
bridge name bridge id STP enabled interfaces
|
||||
ich-dpl 8000.067afc1b9a67 no ich1
|
||||
ich-int 8000.725412ffce6f no ich0
|
||||
```
|
||||
This completes the layer 2 wiring: The cloud-hypervisor is now connected to the hypervisor host via the 2 linux bridges.
|
||||
|
||||
## IP (Layer 3) provisioning
|
||||
|
||||
### Hypervisor host
|
||||
|
||||
On the hypervisor host add the network gateway IP address of each network to the 2 linux bridges:
|
||||
|
||||
```bash
|
||||
root@host:~# ip addr add 192.168.4.1/24 dev ich-int
|
||||
root@host:~# ip addr add 10.0.1.1/24 dev ich-dpl
|
||||
```
|
||||
The routing table of the hypervisor host should now also have corresponding routing entries:
|
||||
|
||||
```bash
|
||||
root@host:~# route -n
|
||||
Kernel IP routing table
|
||||
Destination Gateway Genmask Flags Metric Ref Use Iface
|
||||
0.0.0.0 192.168.178.1 0.0.0.0 UG 600 0 0 wlan1
|
||||
10.0.1.0 0.0.0.0 255.255.255.0 U 0 0 0 ich-dpl
|
||||
192.168.4.0 0.0.0.0 255.255.255.0 U 0 0 0 ich-int
|
||||
192.168.178.0 0.0.0.0 255.255.255.0 U 600 0 0 wlan1
|
||||
```
|
||||
### Virtual Machine
|
||||
|
||||
Within the virtual machine set the vNIC's to up state and provision the corresponding IP addresses on the 2 vNIC's. The steps outlined below use the ip command as an example. Alternative distribution specific procedures can also apply.
|
||||
|
||||
```bash
|
||||
root@guest:~# ip link set up enp0s2
|
||||
root@guest:~# ip link set up enp0s3
|
||||
root@guest:~# ip addr add 192.168.4.2/24 dev enp0s2
|
||||
root@guest:~# ip addr add 10.0.1.2/24 dev enp0s3
|
||||
```
|
||||
|
||||
IP connectivity between the virtual machine and the hypervisor-host can be verified by sending
|
||||
ICMP requests to the hypervisor-host for the gateway IP address from within the virtual machine:
|
||||
|
||||
```bash
|
||||
root@guest:~# ping 192.168.4.1
|
||||
PING 192.168.4.1 (192.168.4.1) 56(84) bytes of data.
|
||||
64 bytes from 192.168.4.1: icmp_seq=1 ttl=64 time=0.456 ms
|
||||
64 bytes from 192.168.4.1: icmp_seq=2 ttl=64 time=0.226 ms
|
||||
root@guest:~# ping 10.0.1.1
|
||||
PING 10.0.1.1 (10.0.1.1) 56(84) bytes of data.
|
||||
64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.449 ms
|
||||
64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=0.393 ms
|
||||
```
|
||||
|
||||
The connection can now be used for instance to log into the virtual machine with
|
||||
ssh under the precondition that the machine has an ssh daemon provisioned:
|
||||
|
||||
```bash
|
||||
root@host:~# ssh root@192.168.4.2
|
||||
The authenticity of host '192.168.4.2 (192.168.4.2)' can't be established.
|
||||
ECDSA key fingerprint is SHA256:qNAUmTtDMW9pNuZARkpLQhfw+Yc1tqUDBrQp7aZGSjw.
|
||||
Are you sure you want to continue connecting (yes/no)? yes
|
||||
Warning: Permanently added '192.168.4.2' (ECDSA) to the list of known hosts.
|
||||
root@192.168.4.2's password:
|
||||
Linux cloud-hypervisor 5.2.0 #2 SMP Thu Jul 11 08:08:16 CEST 2019 x86_64
|
||||
|
||||
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
|
||||
permitted by applicable law.
|
||||
|
||||
|
||||
Last login: Fri Jul 12 13:27:56 2019 from 192.168.4.1
|
||||
root@guest:~#
|
||||
```
|
||||
|
||||
## Internet connectivity
|
||||
|
||||
To enable internet connectivity a default gw and a nameserver has to be set within
|
||||
the virtual machine:
|
||||
|
||||
```bash
|
||||
root@guest:~# ip route add default via 192.168.4.1
|
||||
root@guest:~# cat /etc/resolv.conf
|
||||
options timeout:2
|
||||
domain vallis.nl
|
||||
search vallis.nl
|
||||
nameserver 192.168.178.1
|
||||
```
|
||||
|
||||
make sure that the default gateway of the hypervisor host (in this example host 192.168.178.1 which is an adsl router) has an entry in the routing table for the 192.168.4.0/24 network otherwise IP connectivity will not work.
|
||||
|
||||
```bash
|
||||
root@guest:~# nslookup ftp.nl.debian.org
|
||||
Server: 192.168.178.1
|
||||
Address: 192.168.178.1#53
|
||||
|
||||
Non-authoritative answer:
|
||||
cdn-fastly.deb.debian.org canonical name = prod.debian.map.fastly.net.
|
||||
Name: prod.debian.map.fastly.net
|
||||
Address: 151.101.36.204
|
||||
|
||||
root@guest:~# apt-get update
|
||||
Ign:1 http://cdn-fastly.deb.debian.org/debian stretch InRelease
|
||||
Get:2 http://cdn-fastly.deb.debian.org/debian stretch Release [118 kB]
|
||||
Get:3 http://cdn-fastly.deb.debian.org/debian stretch Release.gpg [2434 B]
|
||||
Fetched 120 kB in 1s (110 kB/s)
|
||||
```
|
||||
@@ -66,13 +66,13 @@ takes the device's sysfs path as an argument. In our example it is
|
||||
```
|
||||
./target/debug/cloud-hypervisor \
|
||||
--kernel ~/vmlinux \
|
||||
--disk ~/clear-29160-kvm.img \
|
||||
--disk path=~/clear-29160-kvm.img \
|
||||
--console off \
|
||||
--serial tty \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||
--cpus 4 \
|
||||
--memory size=512M \
|
||||
--device /sys/bus/pci/devices/0000:01:00.0/
|
||||
--device path=/sys/bus/pci/devices/0000:01:00.0/
|
||||
```
|
||||
|
||||
The guest kernel will then detect the card reader on its PCI bus and provided
|
||||
|
||||
@@ -4,4 +4,4 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
@@ -4,14 +4,14 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
libc = ">=0.2.39"
|
||||
rand = ">=0.6.5"
|
||||
serde = ">=1.0.27"
|
||||
libc = "0.2.60"
|
||||
rand = "0.7.0"
|
||||
serde = "1.0.98"
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
net_gen = { path = "../net_gen" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = ">=1.1.0"
|
||||
pnet = "=0.22.0"
|
||||
serde_json = ">=1.0.9"
|
||||
lazy_static = "1.3.0"
|
||||
pnet = "0.22.0"
|
||||
serde_json = "1.0.40"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use rand::Rng;
|
||||
use std::fmt;
|
||||
use std::result::Result;
|
||||
|
||||
use serde::de::{Deserialize, Deserializer, Error};
|
||||
@@ -66,14 +67,6 @@ impl MacAddr {
|
||||
&self.bytes
|
||||
}
|
||||
|
||||
pub fn to_string(self) -> String {
|
||||
let b = &self.bytes;
|
||||
format!(
|
||||
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
|
||||
b[0], b[1], b[2], b[3], b[4], b[5]
|
||||
)
|
||||
}
|
||||
|
||||
pub fn local_random() -> MacAddr {
|
||||
// Generate a fully random MAC
|
||||
let mut random_bytes = rand::thread_rng().gen::<[u8; MAC_ADDR_LEN]>();
|
||||
@@ -87,6 +80,17 @@ impl MacAddr {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MacAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let b = &self.bytes;
|
||||
write!(
|
||||
f,
|
||||
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
|
||||
b[0], b[1], b[2], b[3], b[4], b[5]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for MacAddr {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
|
||||
@@ -49,6 +49,15 @@ impl PartialEq for Tap {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::clone::Clone for Tap {
|
||||
fn clone(&self) -> Self {
|
||||
Tap {
|
||||
tap_file: self.tap_file.try_clone().unwrap(),
|
||||
if_name: self.if_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a byte vector representing the contents of a null terminated C string which
|
||||
// contains if_name.
|
||||
fn build_terminated_if_name(if_name: &str) -> Result<Vec<u8>> {
|
||||
@@ -396,7 +405,9 @@ mod tests {
|
||||
// For a given interface name, this returns a tuple that contains the MAC address of the
|
||||
// interface, an object that can be used to send Ethernet frames, and a receiver of
|
||||
// Ethernet frames arriving at the specified interface.
|
||||
fn pnet_get_mac_tx_rx(ifname: String) -> (MacAddr, Box<DataLinkSender>, Box<DataLinkReceiver>) {
|
||||
fn pnet_get_mac_tx_rx(
|
||||
ifname: String,
|
||||
) -> (MacAddr, Box<dyn DataLinkSender>, Box<dyn DataLinkReceiver>) {
|
||||
let interface_name_matches = |iface: &NetworkInterface| iface.name == ifname;
|
||||
|
||||
// Find the network interface with the provided name.
|
||||
|
||||
@@ -6,11 +6,11 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
byteorder = "*"
|
||||
byteorder = "1.3.2"
|
||||
devices = { path = "../devices" }
|
||||
kvm-bindings = "0.1"
|
||||
kvm-bindings = "0.1.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = ">=0.2.39"
|
||||
log = "*"
|
||||
libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
@@ -71,23 +71,19 @@ impl PciDevice for PciRoot {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PciConfigIo {
|
||||
pub struct PciBus {
|
||||
/// Devices attached to this bus.
|
||||
/// Device 0 is host bridge.
|
||||
devices: Vec<Arc<Mutex<dyn PciDevice>>>,
|
||||
/// Config space register.
|
||||
config_address: u32,
|
||||
}
|
||||
|
||||
impl PciConfigIo {
|
||||
impl PciBus {
|
||||
pub fn new(pci_root: PciRoot) -> Self {
|
||||
let mut devices: Vec<Arc<Mutex<dyn PciDevice>>> = Vec::new();
|
||||
|
||||
devices.push(Arc::new(Mutex::new(pci_root)));
|
||||
|
||||
PciConfigIo {
|
||||
devices,
|
||||
config_address: 0,
|
||||
}
|
||||
PciBus { devices }
|
||||
}
|
||||
|
||||
pub fn register_mapping(
|
||||
@@ -119,13 +115,32 @@ impl PciConfigIo {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next_device_id(&self) -> u32 {
|
||||
self.devices.len() as u32
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PciConfigIo {
|
||||
/// Config space register.
|
||||
config_address: u32,
|
||||
pci_bus: Arc<Mutex<PciBus>>,
|
||||
}
|
||||
|
||||
impl PciConfigIo {
|
||||
pub fn new(pci_bus: Arc<Mutex<PciBus>>) -> Self {
|
||||
PciConfigIo {
|
||||
pci_bus,
|
||||
config_address: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config_space_read(&self) -> u32 {
|
||||
let enabled = (self.config_address & 0x8000_0000) != 0;
|
||||
if !enabled {
|
||||
return 0xffff_ffff;
|
||||
}
|
||||
|
||||
let (bus, device, _function, register) =
|
||||
let (bus, device, function, register) =
|
||||
parse_config_address(self.config_address & !0x8000_0000);
|
||||
|
||||
// Only support one bus.
|
||||
@@ -133,9 +148,19 @@ impl PciConfigIo {
|
||||
return 0xffff_ffff;
|
||||
}
|
||||
|
||||
self.devices.get(device).map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
})
|
||||
// Don't support multi-function devices.
|
||||
if function > 0 {
|
||||
return 0xffff_ffff;
|
||||
}
|
||||
|
||||
self.pci_bus
|
||||
.lock()
|
||||
.unwrap()
|
||||
.devices
|
||||
.get(device)
|
||||
.map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn config_space_write(&mut self, offset: u64, data: &[u8]) {
|
||||
@@ -156,7 +181,7 @@ impl PciConfigIo {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(d) = self.devices.get(device) {
|
||||
if let Some(d) = self.pci_bus.lock().unwrap().devices.get(device) {
|
||||
d.lock()
|
||||
.unwrap()
|
||||
.write_config_register(register, offset, data);
|
||||
@@ -187,8 +212,8 @@ impl BusDevice for PciConfigIo {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
// `offset` is relative to 0xcf8
|
||||
let value = match offset {
|
||||
0...3 => self.config_address,
|
||||
4...7 => self.config_space_read(),
|
||||
0..=3 => self.config_address,
|
||||
4..=7 => self.config_space_read(),
|
||||
_ => 0xffff_ffff,
|
||||
};
|
||||
|
||||
@@ -209,8 +234,8 @@ impl BusDevice for PciConfigIo {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
// `offset` is relative to 0xcf8
|
||||
match offset {
|
||||
o @ 0...3 => self.set_config_address(o, data),
|
||||
o @ 4...7 => self.config_space_write(o - 4, data),
|
||||
o @ 0..=3 => self.set_config_address(o, data),
|
||||
o @ 4..=7 => self.config_space_write(o - 4, data),
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
@@ -218,17 +243,12 @@ impl BusDevice for PciConfigIo {
|
||||
|
||||
/// Emulates PCI memory-mapped configuration access mechanism.
|
||||
pub struct PciConfigMmio {
|
||||
/// Devices attached to this bus.
|
||||
/// Device 0 is host bridge.
|
||||
devices: Vec<Arc<Mutex<dyn PciDevice>>>,
|
||||
pci_bus: Arc<Mutex<PciBus>>,
|
||||
}
|
||||
|
||||
impl PciConfigMmio {
|
||||
pub fn new(pci_root: PciRoot) -> Self {
|
||||
let mut devices: Vec<Arc<Mutex<dyn PciDevice>>> = Vec::new();
|
||||
|
||||
devices.push(Arc::new(Mutex::new(pci_root)));
|
||||
PciConfigMmio { devices }
|
||||
pub fn new(pci_bus: Arc<Mutex<PciBus>>) -> Self {
|
||||
PciConfigMmio { pci_bus }
|
||||
}
|
||||
|
||||
fn config_space_read(&self, config_address: u32) -> u32 {
|
||||
@@ -239,9 +259,14 @@ impl PciConfigMmio {
|
||||
return 0xffff_ffff;
|
||||
}
|
||||
|
||||
self.devices.get(device).map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
})
|
||||
self.pci_bus
|
||||
.lock()
|
||||
.unwrap()
|
||||
.devices
|
||||
.get(device)
|
||||
.map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
})
|
||||
}
|
||||
|
||||
fn config_space_write(&mut self, config_address: u32, offset: u64, data: &[u8]) {
|
||||
@@ -256,7 +281,7 @@ impl PciConfigMmio {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(d) = self.devices.get(device) {
|
||||
if let Some(d) = self.pci_bus.lock().unwrap().devices.get(device) {
|
||||
d.lock()
|
||||
.unwrap()
|
||||
.write_config_register(register, offset, data);
|
||||
|
||||
@@ -14,8 +14,10 @@ const NUM_CONFIGURATION_REGISTERS: usize = 64;
|
||||
const STATUS_REG: usize = 1;
|
||||
const STATUS_REG_CAPABILITIES_USED_MASK: u32 = 0x0010_0000;
|
||||
const BAR0_REG: usize = 4;
|
||||
const ROM_BAR_REG: usize = 12;
|
||||
const BAR_IO_ADDR_MASK: u32 = 0xffff_fffc;
|
||||
const BAR_MEM_ADDR_MASK: u32 = 0xffff_fff0;
|
||||
const ROM_BAR_ADDR_MASK: u32 = 0xffff_f800;
|
||||
const NUM_BAR_REGS: usize = 6;
|
||||
const CAPABILITY_LIST_HEAD_OFFSET: usize = 0x34;
|
||||
const FIRST_CAPABILITY_OFFSET: usize = 0x40;
|
||||
@@ -249,6 +251,8 @@ pub struct PciConfiguration {
|
||||
writable_bits: [u32; NUM_CONFIGURATION_REGISTERS], // writable bits for each register.
|
||||
bar_size: [u32; NUM_BAR_REGS],
|
||||
bar_used: [bool; NUM_BAR_REGS],
|
||||
rom_bar_size: u32,
|
||||
rom_bar_used: bool,
|
||||
// Contains the byte offset and size of the last capability.
|
||||
last_capability: Option<(usize, usize)>,
|
||||
msix_cap_reg_idx: Option<usize>,
|
||||
@@ -289,6 +293,10 @@ pub enum Error {
|
||||
CapabilityEmpty,
|
||||
CapabilityLengthInvalid(usize),
|
||||
CapabilitySpaceFull(usize),
|
||||
RomBarAddressInvalid(u64, u64),
|
||||
RomBarInUse(usize),
|
||||
RomBarInvalid(usize),
|
||||
RomBarSizeInvalid(u64),
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
@@ -312,6 +320,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),
|
||||
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),
|
||||
RomBarSizeInvalid(s) => write!(f, "rom bar address {} not a power of two", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,6 +374,8 @@ impl PciConfiguration {
|
||||
writable_bits,
|
||||
bar_size,
|
||||
bar_used: [false; NUM_BAR_REGS],
|
||||
rom_bar_size: 0,
|
||||
rom_bar_used: false,
|
||||
last_capability: None,
|
||||
msix_cap_reg_idx: None,
|
||||
msix_config,
|
||||
@@ -376,12 +390,15 @@ impl PciConfiguration {
|
||||
/// Writes a 32bit register to `reg_idx` in the register map.
|
||||
pub fn write_reg(&mut self, reg_idx: usize, value: u32) {
|
||||
let mut mask = self.writable_bits[reg_idx];
|
||||
if reg_idx >= BAR0_REG && reg_idx < BAR0_REG + NUM_BAR_REGS {
|
||||
if reg_idx >= BAR0_REG
|
||||
&& reg_idx < BAR0_REG + NUM_BAR_REGS
|
||||
&& (value & BAR_MEM_ADDR_MASK) == BAR_MEM_ADDR_MASK
|
||||
{
|
||||
// Handle very specific case where the BAR is being written with
|
||||
// all 1's to retrieve the BAR size on 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 && (value & ROM_BAR_ADDR_MASK) == ROM_BAR_ADDR_MASK {
|
||||
mask = self.rom_bar_size;
|
||||
}
|
||||
|
||||
if let Some(r) = self.registers.get_mut(reg_idx) {
|
||||
@@ -500,6 +517,36 @@ impl PciConfiguration {
|
||||
Ok(config.reg_idx)
|
||||
}
|
||||
|
||||
/// Adds rom expansion BAR.
|
||||
pub fn add_pci_rom_bar(&mut self, config: &PciBarConfiguration, active: u32) -> Result<usize> {
|
||||
if self.rom_bar_used {
|
||||
return Err(Error::RomBarInUse(config.reg_idx));
|
||||
}
|
||||
|
||||
if config.size.count_ones() != 1 {
|
||||
return Err(Error::RomBarSizeInvalid(config.size));
|
||||
}
|
||||
|
||||
if config.reg_idx != ROM_BAR_REG {
|
||||
return Err(Error::RomBarInvalid(config.reg_idx));
|
||||
}
|
||||
|
||||
let end_addr = config
|
||||
.addr
|
||||
.checked_add(config.size - 1)
|
||||
.ok_or_else(|| Error::RomBarAddressInvalid(config.addr, config.size))?;
|
||||
|
||||
if end_addr > u64::from(u32::max_value()) {
|
||||
return Err(Error::RomBarAddressInvalid(config.addr, config.size));
|
||||
}
|
||||
|
||||
self.registers[config.reg_idx] = (config.addr as u32) | active;
|
||||
self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK;
|
||||
self.rom_bar_size = config.size as u32;
|
||||
self.rom_bar_used = true;
|
||||
Ok(config.reg_idx)
|
||||
}
|
||||
|
||||
/// Returns the address of the given 32 bits BAR region.
|
||||
pub fn get_bar32_addr(&self, bar_num: usize) -> u32 {
|
||||
let bar_idx = BAR0_REG + bar_num;
|
||||
@@ -663,8 +710,6 @@ mod tests {
|
||||
#[derive(Clone, Copy, Default)]
|
||||
#[allow(dead_code)]
|
||||
struct TestCap {
|
||||
_vndr: u8,
|
||||
_next: u8,
|
||||
len: u8,
|
||||
foo: u8,
|
||||
}
|
||||
@@ -683,7 +728,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn add_capability() {
|
||||
let mut cfg = PciConfiguration::new(
|
||||
0x1234,
|
||||
@@ -698,18 +742,11 @@ mod tests {
|
||||
);
|
||||
|
||||
// Add two capabilities with different contents.
|
||||
let cap1 = TestCap {
|
||||
_vndr: 0,
|
||||
_next: 0,
|
||||
len: 4,
|
||||
foo: 0xAA,
|
||||
};
|
||||
let cap1 = TestCap { len: 4, foo: 0xAA };
|
||||
let cap1_offset = cfg.add_capability(&cap1).unwrap();
|
||||
assert_eq!(cap1_offset % 4, 0);
|
||||
|
||||
let cap2 = TestCap {
|
||||
_vndr: 0,
|
||||
_next: 0,
|
||||
len: 0x04,
|
||||
foo: 0x55,
|
||||
};
|
||||
|
||||
@@ -11,14 +11,14 @@ use std::fmt::{self, Display};
|
||||
use std::sync::Arc;
|
||||
use vm_allocator::SystemAllocator;
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct InterruptParameters<'a> {
|
||||
pub msix: Option<&'a MsixTableEntry>,
|
||||
}
|
||||
|
||||
pub type InterruptDelivery =
|
||||
Box<Fn(InterruptParameters) -> std::result::Result<(), std::io::Error> + Send + Sync>;
|
||||
Box<dyn Fn(InterruptParameters) -> std::result::Result<(), std::io::Error> + Send + Sync>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -70,11 +70,6 @@ pub trait PciDevice: BusDevice {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
/// Register any capabilties specified by the device.
|
||||
fn register_device_capabilities(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets a list of ioeventfds that should be registered with the running VM. The list is
|
||||
/// returned as a Vec of (eventfd, addr, datamatch) tuples.
|
||||
fn ioeventfds(&self) -> Vec<(&EventFd, u64, u64)> {
|
||||
@@ -95,6 +90,4 @@ pub trait PciDevice: BusDevice {
|
||||
/// * `addr` - The guest address inside the BAR.
|
||||
/// * `data` - The data to write.
|
||||
fn write_bar(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
|
||||
/// Invoked when the device is sandboxed.
|
||||
fn on_device_sandboxed(&mut self) {}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ mod device;
|
||||
mod msi;
|
||||
mod msix;
|
||||
|
||||
pub use self::bus::{PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
|
||||
pub use self::bus::{PciBus, PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
|
||||
pub use self::configuration::{
|
||||
PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability, PciCapabilityID,
|
||||
PciClassCode, PciConfiguration, PciHeaderType, PciMassStorageSubclass,
|
||||
|
||||
@@ -53,6 +53,7 @@ pub struct MsixConfig {
|
||||
pub pba_entries: Vec<u64>,
|
||||
interrupt_cb: Option<Arc<InterruptDelivery>>,
|
||||
masked: bool,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl MsixConfig {
|
||||
@@ -70,6 +71,7 @@ impl MsixConfig {
|
||||
pba_entries,
|
||||
interrupt_cb: None,
|
||||
masked: false,
|
||||
enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,10 +83,15 @@ impl MsixConfig {
|
||||
self.masked
|
||||
}
|
||||
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
pub fn set_msg_ctl(&mut self, reg: u16) {
|
||||
let old_masked = self.masked;
|
||||
|
||||
self.masked = ((reg >> FUNCTION_MASK_BIT) & 1u16) == 1u16;
|
||||
self.enabled = ((reg >> MSIX_ENABLE_BIT) & 1u16) == 1u16;
|
||||
|
||||
// If the Function Mask bit was set, and has just been cleared, it's
|
||||
// important to go through the entire PBA to check if there was any
|
||||
@@ -111,7 +118,7 @@ impl MsixConfig {
|
||||
0x0 => self.table_entries[index].msg_addr_lo,
|
||||
0x4 => self.table_entries[index].msg_addr_hi,
|
||||
0x8 => self.table_entries[index].msg_data,
|
||||
0x10 => self.table_entries[index].vector_ctl,
|
||||
0xc => self.table_entries[index].vector_ctl,
|
||||
_ => {
|
||||
error!("invalid offset");
|
||||
0
|
||||
@@ -162,7 +169,7 @@ impl MsixConfig {
|
||||
0x0 => self.table_entries[index].msg_addr_lo = value,
|
||||
0x4 => self.table_entries[index].msg_addr_hi = value,
|
||||
0x8 => self.table_entries[index].msg_data = value,
|
||||
0x10 => {
|
||||
0xc => {
|
||||
old_entry = Some(self.table_entries[index].clone());
|
||||
self.table_entries[index].vector_ctl = value;
|
||||
}
|
||||
@@ -359,11 +366,11 @@ impl MsixCap {
|
||||
}
|
||||
|
||||
pub fn table_offset(&self) -> u32 {
|
||||
self.table >> 3
|
||||
self.table & 0xffff_fff8
|
||||
}
|
||||
|
||||
pub fn pba_offset(&self) -> u32 {
|
||||
self.pba >> 3
|
||||
self.pba & 0xffff_fff8
|
||||
}
|
||||
|
||||
pub fn table_bir(&self) -> u32 {
|
||||
|
||||
@@ -9,11 +9,11 @@ license = "BSD-3-Clause"
|
||||
path = "src/qcow.rs"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "*"
|
||||
libc = "*"
|
||||
log = "*"
|
||||
remain = "*"
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
byteorder = "1.3.2"
|
||||
libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
remain = "0.1.3"
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "*"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
220
qcow/src/qcow.rs
220
qcow/src/qcow.rs
@@ -12,9 +12,12 @@ mod vec_cache;
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use libc::{EINVAL, ENOSPC, ENOTSUP};
|
||||
use remain::sorted;
|
||||
use vmm_sys_util::{FileSetLen, FileSync, PunchHole, SeekHole, WriteZeroes};
|
||||
use vmm_sys_util::{
|
||||
file_traits::FileSetLen, file_traits::FileSync, seek_hole::SeekHole, write_zeroes::PunchHole,
|
||||
write_zeroes::WriteZeroes,
|
||||
};
|
||||
|
||||
use std::cmp::min;
|
||||
use std::cmp::{max, min};
|
||||
use std::fmt::{self, Display};
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
@@ -31,18 +34,21 @@ pub enum Error {
|
||||
BackingFilesNotSupported,
|
||||
CompressedBlocksNotSupported,
|
||||
EvictingCache(io::Error),
|
||||
FileTooBig(u64),
|
||||
GettingFileSize(io::Error),
|
||||
GettingRefcount(refcount::Error),
|
||||
InvalidClusterIndex,
|
||||
InvalidClusterSize,
|
||||
InvalidIndex,
|
||||
InvalidL1TableOffset,
|
||||
InvalidL1TableSize(u32),
|
||||
InvalidMagic,
|
||||
InvalidOffset(u64),
|
||||
InvalidRefcountTableOffset,
|
||||
InvalidRefcountTableSize,
|
||||
InvalidRefcountTableSize(u64),
|
||||
NoFreeClusters,
|
||||
NoRefcountClusters,
|
||||
NotEnoughSpaceForRefcounts,
|
||||
OpeningFile(io::Error),
|
||||
ReadingData(io::Error),
|
||||
ReadingHeader(io::Error),
|
||||
@@ -50,10 +56,14 @@ pub enum Error {
|
||||
ReadingRefCountBlock(refcount::Error),
|
||||
ReadingRefCounts(io::Error),
|
||||
RebuildingRefCounts(io::Error),
|
||||
RefcountTableOffEnd,
|
||||
RefcountTableTooLarge,
|
||||
SeekingFile(io::Error),
|
||||
SettingFileSize(io::Error),
|
||||
SettingRefcountRefcount(io::Error),
|
||||
SizeTooSmallForNumberOfClusters,
|
||||
TooManyL1Entries(u64),
|
||||
TooManyRefcounts(u64),
|
||||
UnsupportedRefcountOrder,
|
||||
UnsupportedVersion(u32),
|
||||
WritingData(io::Error),
|
||||
@@ -72,18 +82,25 @@ impl Display for Error {
|
||||
BackingFilesNotSupported => write!(f, "backing files not supported"),
|
||||
CompressedBlocksNotSupported => write!(f, "compressed blocks not supported"),
|
||||
EvictingCache(e) => write!(f, "failed to evict cache: {}", e),
|
||||
FileTooBig(size) => write!(
|
||||
f,
|
||||
"file larger than max of {}: {}",
|
||||
MAX_QCOW_FILE_SIZE, size
|
||||
),
|
||||
GettingFileSize(e) => write!(f, "failed to get file size: {}", e),
|
||||
GettingRefcount(e) => write!(f, "failed to get refcount: {}", e),
|
||||
InvalidClusterIndex => write!(f, "invalid cluster index"),
|
||||
InvalidClusterSize => write!(f, "invalid cluster size"),
|
||||
InvalidIndex => write!(f, "invalid index"),
|
||||
InvalidL1TableOffset => write!(f, "invalid L1 table offset"),
|
||||
InvalidL1TableSize(size) => write!(f, "invalid L1 table size {}", size),
|
||||
InvalidMagic => write!(f, "invalid magic"),
|
||||
InvalidOffset(_) => write!(f, "invalid offset"),
|
||||
InvalidRefcountTableOffset => write!(f, "invalid refcount table offset"),
|
||||
InvalidRefcountTableSize => write!(f, "invalid refcount table size"),
|
||||
InvalidRefcountTableSize(size) => write!(f, "invalid refcount table size: {}", size),
|
||||
NoFreeClusters => write!(f, "no free clusters"),
|
||||
NoRefcountClusters => write!(f, "no refcount clusters"),
|
||||
NotEnoughSpaceForRefcounts => write!(f, "not enough space for refcounts"),
|
||||
OpeningFile(e) => write!(f, "failed to open file: {}", e),
|
||||
ReadingData(e) => write!(f, "failed to read data: {}", e),
|
||||
ReadingHeader(e) => write!(f, "failed to read header: {}", e),
|
||||
@@ -91,10 +108,14 @@ impl Display for Error {
|
||||
ReadingRefCountBlock(e) => write!(f, "failed to read ref count block: {}", e),
|
||||
ReadingRefCounts(e) => write!(f, "failed to read ref counts: {}", e),
|
||||
RebuildingRefCounts(e) => write!(f, "failed to rebuild ref counts: {}", e),
|
||||
RefcountTableOffEnd => write!(f, "refcount table offset past file end"),
|
||||
RefcountTableTooLarge => write!(f, "too many clusters specified for refcount table"),
|
||||
SeekingFile(e) => write!(f, "failed to seek file: {}", e),
|
||||
SettingFileSize(e) => write!(f, "failed to set file size: {}", e),
|
||||
SettingRefcountRefcount(e) => write!(f, "failed to set refcount refcount: {}", e),
|
||||
SizeTooSmallForNumberOfClusters => write!(f, "size too small for number of clusters"),
|
||||
TooManyL1Entries(count) => write!(f, "l1 entry table too large: {}", count),
|
||||
TooManyRefcounts(count) => write!(f, "ref count table too large: {}", count),
|
||||
UnsupportedRefcountOrder => write!(f, "unsupported refcount order"),
|
||||
UnsupportedVersion(v) => write!(f, "unsupported version: {}", v),
|
||||
WritingData(e) => write!(f, "failed to write data: {}", e),
|
||||
@@ -108,11 +129,21 @@ pub enum ImageType {
|
||||
Qcow2,
|
||||
}
|
||||
|
||||
// Maximum data size supported.
|
||||
const MAX_QCOW_FILE_SIZE: u64 = 0x01 << 44; // 16 TB.
|
||||
|
||||
// QCOW magic constant that starts the header.
|
||||
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
||||
// Default to a cluster size of 2^DEFAULT_CLUSTER_BITS
|
||||
const DEFAULT_CLUSTER_BITS: u32 = 16;
|
||||
const MAX_CLUSTER_BITS: u32 = 30;
|
||||
// Limit clusters to reasonable sizes. Choose the same limits as qemu. Making the clusters smaller
|
||||
// increases the amount of overhead for book keeping.
|
||||
const MIN_CLUSTER_BITS: u32 = 9;
|
||||
const MAX_CLUSTER_BITS: u32 = 21;
|
||||
// The L1 and RefCount table are kept in RAM, only handle files that require less than 35M entries.
|
||||
// This easily covers 1 TB files. When support for bigger files is needed the assumptions made to
|
||||
// keep these tables in RAM needs to be thrown out.
|
||||
const MAX_RAM_POINTER_TABLE_SIZE: u64 = 35_000_000;
|
||||
// Only support 2 byte refcounts, 2^refcount_order bits.
|
||||
const DEFAULT_REFCOUNT_ORDER: u32 = 4;
|
||||
|
||||
@@ -319,11 +350,15 @@ impl QcowHeader {
|
||||
}
|
||||
}
|
||||
|
||||
fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u32) -> usize {
|
||||
let refcount_bytes = (0x01u32 << refcount_order) / 8;
|
||||
let for_data = div_round_up_u32(num_clusters * refcount_bytes, cluster_size);
|
||||
let for_refcounts = div_round_up_u32(for_data * refcount_bytes, cluster_size);
|
||||
for_data as usize + for_refcounts as usize
|
||||
fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u32) -> u64 {
|
||||
// Use u64 as the product of the u32 inputs can overflow.
|
||||
let refcount_bytes = (0x01 << u64::from(refcount_order)) / 8;
|
||||
let for_data = div_round_up_u64(
|
||||
u64::from(num_clusters) * refcount_bytes,
|
||||
u64::from(cluster_size),
|
||||
);
|
||||
let for_refcounts = div_round_up_u64(for_data * refcount_bytes, u64::from(cluster_size));
|
||||
for_data + for_refcounts
|
||||
}
|
||||
|
||||
/// Represents a qcow2 file. This is a sparse file format maintained by the qemu project.
|
||||
@@ -368,14 +403,20 @@ impl QcowFile {
|
||||
return Err(Error::UnsupportedVersion(header.version));
|
||||
}
|
||||
|
||||
// Make sure that the L1 table fits in RAM.
|
||||
if u64::from(header.l1_size) > MAX_RAM_POINTER_TABLE_SIZE {
|
||||
return Err(Error::InvalidL1TableSize(header.l1_size));
|
||||
}
|
||||
|
||||
let cluster_bits: u32 = header.cluster_bits;
|
||||
if cluster_bits > MAX_CLUSTER_BITS {
|
||||
if cluster_bits < MIN_CLUSTER_BITS || cluster_bits > MAX_CLUSTER_BITS {
|
||||
return Err(Error::InvalidClusterSize);
|
||||
}
|
||||
let cluster_size = 0x01u64 << cluster_bits;
|
||||
if cluster_size < size_of::<u64>() as u64 {
|
||||
// Can't fit an offset in a cluster, nothing is going to work.
|
||||
return Err(Error::InvalidClusterSize);
|
||||
|
||||
// Limit the total size of the disk.
|
||||
if header.size > MAX_QCOW_FILE_SIZE {
|
||||
return Err(Error::FileTooBig(header.size));
|
||||
}
|
||||
|
||||
// No current support for backing files.
|
||||
@@ -398,8 +439,13 @@ impl QcowFile {
|
||||
}
|
||||
offset_is_cluster_boundary(header.backing_file_offset, header.cluster_bits)?;
|
||||
offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)?;
|
||||
offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)?;
|
||||
offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)?;
|
||||
// refcount table must be a cluster boundary, and within the file's virtual or actual size.
|
||||
offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)?;
|
||||
let file_size = file.metadata().map_err(Error::GettingFileSize)?.len();
|
||||
if header.refcount_table_offset > max(file_size, header.size) {
|
||||
return Err(Error::RefcountTableOffEnd);
|
||||
}
|
||||
|
||||
// The first cluster should always have a non-zero refcount, so if it is 0,
|
||||
// this is an old file with broken refcounts, which requires a rebuild.
|
||||
@@ -432,6 +478,9 @@ impl QcowFile {
|
||||
let num_l2_clusters = div_round_up_u64(num_clusters, l2_size);
|
||||
let l1_clusters = div_round_up_u64(num_l2_clusters, cluster_size);
|
||||
let header_clusters = div_round_up_u64(size_of::<QcowHeader>() as u64, cluster_size);
|
||||
if num_l2_clusters > MAX_RAM_POINTER_TABLE_SIZE {
|
||||
return Err(Error::TooManyL1Entries(num_l2_clusters));
|
||||
}
|
||||
let l1_table = VecCache::from_vec(
|
||||
raw_file
|
||||
.read_pointer_table(
|
||||
@@ -447,7 +496,14 @@ impl QcowFile {
|
||||
header.refcount_order,
|
||||
cluster_size as u32,
|
||||
(num_clusters + l1_clusters + num_l2_clusters + header_clusters) as u32,
|
||||
) as u64;
|
||||
);
|
||||
// Check that the given header doesn't have a suspiciously sized refcount table.
|
||||
if u64::from(header.refcount_table_clusters) > 2 * refcount_clusters {
|
||||
return Err(Error::RefcountTableTooLarge);
|
||||
}
|
||||
if l1_clusters + refcount_clusters > MAX_RAM_POINTER_TABLE_SIZE {
|
||||
return Err(Error::TooManyRefcounts(refcount_clusters));
|
||||
}
|
||||
let refcount_block_entries = cluster_size / refcount_bytes;
|
||||
let refcounts = RefCount::new(
|
||||
&mut raw_file,
|
||||
@@ -716,7 +772,7 @@ impl QcowFile {
|
||||
while refcounts[first_free_cluster as usize] != 0 {
|
||||
first_free_cluster += 1;
|
||||
if first_free_cluster >= refcounts.len() as u64 {
|
||||
return Err(Error::InvalidRefcountTableSize);
|
||||
return Err(Error::NotEnoughSpaceForRefcounts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,13 +872,13 @@ impl QcowFile {
|
||||
max_valid_cluster_index += refblock_clusters + reftable_clusters;
|
||||
max_valid_cluster_index += refblocks_for_refs + reftable_clusters_for_refs;
|
||||
|
||||
if max_valid_cluster_index > usize::max_value() as u64 {
|
||||
return Err(Error::InvalidRefcountTableSize);
|
||||
if max_valid_cluster_index > MAX_RAM_POINTER_TABLE_SIZE {
|
||||
return Err(Error::InvalidRefcountTableSize(max_valid_cluster_index));
|
||||
}
|
||||
|
||||
let max_valid_cluster_offset = max_valid_cluster_index * cluster_size;
|
||||
if max_valid_cluster_offset < file_size - cluster_size {
|
||||
return Err(Error::InvalidRefcountTableSize);
|
||||
return Err(Error::InvalidRefcountTableSize(max_valid_cluster_offset));
|
||||
}
|
||||
|
||||
let mut refcounts = vec![0; max_valid_cluster_index as usize];
|
||||
@@ -1025,20 +1081,16 @@ impl QcowFile {
|
||||
fn get_new_cluster(&mut self) -> std::io::Result<u64> {
|
||||
// First use a pre allocated cluster if one is available.
|
||||
if let Some(free_cluster) = self.avail_clusters.pop() {
|
||||
let cluster_size = self.raw_file.cluster_size() as usize;
|
||||
self.raw_file
|
||||
.file_mut()
|
||||
.seek(SeekFrom::Start(free_cluster))?;
|
||||
self.raw_file.file_mut().write_zeroes(cluster_size)?;
|
||||
self.raw_file.zero_cluster(free_cluster)?;
|
||||
return Ok(free_cluster);
|
||||
}
|
||||
|
||||
let max_valid_cluster_offset = self.refcounts.max_valid_cluster_offset();
|
||||
if let Some(new_cluster) = self.raw_file.add_cluster_end(max_valid_cluster_offset)? {
|
||||
return Ok(new_cluster);
|
||||
Ok(new_cluster)
|
||||
} else {
|
||||
error!("No free clusters in get_new_cluster()");
|
||||
return Err(std::io::Error::from_raw_os_error(ENOSPC));
|
||||
Err(std::io::Error::from_raw_os_error(ENOSPC))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1514,12 +1566,12 @@ fn offset_is_cluster_boundary(offset: u64, cluster_bits: u32) -> Result<()> {
|
||||
|
||||
// Ceiling of the division of `dividend`/`divisor`.
|
||||
fn div_round_up_u64(dividend: u64, divisor: u64) -> u64 {
|
||||
(dividend + divisor - 1) / divisor
|
||||
dividend / divisor + if dividend % divisor != 0 { 1 } else { 0 }
|
||||
}
|
||||
|
||||
// Ceiling of the division of `dividend`/`divisor`.
|
||||
fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 {
|
||||
(dividend + divisor - 1) / divisor
|
||||
dividend / divisor + if dividend % divisor != 0 { 1 } else { 0 }
|
||||
}
|
||||
|
||||
fn convert_copy<R, W>(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()>
|
||||
@@ -1693,13 +1745,37 @@ mod tests {
|
||||
]
|
||||
}
|
||||
|
||||
// Test case found by clusterfuzz to allocate excessive memory.
|
||||
fn test_huge_header() -> Vec<u8> {
|
||||
vec![
|
||||
0x51, 0x46, 0x49, 0xfb, // magic
|
||||
0x00, 0x00, 0x00, 0x03, // version
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // backing file offset
|
||||
0x00, 0x00, 0x00, 0x00, // backing file size
|
||||
0x00, 0x00, 0x00, 0x09, // cluster_bits
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, // size
|
||||
0x00, 0x00, 0x00, 0x00, // crypt method
|
||||
0x00, 0x00, 0x01, 0x00, // L1 size
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // L1 table offset
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // refcount table offset
|
||||
0x00, 0x00, 0x00, 0x03, // refcount table clusters
|
||||
0x00, 0x00, 0x00, 0x00, // nb snapshots
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // snapshots offset
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // incompatible_features
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compatible_features
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // autoclear_features
|
||||
0x00, 0x00, 0x00, 0x04, // refcount_order
|
||||
0x00, 0x00, 0x00, 0x68, // header_length
|
||||
]
|
||||
}
|
||||
|
||||
fn with_basic_file<F>(header: &[u8], mut testfn: F)
|
||||
where
|
||||
F: FnMut(File),
|
||||
{
|
||||
let mut disk_file: File = tempfile().unwrap();
|
||||
disk_file.write_all(&header).unwrap();
|
||||
disk_file.set_len(0x5_0000).unwrap();
|
||||
disk_file.set_len(0x1_0000_0000).unwrap();
|
||||
disk_file.seek(SeekFrom::Start(0)).unwrap();
|
||||
|
||||
testfn(disk_file); // File closed when the function exits.
|
||||
@@ -1770,6 +1846,90 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_cluster_bits() {
|
||||
let mut header = valid_header_v3();
|
||||
header[23] = 3;
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_huge_file() {
|
||||
let header = test_huge_header();
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_crazy_file_size_rejected() {
|
||||
let mut header = valid_header_v3();
|
||||
&mut header[24..32].copy_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1e]);
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_huge_l1_table() {
|
||||
let mut header = valid_header_v3();
|
||||
header[36] = 0x12;
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_1_tb_file_min_cluster() {
|
||||
let mut header = test_huge_header();
|
||||
header[24] = 0;
|
||||
header[26] = 1;
|
||||
header[31] = 0;
|
||||
// 1 TB with the min cluster size makes the arrays too big, it should fail.
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_1_tb_file() {
|
||||
let mut header = test_huge_header();
|
||||
// reset to 1 TB size.
|
||||
header[24] = 0;
|
||||
header[26] = 1;
|
||||
header[31] = 0;
|
||||
// set cluster_bits
|
||||
header[23] = 16;
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
let mut qcow = QcowFile::from(disk_file).expect("Failed to create file.");
|
||||
qcow.seek(SeekFrom::Start(0x100_0000_0000 - 8))
|
||||
.expect("Failed to seek.");
|
||||
let value = 0x0000_0040_3f00_ffffu64;
|
||||
qcow.write_all(&value.to_le_bytes())
|
||||
.expect("failed to write data");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_huge_num_refcounts() {
|
||||
let mut header = valid_header_v3();
|
||||
&mut header[56..60].copy_from_slice(&[0x02, 0x00, 0xe8, 0xff]);
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount clusters");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_huge_refcount_offset() {
|
||||
let mut header = valid_header_v3();
|
||||
&mut header[48..56].copy_from_slice(&[0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00]);
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount offset");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_read_start() {
|
||||
with_basic_file(&valid_header_v3(), |disk_file: File| {
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::io::{self, BufWriter, Seek, SeekFrom};
|
||||
use std::mem::size_of;
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use vmm_sys_util::write_zeroes::WriteZeroes;
|
||||
|
||||
/// A qcow file. Allows reading/writing clusters and appending clusters.
|
||||
#[derive(Debug)]
|
||||
@@ -133,6 +134,14 @@ impl QcowRawFile {
|
||||
pub fn cluster_offset(&self, address: u64) -> u64 {
|
||||
address & self.cluster_mask
|
||||
}
|
||||
|
||||
/// Zeros out a cluster in the file.
|
||||
pub fn zero_cluster(&mut self, address: u64) -> io::Result<()> {
|
||||
let cluster_size = self.cluster_size as usize;
|
||||
self.file.seek(SeekFrom::Start(address))?;
|
||||
self.file.write_zeroes(cluster_size)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for QcowRawFile {
|
||||
|
||||
150
release-notes.md
150
release-notes.md
@@ -1,3 +1,153 @@
|
||||
- [v0.3.0](#v030)
|
||||
+ [Block device offloading](#block-device-offloading)
|
||||
+ [Network device backend](#network-device-backend)
|
||||
+ [Virtual sockets](#virtual-sockets)
|
||||
+ [HTTP based API](#http-based-api)
|
||||
+ [Memory mapped virtio transport](#memory-mapped-virtio-transport)
|
||||
+ [Paravirtualized IOMMU](#paravirtualized-iommu)
|
||||
+ [Ubuntu 19.10](#ubuntu-1910)
|
||||
+ [Guest large memory](#guest-large-memory)
|
||||
- [v0.2.0](#v020)
|
||||
+ [Network device offloading](#network-device-offloading)
|
||||
+ [Minimal hardware-reduced ACPI](#minimal-hardware-reduced-acpi)
|
||||
+ [Debug I/O port](#debug-i-o-port)
|
||||
+ [Improved direct device assignment](#improved-direct-device-assignment)
|
||||
+ [Improved shared filesystem](#improved-shared-filesystem)
|
||||
+ [Ubuntu bionic based CI](#ubuntu-bionic-based-ci)
|
||||
- [v0.1.0](#v010)
|
||||
+ [Shared filesystem](#shared-filesystem)
|
||||
+ [Initial direct device assignment support](#initial-direct-device-assignment-support)
|
||||
+ [Userspace IOAPIC](#userspace-ioapic)
|
||||
+ [Virtual persistent memory](#virtual-persistent-memory)
|
||||
+ [Linux kernel bzImage](#linux-kernel-bzimage)
|
||||
+ [Console over virtio](#console-over-virtio)
|
||||
+ [Unit testing](#unit-testing)
|
||||
+ [Integration tests parallelization](#integration-tests-parallelization)
|
||||
|
||||
# v0.3.0
|
||||
|
||||
This release has been tracked through the [0.3.0 project](https://github.com/intel/cloud-hypervisor/projects/3).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.3.0 include:
|
||||
|
||||
### Block device offloading
|
||||
|
||||
We continue to work on offloading paravirtualized I/O to external processes,
|
||||
and we added support for
|
||||
[vhost-user-blk](https://access.redhat.com/solutions/3394851) backends.
|
||||
This enables `cloud-hypervisor` users to plug a `vhost-user` based block device
|
||||
like [SPDK](https://spdk.io)) into the VMM as their paravirtualized storage
|
||||
backend.
|
||||
|
||||
### Network device backend
|
||||
|
||||
The previous release provided support for
|
||||
[vhost-user-net](https://access.redhat.com/solutions/3394851) backends. Now we
|
||||
also provide a TAP based vhost-user-net backend, implemented in Rust. Together
|
||||
with the vhost-user-net device implementation, this will eventually become the
|
||||
Cloud Hypervisor default paravirtualized networking architecture.
|
||||
|
||||
### Virtual sockets
|
||||
|
||||
In order to more efficiently and securely communicate between host and guest,
|
||||
we added an hybrid implementation of the
|
||||
[VSOCK](http://man7.org/linux/man-pages/man7/vsock.7.html) socket address
|
||||
family over virtio. Credits go to the
|
||||
[Firecracker](https://github.com/firecracker-microvm/firecracker/blob/master/docs/vsock.md)
|
||||
project as our implementation is a copy of theirs.
|
||||
|
||||
### HTTP based API
|
||||
|
||||
In anticipation of the need to support asynchronous operations to Cloud
|
||||
Hypervisor guests (e.g. resources hotplug and guest migration), we added a HTTP
|
||||
based API to the VMM. The API will be more extensively documented during the
|
||||
next release cycle.
|
||||
|
||||
### Memory mapped virtio transport
|
||||
|
||||
In order to support potential PCI-free use cases, we added support for the
|
||||
[virtio MMIO](https://docs.oasis-open.org/virtio/virtio/v1.1/cs01/virtio-v1.1-cs01.html#x1-1440002)
|
||||
transport layer. This will allow us to support simple, minimal guest
|
||||
configurations that do not require a PCI bus emulation.
|
||||
|
||||
### Paravirtualized IOMMU
|
||||
|
||||
As we want to improve our nested guests support, we added support for exposing
|
||||
a [paravirtualized IOMMU](https://github.com/intel/cloud-hypervisor/blob/master/docs/iommu.md)
|
||||
device through virtio. This allows for a safer nested virtio and directly
|
||||
assigned devices support.
|
||||
|
||||
To add the IOMMU support, we had to make some CLI changes for Cloud Hypervisor
|
||||
users to be able to specify if devices had to be handled through this virtual
|
||||
IOMMU or not. In particular, the `--disk` option now expects disk paths to be
|
||||
prefixed with a `path=` string, and supports an optional `iommu=[on|off]`
|
||||
setting.
|
||||
|
||||
### Ubuntu 19.10
|
||||
|
||||
With the latest [hypervisor firmware](https://github.com/intel/rust-hypervisor-firmware),
|
||||
we can now support the latest
|
||||
[Ubuntu 19.10 (Eoan Ermine)](http://releases.ubuntu.com/19.10/) cloud images.
|
||||
|
||||
### Large memory guests
|
||||
|
||||
After simplifying and changing our guest address space handling, we can now
|
||||
support guests with large amount of memory (more than 64GB).
|
||||
|
||||
# v0.2.0
|
||||
|
||||
This release has been tracked through the [0.2.0 project](https://github.com/intel/cloud-hypervisor/projects/2).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.2.0 include:
|
||||
|
||||
### Network device offloading
|
||||
|
||||
As part of our general effort to offload paravirtualized I/O to external
|
||||
processes, we added support for
|
||||
[vhost-user-net](https://access.redhat.com/solutions/3394851) backends. This
|
||||
enables `cloud-hypervisor` users to plug a `vhost-user` based networking device
|
||||
(e.g. [DPDK](https://dpdk.org)) into the VMM as their virtio network backend.
|
||||
|
||||
### Minimal hardware-reduced ACPI
|
||||
|
||||
In order to properly implement and guest reset and shutdown, we implemented
|
||||
a minimal version of the hardware-reduced ACPI specification. Together with
|
||||
a tiny I/O port based ACPI device, this allows `cloud-hypervisor` guests to
|
||||
cleanly reboot and shutdown.
|
||||
|
||||
The ACPI implementation is a `cloud-hypervisor` build time option that is
|
||||
enabled by default.
|
||||
|
||||
### Debug I/O port
|
||||
|
||||
Based on the Firecracker idea of using a dedicated I/O port to measure guest
|
||||
boot times, we added support for logging guest events through the
|
||||
[0x80](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
|
||||
PC debug port. This allows, among other things, for granular guest boot time
|
||||
measurements. See our [debug port documentation](https://github.com/intel/cloud-hypervisor/blob/master/docs/debug-port.md)
|
||||
for more details.
|
||||
|
||||
### Improved direct device assignment
|
||||
|
||||
We fixed a major performance issue with our initial VFIO implementation: When
|
||||
enabling VT-d through the KVM and VFIO APIs, our guest memory writes and reads
|
||||
were (in many cases) not cached. After correctly tagging the guest memory from
|
||||
`cloud-hypervisor` we're now able to reach the expected performance from
|
||||
directly assigned devices.
|
||||
|
||||
### Improved shared filesystem
|
||||
|
||||
We added shared memory region with [DAX](https://www.kernel.org/doc/Documentation/filesystems/dax.txt)
|
||||
support to our [virtio-fs](https://virtio-fs.gitlab.io/) shared file system.
|
||||
This provides better shared filesystem IO performance with a smaller guest
|
||||
memory footprint.
|
||||
|
||||
### Ubuntu bionic based CI
|
||||
|
||||
Thanks to our [simple KVM firmware](https://github.com/intel/rust-hypervisor-firmware)
|
||||
improvements, we are now able to boot Ubuntu bionic images. We added those to
|
||||
our CI pipeline.
|
||||
|
||||
# v0.1.0
|
||||
|
||||
This release has been tracked through the [0.1.0 project](https://github.com/intel/cloud-hypervisor/projects/1).
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
#
|
||||
# Automatically generated file; DO NOT EDIT.
|
||||
# Linux/x86 5.2.0-rc5 Kernel Configuration
|
||||
# Linux/x86 5.3.0-rc3 Kernel Configuration
|
||||
#
|
||||
|
||||
#
|
||||
# Compiler: gcc (Ubuntu 7.4.0-1ubuntu1~18.04) 7.4.0
|
||||
# Compiler: gcc (Ubuntu 8.3.0-6ubuntu1) 8.3.0
|
||||
#
|
||||
CONFIG_CC_IS_GCC=y
|
||||
CONFIG_GCC_VERSION=70400
|
||||
CONFIG_GCC_VERSION=80300
|
||||
CONFIG_CLANG_VERSION=0
|
||||
CONFIG_CC_CAN_LINK=y
|
||||
CONFIG_CC_HAS_ASM_GOTO=y
|
||||
CONFIG_CC_HAS_WARN_MAYBE_UNINITIALIZED=y
|
||||
CONFIG_IRQ_WORK=y
|
||||
@@ -20,6 +21,7 @@ CONFIG_THREAD_INFO_IN_TASK=y
|
||||
#
|
||||
CONFIG_INIT_ENV_ARG_LIMIT=32
|
||||
# CONFIG_COMPILE_TEST is not set
|
||||
# CONFIG_HEADER_TEST is not set
|
||||
CONFIG_LOCALVERSION=""
|
||||
# CONFIG_LOCALVERSION_AUTO is not set
|
||||
CONFIG_BUILD_SALT=""
|
||||
@@ -59,6 +61,7 @@ CONFIG_IRQ_DOMAIN=y
|
||||
CONFIG_IRQ_DOMAIN_HIERARCHY=y
|
||||
CONFIG_GENERIC_MSI_IRQ=y
|
||||
CONFIG_GENERIC_MSI_IRQ_DOMAIN=y
|
||||
CONFIG_IRQ_MSI_IOMMU=y
|
||||
CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR=y
|
||||
CONFIG_GENERIC_IRQ_RESERVATION_MODE=y
|
||||
CONFIG_IRQ_FORCED_THREADING=y
|
||||
@@ -128,6 +131,12 @@ CONFIG_LOG_BUF_SHIFT=21
|
||||
CONFIG_LOG_CPU_MAX_BUF_SHIFT=12
|
||||
CONFIG_PRINTK_SAFE_LOG_BUF_SHIFT=13
|
||||
CONFIG_HAVE_UNSTABLE_SCHED_CLOCK=y
|
||||
|
||||
#
|
||||
# Scheduler features
|
||||
#
|
||||
# end of Scheduler features
|
||||
|
||||
CONFIG_ARCH_SUPPORTS_NUMA_BALANCING=y
|
||||
CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH=y
|
||||
CONFIG_ARCH_SUPPORTS_INT128=y
|
||||
@@ -140,7 +149,6 @@ CONFIG_MEMCG_SWAP=y
|
||||
CONFIG_MEMCG_SWAP_ENABLED=y
|
||||
CONFIG_MEMCG_KMEM=y
|
||||
CONFIG_BLK_CGROUP=y
|
||||
# CONFIG_DEBUG_BLK_CGROUP is not set
|
||||
CONFIG_CGROUP_WRITEBACK=y
|
||||
CONFIG_CGROUP_SCHED=y
|
||||
CONFIG_FAIR_GROUP_SCHED=y
|
||||
@@ -255,7 +263,6 @@ CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK=y
|
||||
CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK=y
|
||||
CONFIG_ARCH_HIBERNATION_POSSIBLE=y
|
||||
CONFIG_ARCH_SUSPEND_POSSIBLE=y
|
||||
CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y
|
||||
CONFIG_ARCH_WANT_GENERAL_HUGETLB=y
|
||||
CONFIG_ZONE_DMA32=y
|
||||
CONFIG_AUDIT_ARCH=y
|
||||
@@ -294,6 +301,7 @@ CONFIG_KVM_DEBUG_FS=y
|
||||
CONFIG_PARAVIRT_TIME_ACCOUNTING=y
|
||||
CONFIG_PARAVIRT_CLOCK=y
|
||||
# CONFIG_JAILHOUSE_GUEST is not set
|
||||
# CONFIG_ACRN_GUEST is not set
|
||||
# CONFIG_MK8 is not set
|
||||
# CONFIG_MPSC is not set
|
||||
# CONFIG_MCORE2 is not set
|
||||
@@ -310,6 +318,7 @@ CONFIG_CPU_SUP_INTEL=y
|
||||
CONFIG_CPU_SUP_AMD=y
|
||||
CONFIG_CPU_SUP_HYGON=y
|
||||
CONFIG_CPU_SUP_CENTAUR=y
|
||||
CONFIG_CPU_SUP_ZHAOXIN=y
|
||||
CONFIG_HPET_TIMER=y
|
||||
CONFIG_DMI=y
|
||||
# CONFIG_GART_IOMMU is not set
|
||||
@@ -399,6 +408,7 @@ CONFIG_HOTPLUG_CPU=y
|
||||
# CONFIG_BOOTPARAM_HOTPLUG_CPU0 is not set
|
||||
# CONFIG_DEBUG_HOTPLUG_CPU0 is not set
|
||||
CONFIG_LEGACY_VSYSCALL_EMULATE=y
|
||||
# CONFIG_LEGACY_VSYSCALL_XONLY is not set
|
||||
# CONFIG_LEGACY_VSYSCALL_NONE is not set
|
||||
# CONFIG_CMDLINE_BOOL is not set
|
||||
CONFIG_MODIFY_LDT_SYSCALL=y
|
||||
@@ -476,6 +486,7 @@ CONFIG_HAVE_ACPI_APEI_NMI=y
|
||||
# CONFIG_DPTF_POWER is not set
|
||||
# CONFIG_PMIC_OPREGION is not set
|
||||
# CONFIG_ACPI_CONFIGFS is not set
|
||||
CONFIG_ACPI_IORT=y
|
||||
CONFIG_X86_PM_TIMER=y
|
||||
# CONFIG_SFI is not set
|
||||
|
||||
@@ -541,8 +552,6 @@ CONFIG_AMD_NB=y
|
||||
# CONFIG_X86_X32 is not set
|
||||
# end of Binary Emulations
|
||||
|
||||
CONFIG_HAVE_GENERIC_GUP=y
|
||||
|
||||
#
|
||||
# Firmware Drivers
|
||||
#
|
||||
@@ -551,7 +560,6 @@ CONFIG_FIRMWARE_MEMMAP=y
|
||||
CONFIG_DMIID=y
|
||||
CONFIG_DMI_SYSFS=y
|
||||
CONFIG_DMI_SCAN_MACHINE_NON_EFI_FALLBACK=y
|
||||
# CONFIG_ISCSI_IBFT_FIND is not set
|
||||
# CONFIG_FW_CFG_SYSFS is not set
|
||||
# CONFIG_GOOGLE_FIRMWARE is not set
|
||||
|
||||
@@ -578,7 +586,25 @@ CONFIG_EFI_EARLYCON=y
|
||||
# end of Firmware Drivers
|
||||
|
||||
CONFIG_HAVE_KVM=y
|
||||
# CONFIG_VIRTUALIZATION is not set
|
||||
CONFIG_HAVE_KVM_IRQCHIP=y
|
||||
CONFIG_HAVE_KVM_IRQFD=y
|
||||
CONFIG_HAVE_KVM_IRQ_ROUTING=y
|
||||
CONFIG_HAVE_KVM_EVENTFD=y
|
||||
CONFIG_KVM_MMIO=y
|
||||
CONFIG_KVM_ASYNC_PF=y
|
||||
CONFIG_HAVE_KVM_MSI=y
|
||||
CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT=y
|
||||
CONFIG_KVM_VFIO=y
|
||||
CONFIG_KVM_GENERIC_DIRTYLOG_READ_PROTECT=y
|
||||
CONFIG_HAVE_KVM_IRQ_BYPASS=y
|
||||
CONFIG_HAVE_KVM_NO_POLL=y
|
||||
CONFIG_VIRTUALIZATION=y
|
||||
CONFIG_KVM=y
|
||||
CONFIG_KVM_INTEL=y
|
||||
# CONFIG_KVM_AMD is not set
|
||||
# CONFIG_VHOST_NET is not set
|
||||
# CONFIG_VHOST_VSOCK is not set
|
||||
# CONFIG_VHOST_CROSS_ENDIAN_LEGACY is not set
|
||||
|
||||
#
|
||||
# General architecture-dependent options
|
||||
@@ -592,6 +618,7 @@ CONFIG_JUMP_LABEL=y
|
||||
# CONFIG_STATIC_KEYS_SELFTEST is not set
|
||||
CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y
|
||||
CONFIG_ARCH_USE_BUILTIN_BSWAP=y
|
||||
CONFIG_USER_RETURN_NOTIFIER=y
|
||||
CONFIG_HAVE_IOREMAP_PROT=y
|
||||
CONFIG_HAVE_KPROBES=y
|
||||
CONFIG_HAVE_KRETPROBES=y
|
||||
@@ -640,6 +667,7 @@ CONFIG_HAVE_MOVE_PMD=y
|
||||
CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE=y
|
||||
CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD=y
|
||||
CONFIG_HAVE_ARCH_HUGE_VMAP=y
|
||||
CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y
|
||||
CONFIG_HAVE_ARCH_SOFT_DIRTY=y
|
||||
CONFIG_HAVE_MOD_ARCH_SPECIFIC=y
|
||||
CONFIG_MODULES_USE_ELF_RELA=y
|
||||
@@ -726,6 +754,7 @@ CONFIG_BLK_PM=y
|
||||
# CONFIG_IOSCHED_BFQ is not set
|
||||
# end of IO Schedulers
|
||||
|
||||
CONFIG_PREEMPT_NOTIFIERS=y
|
||||
CONFIG_INLINE_SPIN_UNLOCK_IRQ=y
|
||||
CONFIG_INLINE_READ_UNLOCK=y
|
||||
CONFIG_INLINE_READ_UNLOCK_IRQ=y
|
||||
@@ -766,6 +795,7 @@ CONFIG_SPARSEMEM_EXTREME=y
|
||||
CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y
|
||||
CONFIG_SPARSEMEM_VMEMMAP=y
|
||||
CONFIG_HAVE_MEMBLOCK_NODE_MAP=y
|
||||
CONFIG_HAVE_FAST_GUP=y
|
||||
CONFIG_MEMORY_ISOLATION=y
|
||||
CONFIG_HAVE_BOOTMEM_INFO_NODE=y
|
||||
CONFIG_MEMORY_HOTPLUG=y
|
||||
@@ -781,6 +811,7 @@ CONFIG_CONTIG_ALLOC=y
|
||||
CONFIG_PHYS_ADDR_T_64BIT=y
|
||||
CONFIG_BOUNCE=y
|
||||
CONFIG_VIRT_TO_BUS=y
|
||||
CONFIG_MMU_NOTIFIER=y
|
||||
CONFIG_KSM=y
|
||||
CONFIG_DEFAULT_MMAP_MIN_ADDR=4096
|
||||
CONFIG_TRANSPARENT_HUGEPAGE=y
|
||||
@@ -802,10 +833,11 @@ CONFIG_ZSMALLOC_STAT=y
|
||||
CONFIG_GENERIC_EARLY_IOREMAP=y
|
||||
# CONFIG_DEFERRED_STRUCT_PAGE_INIT is not set
|
||||
# CONFIG_IDLE_PAGE_TRACKING is not set
|
||||
CONFIG_ARCH_HAS_ZONE_DEVICE=y
|
||||
# CONFIG_ZONE_DEVICE is not set
|
||||
CONFIG_ARCH_HAS_HMM_MIRROR=y
|
||||
CONFIG_ARCH_HAS_HMM_DEVICE=y
|
||||
CONFIG_ARCH_HAS_PTE_DEVMAP=y
|
||||
CONFIG_ZONE_DEVICE=y
|
||||
CONFIG_DEV_PAGEMAP_OPS=y
|
||||
# CONFIG_HMM_MIRROR is not set
|
||||
# CONFIG_DEVICE_PRIVATE is not set
|
||||
CONFIG_PERCPU_STATS=y
|
||||
# CONFIG_GUP_BENCHMARK is not set
|
||||
CONFIG_ARCH_HAS_PTE_SPECIAL=y
|
||||
@@ -965,6 +997,7 @@ CONFIG_PCI_LOCKLESS_CONFIG=y
|
||||
# CONFIG_PCI_IOV is not set
|
||||
# CONFIG_PCI_PRI is not set
|
||||
# CONFIG_PCI_PASID is not set
|
||||
# CONFIG_PCI_P2PDMA is not set
|
||||
CONFIG_PCI_LABEL=y
|
||||
# CONFIG_HOTPLUG_PCI is not set
|
||||
|
||||
@@ -1016,9 +1049,11 @@ CONFIG_PREVENT_FIRMWARE_BUILD=y
|
||||
# Firmware loader
|
||||
#
|
||||
CONFIG_FW_LOADER=y
|
||||
CONFIG_FW_LOADER_PAGED_BUF=y
|
||||
CONFIG_EXTRA_FIRMWARE=""
|
||||
CONFIG_FW_LOADER_USER_HELPER=y
|
||||
# CONFIG_FW_LOADER_USER_HELPER_FALLBACK is not set
|
||||
# CONFIG_FW_LOADER_COMPRESS is not set
|
||||
# end of Firmware loader
|
||||
|
||||
CONFIG_ALLOW_DEV_COREDUMP=y
|
||||
@@ -1093,6 +1128,7 @@ CONFIG_VIRTIO_BLK=y
|
||||
# CONFIG_HP_ILO is not set
|
||||
# CONFIG_SRAM is not set
|
||||
# CONFIG_PCI_ENDPOINT_TEST is not set
|
||||
# CONFIG_XILINX_SDFEC is not set
|
||||
# CONFIG_PVPANIC is not set
|
||||
# CONFIG_C2PORT is not set
|
||||
|
||||
@@ -1710,6 +1746,8 @@ CONFIG_DMA_ACPI=y
|
||||
CONFIG_DW_DMAC_CORE=y
|
||||
# CONFIG_DW_DMAC is not set
|
||||
# CONFIG_DW_DMAC_PCI is not set
|
||||
# CONFIG_DW_EDMA is not set
|
||||
# CONFIG_DW_EDMA_PCIE is not set
|
||||
CONFIG_HSU_DMA=y
|
||||
|
||||
#
|
||||
@@ -1735,6 +1773,17 @@ CONFIG_UIO_DMEM_GENIRQ=y
|
||||
# CONFIG_UIO_NETX is not set
|
||||
# CONFIG_UIO_PRUSS is not set
|
||||
# CONFIG_UIO_MF624 is not set
|
||||
CONFIG_VFIO_IOMMU_TYPE1=y
|
||||
CONFIG_VFIO_VIRQFD=y
|
||||
CONFIG_VFIO=y
|
||||
# CONFIG_VFIO_NOIOMMU is not set
|
||||
CONFIG_VFIO_PCI=y
|
||||
# CONFIG_VFIO_PCI_VGA is not set
|
||||
CONFIG_VFIO_PCI_MMAP=y
|
||||
CONFIG_VFIO_PCI_INTX=y
|
||||
# CONFIG_VFIO_PCI_IGD is not set
|
||||
# CONFIG_VFIO_MDEV is not set
|
||||
CONFIG_IRQ_BYPASS_MANAGER=y
|
||||
# CONFIG_VIRT_DRIVERS is not set
|
||||
CONFIG_VIRTIO=y
|
||||
CONFIG_VIRTIO_MENU=y
|
||||
@@ -1779,6 +1828,8 @@ CONFIG_CLKBLD_I8253=y
|
||||
CONFIG_MAILBOX=y
|
||||
CONFIG_PCC=y
|
||||
# CONFIG_ALTERA_MBOX is not set
|
||||
CONFIG_IOMMU_IOVA=y
|
||||
CONFIG_IOMMU_API=y
|
||||
CONFIG_IOMMU_SUPPORT=y
|
||||
|
||||
#
|
||||
@@ -1787,9 +1838,12 @@ CONFIG_IOMMU_SUPPORT=y
|
||||
# end of Generic IOMMU Pagetable Support
|
||||
|
||||
# CONFIG_IOMMU_DEBUGFS is not set
|
||||
# CONFIG_IOMMU_DEFAULT_PASSTHROUGH is not set
|
||||
CONFIG_IOMMU_DMA=y
|
||||
# CONFIG_AMD_IOMMU is not set
|
||||
# CONFIG_INTEL_IOMMU is not set
|
||||
# CONFIG_IRQ_REMAP is not set
|
||||
CONFIG_VIRTIO_IOMMU=y
|
||||
|
||||
#
|
||||
# Remoteproc drivers
|
||||
@@ -1867,12 +1921,10 @@ CONFIG_IOMMU_SUPPORT=y
|
||||
#
|
||||
# IRQ chip support
|
||||
#
|
||||
CONFIG_ARM_GIC_MAX_NR=1
|
||||
# end of IRQ chip support
|
||||
|
||||
# CONFIG_IPACK_BUS is not set
|
||||
# CONFIG_RESET_CONTROLLER is not set
|
||||
# CONFIG_FMC is not set
|
||||
|
||||
#
|
||||
# PHY Subsystem
|
||||
@@ -1906,6 +1958,9 @@ CONFIG_ND_BLK=y
|
||||
CONFIG_ND_CLAIM=y
|
||||
CONFIG_ND_BTT=y
|
||||
CONFIG_BTT=y
|
||||
CONFIG_ND_PFN=y
|
||||
CONFIG_NVDIMM_PFN=y
|
||||
CONFIG_NVDIMM_DAX=y
|
||||
CONFIG_DAX_DRIVER=y
|
||||
CONFIG_DAX=y
|
||||
# CONFIG_DEV_DAX is not set
|
||||
@@ -1950,7 +2005,8 @@ CONFIG_FS_MBCACHE=y
|
||||
# CONFIG_BTRFS_FS is not set
|
||||
# CONFIG_NILFS2_FS is not set
|
||||
# CONFIG_F2FS_FS is not set
|
||||
# CONFIG_FS_DAX is not set
|
||||
CONFIG_FS_DAX=y
|
||||
CONFIG_FS_DAX_PMD=y
|
||||
CONFIG_FS_POSIX_ACL=y
|
||||
CONFIG_EXPORTFS=y
|
||||
# CONFIG_EXPORTFS_BLOCK_OPS is not set
|
||||
@@ -2014,6 +2070,7 @@ CONFIG_PROC_KCORE=y
|
||||
CONFIG_PROC_SYSCTL=y
|
||||
CONFIG_PROC_PAGE_MONITOR=y
|
||||
CONFIG_PROC_CHILDREN=y
|
||||
CONFIG_PROC_PID_ARCH_STATUS=y
|
||||
CONFIG_KERNFS=y
|
||||
CONFIG_SYSFS=y
|
||||
CONFIG_TMPFS=y
|
||||
@@ -2088,6 +2145,7 @@ CONFIG_NLS_UTF8=y
|
||||
# Security options
|
||||
#
|
||||
CONFIG_KEYS=y
|
||||
# CONFIG_KEYS_REQUEST_CACHE is not set
|
||||
CONFIG_PERSISTENT_KEYRINGS=y
|
||||
# CONFIG_BIG_KEYS is not set
|
||||
# CONFIG_ENCRYPTED_KEYS is not set
|
||||
@@ -2111,6 +2169,8 @@ CONFIG_LSM="yama,loadpin,safesetid,integrity"
|
||||
# Memory initialization
|
||||
#
|
||||
CONFIG_INIT_STACK_NONE=y
|
||||
# CONFIG_INIT_ON_ALLOC_DEFAULT_ON is not set
|
||||
# CONFIG_INIT_ON_FREE_DEFAULT_ON is not set
|
||||
# end of Memory initialization
|
||||
# end of Kernel hardening options
|
||||
# end of Security options
|
||||
@@ -2144,7 +2204,6 @@ CONFIG_CRYPTO_GF128MUL=y
|
||||
CONFIG_CRYPTO_NULL=y
|
||||
CONFIG_CRYPTO_NULL2=y
|
||||
# CONFIG_CRYPTO_PCRYPT is not set
|
||||
CONFIG_CRYPTO_WORKQUEUE=y
|
||||
CONFIG_CRYPTO_CRYPTD=y
|
||||
# CONFIG_CRYPTO_AUTHENC is not set
|
||||
CONFIG_CRYPTO_SIMD=y
|
||||
@@ -2210,6 +2269,7 @@ CONFIG_CRYPTO_CRC32C=y
|
||||
CONFIG_CRYPTO_CRC32C_INTEL=y
|
||||
# CONFIG_CRYPTO_CRC32 is not set
|
||||
# CONFIG_CRYPTO_CRC32_PCLMUL is not set
|
||||
# CONFIG_CRYPTO_XXHASH is not set
|
||||
CONFIG_CRYPTO_CRCT10DIF=y
|
||||
# CONFIG_CRYPTO_CRCT10DIF_PCLMUL is not set
|
||||
CONFIG_CRYPTO_GHASH=y
|
||||
@@ -2243,6 +2303,7 @@ CONFIG_CRYPTO_AES=y
|
||||
CONFIG_CRYPTO_AES_X86_64=y
|
||||
CONFIG_CRYPTO_AES_NI_INTEL=y
|
||||
# CONFIG_CRYPTO_ANUBIS is not set
|
||||
CONFIG_CRYPTO_LIB_ARC4=y
|
||||
CONFIG_CRYPTO_ARC4=y
|
||||
# CONFIG_CRYPTO_BLOWFISH is not set
|
||||
# CONFIG_CRYPTO_BLOWFISH_X86_64 is not set
|
||||
@@ -2346,6 +2407,7 @@ CONFIG_ZLIB_DEFLATE=y
|
||||
CONFIG_LZO_COMPRESS=y
|
||||
CONFIG_LZO_DECOMPRESS=y
|
||||
# CONFIG_XZ_DEC is not set
|
||||
CONFIG_INTERVAL_TREE=y
|
||||
CONFIG_XARRAY_MULTI=y
|
||||
CONFIG_ASSOCIATIVE_ARRAY=y
|
||||
CONFIG_HAS_IOMEM=y
|
||||
@@ -2354,15 +2416,18 @@ CONFIG_HAS_DMA=y
|
||||
CONFIG_NEED_SG_DMA_LENGTH=y
|
||||
CONFIG_NEED_DMA_MAP_STATE=y
|
||||
CONFIG_ARCH_DMA_ADDR_T_64BIT=y
|
||||
CONFIG_ARCH_HAS_FORCE_DMA_UNENCRYPTED=y
|
||||
CONFIG_SWIOTLB=y
|
||||
# CONFIG_DMA_API_DEBUG is not set
|
||||
CONFIG_SGL_ALLOC=y
|
||||
CONFIG_CPU_RMAP=y
|
||||
CONFIG_DQL=y
|
||||
CONFIG_NLATTR=y
|
||||
# CONFIG_DDR is not set
|
||||
CONFIG_IRQ_POLL=y
|
||||
# CONFIG_DIMLIB is not set
|
||||
CONFIG_UCS2_STRING=y
|
||||
CONFIG_HAVE_GENERIC_VDSO=y
|
||||
CONFIG_GENERIC_GETTIMEOFDAY=y
|
||||
CONFIG_FONT_SUPPORT=y
|
||||
# CONFIG_FONTS is not set
|
||||
CONFIG_FONT_8x8=y
|
||||
@@ -2400,7 +2465,7 @@ CONFIG_STRIP_ASM_SYMS=y
|
||||
# CONFIG_READABLE_ASM is not set
|
||||
CONFIG_UNUSED_SYMBOLS=y
|
||||
CONFIG_DEBUG_FS=y
|
||||
# CONFIG_HEADERS_CHECK is not set
|
||||
# CONFIG_HEADERS_INSTALL is not set
|
||||
CONFIG_OPTIMIZE_INLINING=y
|
||||
CONFIG_DEBUG_SECTION_MISMATCH=y
|
||||
CONFIG_SECTION_MISMATCH_WARN_ONLY=y
|
||||
@@ -2526,6 +2591,7 @@ CONFIG_RUNTIME_TESTING_MENU=y
|
||||
# CONFIG_TEST_SORT is not set
|
||||
# CONFIG_BACKTRACE_SELF_TEST is not set
|
||||
# CONFIG_RBTREE_TEST is not set
|
||||
# CONFIG_REED_SOLOMON_TEST is not set
|
||||
# CONFIG_INTERVAL_TREE_TEST is not set
|
||||
# CONFIG_ATOMIC64_SELFTEST is not set
|
||||
# CONFIG_TEST_HEXDUMP is not set
|
||||
@@ -2547,6 +2613,7 @@ CONFIG_RUNTIME_TESTING_MENU=y
|
||||
# CONFIG_TEST_UDELAY is not set
|
||||
# CONFIG_TEST_MEMCAT_P is not set
|
||||
# CONFIG_TEST_STACKINIT is not set
|
||||
# CONFIG_TEST_MEMINIT is not set
|
||||
# CONFIG_MEMTEST is not set
|
||||
CONFIG_BUG_ON_DATA_CORRUPTION=y
|
||||
# CONFIG_SAMPLES is not set
|
||||
@@ -2569,15 +2636,10 @@ CONFIG_EARLY_PRINTK=y
|
||||
CONFIG_DOUBLEFAULT=y
|
||||
# CONFIG_DEBUG_TLBFLUSH is not set
|
||||
CONFIG_HAVE_MMIOTRACE_SUPPORT=y
|
||||
CONFIG_IO_DELAY_TYPE_0X80=0
|
||||
CONFIG_IO_DELAY_TYPE_0XED=1
|
||||
CONFIG_IO_DELAY_TYPE_UDELAY=2
|
||||
CONFIG_IO_DELAY_TYPE_NONE=3
|
||||
CONFIG_IO_DELAY_0X80=y
|
||||
# CONFIG_IO_DELAY_0XED is not set
|
||||
# CONFIG_IO_DELAY_UDELAY is not set
|
||||
# CONFIG_IO_DELAY_NONE is not set
|
||||
CONFIG_DEFAULT_IO_DELAY_TYPE=0
|
||||
# CONFIG_DEBUG_BOOT_PARAMS is not set
|
||||
# CONFIG_CPA_DEBUG is not set
|
||||
# CONFIG_DEBUG_ENTRY is not set
|
||||
13
scripts/create-cloud-init.sh
Executable file
13
scripts/create-cloud-init.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
set -x
|
||||
|
||||
rm /tmp/clear-cloudinit.img
|
||||
mkdosfs -n config-2 -C /tmp/clear-cloudinit.img 8192
|
||||
mcopy -oi /tmp/clear-cloudinit.img -s test_data/cloud-init/clear/openstack ::
|
||||
|
||||
rm /tmp/ubuntu-cloudinit.img
|
||||
mkdosfs -n cidata -C /tmp/ubuntu-cloudinit.img 8192
|
||||
mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/user-data ::
|
||||
mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/meta-data ::
|
||||
mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/network-config ::
|
||||
|
||||
@@ -14,29 +14,55 @@ if [ ! -f "$FW" ]; then
|
||||
popd
|
||||
fi
|
||||
|
||||
OVMF_URL="https://cdn.download.clearlinux.org/image/OVMF.fd"
|
||||
OVMF="$WORKLOADS_DIR/OVMF.fd"
|
||||
if [ ! -f "$OVMF" ]; then
|
||||
CLEAR_OS_IMAGE_NAME="clear-cloudguest.img"
|
||||
CLEAR_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$CLEAR_OS_IMAGE_NAME"
|
||||
CLEAR_OS_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_IMAGE_NAME"
|
||||
if [ ! -f "$CLEAR_OS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
wget --quiet $OVMF_URL
|
||||
wget --quiet $CLEAR_OS_IMAGE_URL
|
||||
popd
|
||||
fi
|
||||
|
||||
OS_IMAGE_NAME="clear-29810-cloud.img"
|
||||
OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$OS_IMAGE_NAME.xz"
|
||||
OS_IMAGE="$WORKLOADS_DIR/$OS_IMAGE_NAME"
|
||||
if [ ! -f "$OS_IMAGE" ]; then
|
||||
CLEAR_OS_RAW_IMAGE_NAME="clear-cloudguest-raw.img"
|
||||
CLEAR_OS_RAW_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$CLEAR_OS_RAW_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
wget --quiet $OS_IMAGE_URL
|
||||
unxz $OS_IMAGE_NAME.xz
|
||||
qemu-img convert -p -f qcow2 -O raw $CLEAR_OS_IMAGE_NAME $CLEAR_OS_RAW_IMAGE_NAME
|
||||
popd
|
||||
fi
|
||||
|
||||
OS_RAW_IMAGE_NAME="clear-29810-cloud-raw.img"
|
||||
OS_RAW_IMAGE="$WORKLOADS_DIR/$OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$OS_RAW_IMAGE" ]; then
|
||||
BIONIC_OS_IMAGE_NAME="bionic-server-cloudimg-amd64.img"
|
||||
BIONIC_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$BIONIC_OS_IMAGE_NAME"
|
||||
BIONIC_OS_IMAGE="$WORKLOADS_DIR/$BIONIC_OS_IMAGE_NAME"
|
||||
if [ ! -f "$BIONIC_OS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
qemu-img convert -p -f qcow2 -O raw $OS_IMAGE_NAME $OS_RAW_IMAGE_NAME
|
||||
wget --quiet $BIONIC_OS_IMAGE_URL
|
||||
popd
|
||||
fi
|
||||
|
||||
BIONIC_OS_RAW_IMAGE_NAME="bionic-server-cloudimg-amd64-raw.img"
|
||||
BIONIC_OS_RAW_IMAGE="$WORKLOADS_DIR/$BIONIC_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$BIONIC_OS_RAW_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
qemu-img convert -p -f qcow2 -O raw $BIONIC_OS_IMAGE_NAME $BIONIC_OS_RAW_IMAGE_NAME
|
||||
popd
|
||||
fi
|
||||
|
||||
|
||||
EOAN_OS_IMAGE_NAME="eoan-server-cloudimg-amd64.img"
|
||||
EOAN_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$EOAN_OS_IMAGE_NAME"
|
||||
EOAN_OS_IMAGE="$WORKLOADS_DIR/$EOAN_OS_IMAGE_NAME"
|
||||
if [ ! -f "$EOAN_OS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
wget --quiet $EOAN_OS_IMAGE_URL
|
||||
popd
|
||||
fi
|
||||
|
||||
EOAN_OS_RAW_IMAGE_NAME="eoan-server-cloudimg-amd64-raw.img"
|
||||
EOAN_OS_RAW_IMAGE="$WORKLOADS_DIR/$EOAN_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$EOAN_OS_RAW_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
qemu-img convert -p -f qcow2 -O raw $EOAN_OS_IMAGE_NAME $EOAN_OS_RAW_IMAGE_NAME
|
||||
popd
|
||||
fi
|
||||
|
||||
@@ -50,25 +76,46 @@ LINUX_CUSTOM_DIR="linux-custom"
|
||||
if [ ! -f "$VMLINUX_IMAGE" ]; then
|
||||
SRCDIR=$PWD
|
||||
pushd $WORKLOADS_DIR
|
||||
git clone --depth 1 "https://github.com/sboeuf/linux.git" -b "virtio-pmem_and_virtio-fs" $LINUX_CUSTOM_DIR
|
||||
git clone --depth 1 "https://github.com/sboeuf/linux.git" -b "virtio-fs-virtio-iommu" $LINUX_CUSTOM_DIR
|
||||
pushd $LINUX_CUSTOM_DIR
|
||||
cp $SRCDIR/resources/linux-virtio-pmem-and-virtio-fs-config .config
|
||||
cp $SRCDIR/resources/linux-virtio-fs-virtio-iommu-config .config
|
||||
make bzImage -j `nproc`
|
||||
cp vmlinux $VMLINUX_IMAGE
|
||||
cp arch/x86/boot/bzImage $BZIMAGE_IMAGE
|
||||
popd
|
||||
rm -r $LINUX_CUSTOM_DIR
|
||||
rm -rf $LINUX_CUSTOM_DIR
|
||||
popd
|
||||
fi
|
||||
|
||||
VIRTIOFSD_URL="$(curl --silent https://api.github.com/repos/intel/nemu/releases/latest | grep "browser_download_url" | grep "virtiofsd-x86_64" | grep -o 'https://.*[^ "]')"
|
||||
VIRTIOFSD="$WORKLOADS_DIR/virtiofsd"
|
||||
if [ ! -f "$VIRTIOFSD" ]; then
|
||||
VUBD="$WORKLOADS_DIR/vubd"
|
||||
QEMU_DIR="qemu_build"
|
||||
if [ ! -f "$VIRTIOFSD" ] || [ ! -f "$VUBD" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
wget --quiet $VIRTIOFSD_URL -O "virtiofsd"
|
||||
chmod +x "virtiofsd"
|
||||
sudo setcap cap_sys_admin+epi "virtiofsd"
|
||||
git clone --depth 1 "https://github.com/sboeuf/qemu.git" -b "virtio-fs" $QEMU_DIR
|
||||
pushd $QEMU_DIR
|
||||
./configure --prefix=$PWD --target-list=x86_64-softmmu
|
||||
make virtiofsd vhost-user-blk -j `nproc`
|
||||
cp virtiofsd $VIRTIOFSD
|
||||
cp vhost-user-blk $VUBD
|
||||
popd
|
||||
rm -rf $QEMU_DIR
|
||||
sudo setcap cap_dac_override,cap_sys_admin+epi "virtiofsd"
|
||||
popd
|
||||
fi
|
||||
|
||||
BLK_IMAGE="$WORKLOADS_DIR/blk.img"
|
||||
MNT_DIR="mount_image"
|
||||
if [ ! -f "$BLK_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
fallocate -l 16M $BLK_IMAGE
|
||||
mkfs.ext4 -j $BLK_IMAGE
|
||||
mkdir $MNT_DIR
|
||||
sudo mount -t ext4 $BLK_IMAGE $MNT_DIR
|
||||
sudo bash -c "echo bar > $MNT_DIR/foo"
|
||||
sudo umount $BLK_IMAGE
|
||||
rm -r $MNT_DIR
|
||||
popd
|
||||
fi
|
||||
|
||||
SHARED_DIR="$WORKLOADS_DIR/shared_dir"
|
||||
@@ -81,16 +128,16 @@ fi
|
||||
VFIO_DIR="$WORKLOADS_DIR/vfio"
|
||||
if [ ! -d "$VFIO_DIR" ]; then
|
||||
mkdir -p $VFIO_DIR
|
||||
cp $OS_IMAGE $VFIO_DIR
|
||||
cp $CLEAR_OS_IMAGE $VFIO_DIR
|
||||
cp $FW $VFIO_DIR
|
||||
cp $VMLINUX_IMAGE $VFIO_DIR
|
||||
fi
|
||||
|
||||
# VFIO test network setup.
|
||||
# We reserve a different IP class for it: 172.16.0.0/24.
|
||||
# We reserve a different IP class for it: 172.17.0.0/24.
|
||||
sudo ip link add name vfio-br0 type bridge
|
||||
sudo ip link set vfio-br0 up
|
||||
sudo ip addr add 172.16.0.1/24 dev vfio-br0
|
||||
sudo ip addr add 172.17.0.1/24 dev vfio-br0
|
||||
|
||||
sudo ip tuntap add vfio-tap0 mode tap
|
||||
sudo ip link set vfio-tap0 master vfio-br0
|
||||
@@ -102,18 +149,34 @@ sudo ip link set vfio-tap1 up
|
||||
|
||||
cargo build
|
||||
sudo setcap cap_net_admin+ep target/debug/cloud-hypervisor
|
||||
sudo setcap cap_net_admin+ep target/debug/vhost_user_net
|
||||
|
||||
# We always copy a fresh version of our binary for our L2 guest.
|
||||
cp target/debug/cloud-hypervisor $VFIO_DIR
|
||||
# We need qemu to have NET_ADMIN as well.
|
||||
sudo setcap cap_net_admin+ep /usr/bin/qemu-system-x86_64
|
||||
|
||||
sudo adduser $USER kvm
|
||||
newgrp kvm << EOF
|
||||
cargo test --features "integration_tests"
|
||||
export RUST_BACKTRACE=1
|
||||
cargo test --features "integration_tests" -- --nocapture
|
||||
EOF
|
||||
RES=$?
|
||||
|
||||
if [ $RES -eq 0 ]; then
|
||||
# virtio-mmio based testing
|
||||
cargo build --no-default-features --features "mmio"
|
||||
sudo setcap cap_net_admin+ep target/debug/cloud-hypervisor
|
||||
|
||||
newgrp kvm << EOF
|
||||
export RUST_BACKTRACE=1
|
||||
cargo test --features "integration_tests,mmio" -- --nocapture
|
||||
EOF
|
||||
|
||||
RES=$?
|
||||
fi
|
||||
|
||||
# Tear VFIO test network down
|
||||
sudo ip link del vfio-br0
|
||||
sudo ip link del vfio-tap0
|
||||
sudo ip link del vfio-tap1
|
||||
|
||||
exit $RES
|
||||
|
||||
@@ -13,8 +13,12 @@ pushd target/debug
|
||||
ls | grep net_util | grep -v "\.d" | xargs -n 1 sudo setcap cap_net_admin,cap_net_raw+ep
|
||||
popd
|
||||
|
||||
for f in $(find . -name Cargo.toml -printf '%h\n' | sort -u); do
|
||||
pushd $f > /dev/null;
|
||||
sudo adduser $USER kvm
|
||||
newgrp kvm << EOF || exit 1
|
||||
for f in \$(find . -name Cargo.toml -printf '%h\n' | sort -u); do
|
||||
pushd \$f > /dev/null;
|
||||
export RUST_BACKTRACE=1
|
||||
cargo test || exit 1;
|
||||
popd > /dev/null;
|
||||
done
|
||||
EOF
|
||||
|
||||
597
src/bin/vhost_user_net.rs
Normal file
597
src/bin/vhost_user_net.rs
Normal file
@@ -0,0 +1,597 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause)
|
||||
|
||||
#[macro_use(crate_version, crate_authors)]
|
||||
extern crate clap;
|
||||
extern crate log;
|
||||
extern crate net_util;
|
||||
extern crate vhost_rs;
|
||||
extern crate vhost_user_backend;
|
||||
extern crate vm_virtio;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use epoll;
|
||||
use libc::{self, EAGAIN, EFD_NONBLOCK};
|
||||
use log::*;
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
use std::io::Read;
|
||||
use std::io::{self, Write};
|
||||
use std::mem;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::process;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::vec::Vec;
|
||||
|
||||
use vhost_rs::vhost_user::message::*;
|
||||
use vhost_rs::vhost_user::Error as VhostUserError;
|
||||
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
|
||||
|
||||
use net_gen;
|
||||
|
||||
use net_util::{Tap, TapError};
|
||||
use virtio_bindings::bindings::virtio_net::*;
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// The maximum buffer size when segmentation offload is enabled. This
|
||||
/// includes the 12-byte virtio net header.
|
||||
/// http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html#x1-1740003
|
||||
const MAX_BUFFER_SIZE: usize = 65562;
|
||||
const QUEUE_SIZE: usize = 1024;
|
||||
const NUM_QUEUES: usize = 2;
|
||||
|
||||
// The guest has made a buffer available to receive a frame into.
|
||||
const RX_QUEUE_EVENT: u16 = 0;
|
||||
// The transmit queue has a frame that is ready to send from the guest.
|
||||
const TX_QUEUE_EVENT: u16 = 1;
|
||||
// A frame is available for reading from the tap device to receive in the guest.
|
||||
const RX_TAP_EVENT: u16 = 2;
|
||||
// The device has been dropped.
|
||||
const KILL_EVENT: u16 = 3;
|
||||
|
||||
pub type VhostUserResult<T> = std::result::Result<T, VhostUserError>;
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to activate device.
|
||||
BadActivate,
|
||||
/// Failed to create kill eventfd
|
||||
CreateKillEventFd,
|
||||
/// Failed to add event.
|
||||
EpollCtl(io::Error),
|
||||
/// Fail to wait event.
|
||||
EpollWait(io::Error),
|
||||
/// Failed to create EventFd.
|
||||
EpollCreateFd,
|
||||
/// Failed to read Tap.
|
||||
FailedReadTap,
|
||||
/// Failed to signal used queue.
|
||||
FailedSignalingUsedQueue,
|
||||
/// Failed to handle event other than input event.
|
||||
HandleEventNotEpollIn,
|
||||
/// Failed to handle unknown event.
|
||||
HandleEventUnknownEvent,
|
||||
/// Invalid vring address.
|
||||
InvalidVringAddr,
|
||||
/// No vring call fd to notify.
|
||||
NoVringCallFdNotify,
|
||||
/// No memory configured.
|
||||
NoMemoryConfigured,
|
||||
/// Failed to parse sock parameter.
|
||||
ParseSockParam,
|
||||
/// Failed to parse ip parameter.
|
||||
ParseIpParam,
|
||||
/// Failed to parse mask parameter.
|
||||
ParseMaskParam,
|
||||
/// Open tap device failed.
|
||||
TapOpen(TapError),
|
||||
/// Setting tap IP failed.
|
||||
TapSetIp(TapError),
|
||||
/// Setting tap netmask failed.
|
||||
TapSetNetmask(TapError),
|
||||
/// Setting tap interface offload flags failed.
|
||||
TapSetOffload(TapError),
|
||||
/// Setting vnet header size failed.
|
||||
TapSetVnetHdrSize(TapError),
|
||||
/// Enabling tap interface failed.
|
||||
TapEnable(TapError),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "vhost_user_net_error: {:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl std::convert::From<Error> for std::io::Error {
|
||||
fn from(e: Error) -> Self {
|
||||
std::io::Error::new(io::ErrorKind::Other, e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TxVirtio {
|
||||
iovec: Vec<(GuestAddress, usize)>,
|
||||
frame_buf: [u8; MAX_BUFFER_SIZE],
|
||||
}
|
||||
|
||||
impl TxVirtio {
|
||||
fn new() -> Self {
|
||||
TxVirtio {
|
||||
iovec: Vec::new(),
|
||||
frame_buf: [0u8; MAX_BUFFER_SIZE],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RxVirtio {
|
||||
deferred_frame: bool,
|
||||
deferred_irqs: bool,
|
||||
bytes_read: usize,
|
||||
frame_buf: [u8; MAX_BUFFER_SIZE],
|
||||
}
|
||||
|
||||
impl RxVirtio {
|
||||
fn new() -> Self {
|
||||
RxVirtio {
|
||||
deferred_frame: false,
|
||||
deferred_irqs: false,
|
||||
bytes_read: 0,
|
||||
frame_buf: [0u8; MAX_BUFFER_SIZE],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn vnet_hdr_len() -> usize {
|
||||
mem::size_of::<virtio_net_hdr_v1>()
|
||||
}
|
||||
|
||||
struct VhostUserNetBackend {
|
||||
mem: Option<GuestMemoryMmap>,
|
||||
vring_worker: Option<Arc<VringWorker>>,
|
||||
kill_evt: EventFd,
|
||||
tap: Tap,
|
||||
rx: RxVirtio,
|
||||
tx: TxVirtio,
|
||||
rx_tap_listening: bool,
|
||||
}
|
||||
|
||||
impl std::clone::Clone for VhostUserNetBackend {
|
||||
fn clone(&self) -> Self {
|
||||
VhostUserNetBackend {
|
||||
mem: self.mem.clone(),
|
||||
vring_worker: self.vring_worker.clone(),
|
||||
kill_evt: self.kill_evt.try_clone().unwrap(),
|
||||
tap: self.tap.clone(),
|
||||
rx: self.rx.clone(),
|
||||
tx: self.tx.clone(),
|
||||
rx_tap_listening: self.rx_tap_listening,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserNetBackend {
|
||||
/// Create a new virtio network device with the given TAP interface.
|
||||
pub fn new_with_tap(tap: Tap) -> Result<Self> {
|
||||
// Set offload flags to match the virtio features below.
|
||||
tap.set_offload(
|
||||
net_gen::TUN_F_CSUM | net_gen::TUN_F_UFO | net_gen::TUN_F_TSO4 | net_gen::TUN_F_TSO6,
|
||||
)
|
||||
.map_err(Error::TapSetOffload)?;
|
||||
|
||||
let vnet_hdr_size = vnet_hdr_len() as i32;
|
||||
tap.set_vnet_hdr_size(vnet_hdr_size)
|
||||
.map_err(Error::TapSetVnetHdrSize)?;
|
||||
|
||||
let rx = RxVirtio::new();
|
||||
let tx = TxVirtio::new();
|
||||
|
||||
Ok(VhostUserNetBackend {
|
||||
mem: None,
|
||||
vring_worker: None,
|
||||
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(|_| Error::CreateKillEventFd)?,
|
||||
tap,
|
||||
rx,
|
||||
tx,
|
||||
rx_tap_listening: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new virtio network device with the given IP address and
|
||||
/// netmask.
|
||||
pub fn new(ip_addr: Ipv4Addr, netmask: Ipv4Addr) -> Result<Self> {
|
||||
let tap = Tap::new().map_err(Error::TapOpen)?;
|
||||
tap.set_ip_addr(ip_addr).map_err(Error::TapSetIp)?;
|
||||
tap.set_netmask(netmask).map_err(Error::TapSetNetmask)?;
|
||||
tap.enable().map_err(Error::TapEnable)?;
|
||||
|
||||
Self::new_with_tap(tap)
|
||||
}
|
||||
|
||||
// Copies a single frame from `self.rx.frame_buf` into the guest. Returns true
|
||||
// if a buffer was used, and false if the frame must be deferred until a buffer
|
||||
// is made available by the driver.
|
||||
fn rx_single_frame(&mut self, vring: &mut Vring) -> Result<bool> {
|
||||
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
|
||||
|
||||
let mut next_desc = vring.mut_queue().iter(&mem).next();
|
||||
|
||||
if next_desc.is_none() {
|
||||
// Queue has no available descriptors
|
||||
if self.rx_tap_listening {
|
||||
self.vring_worker
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.unregister_listener(
|
||||
self.tap.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(RX_TAP_EVENT),
|
||||
)
|
||||
.unwrap();
|
||||
self.rx_tap_listening = false;
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// We just checked that the head descriptor exists.
|
||||
let head_index = next_desc.as_ref().unwrap().index;
|
||||
let mut write_count = 0;
|
||||
|
||||
// Copy from frame into buffer, which may span multiple descriptors.
|
||||
loop {
|
||||
match next_desc {
|
||||
Some(desc) => {
|
||||
if !desc.is_write_only() {
|
||||
break;
|
||||
}
|
||||
let limit = cmp::min(write_count + desc.len as usize, self.rx.bytes_read);
|
||||
let source_slice = &self.rx.frame_buf[write_count..limit];
|
||||
let write_result = mem.write_slice(source_slice, desc.addr);
|
||||
|
||||
match write_result {
|
||||
Ok(_) => {
|
||||
write_count = limit;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to write slice: {:?}", e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if write_count >= self.rx.bytes_read {
|
||||
break;
|
||||
}
|
||||
next_desc = desc.next_descriptor();
|
||||
}
|
||||
None => {
|
||||
warn!("Receiving buffer is too small to hold frame of current size");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vring
|
||||
.mut_queue()
|
||||
.add_used(&mem, head_index, write_count as u32);
|
||||
|
||||
// Mark that we have at least one pending packet and we need to interrupt the guest.
|
||||
self.rx.deferred_irqs = true;
|
||||
|
||||
Ok(write_count >= self.rx.bytes_read)
|
||||
}
|
||||
|
||||
fn process_rx(&mut self, vring: &mut Vring) -> Result<()> {
|
||||
// Read as many frames as possible.
|
||||
loop {
|
||||
match self.read_tap() {
|
||||
Ok(count) => {
|
||||
self.rx.bytes_read = count;
|
||||
if !self.rx_single_frame(vring)? {
|
||||
self.rx.deferred_frame = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// The tap device is non-blocking, so any error aside from EAGAIN is
|
||||
// unexpected.
|
||||
match e.raw_os_error() {
|
||||
Some(err) if err == EAGAIN => (),
|
||||
_ => {
|
||||
error!("Failed to read tap: {:?}", e);
|
||||
return Err(Error::FailedReadTap);
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.rx.deferred_irqs {
|
||||
self.rx.deferred_irqs = false;
|
||||
vring.signal_used_queue().unwrap();
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn resume_rx(&mut self, vring: &mut Vring) -> Result<()> {
|
||||
if self.rx.deferred_frame {
|
||||
if self.rx_single_frame(vring)? {
|
||||
self.rx.deferred_frame = false;
|
||||
// process_rx() was interrupted possibly before consuming all
|
||||
// packets in the tap; try continuing now.
|
||||
self.process_rx(vring)
|
||||
} else if self.rx.deferred_irqs {
|
||||
self.rx.deferred_irqs = false;
|
||||
vring.signal_used_queue().unwrap();
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn process_tx(&mut self, vring: &mut Vring) -> Result<()> {
|
||||
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
|
||||
|
||||
let mut used_desc_heads = [(0, 0); QUEUE_SIZE];
|
||||
let mut used_count = 0;
|
||||
while let Some(avail_desc) = vring.mut_queue().iter(&mem).next() {
|
||||
let head_index = avail_desc.index;
|
||||
let mut read_count = 0;
|
||||
let mut next_desc = Some(avail_desc);
|
||||
|
||||
self.tx.iovec.clear();
|
||||
while let Some(desc) = next_desc {
|
||||
if desc.is_write_only() {
|
||||
break;
|
||||
}
|
||||
self.tx.iovec.push((desc.addr, desc.len as usize));
|
||||
read_count += desc.len as usize;
|
||||
next_desc = desc.next_descriptor();
|
||||
}
|
||||
used_desc_heads[used_count] = (head_index, read_count);
|
||||
used_count += 1;
|
||||
read_count = 0;
|
||||
// Copy buffer from across multiple descriptors.
|
||||
// TODO(performance - Issue #420): change this to use `writev()` instead of `write()`
|
||||
// and get rid of the intermediate buffer.
|
||||
for (desc_addr, desc_len) in self.tx.iovec.drain(..) {
|
||||
let limit = cmp::min((read_count + desc_len) as usize, self.tx.frame_buf.len());
|
||||
|
||||
let read_result = mem.read_slice(
|
||||
&mut self.tx.frame_buf[read_count..limit as usize],
|
||||
desc_addr,
|
||||
);
|
||||
match read_result {
|
||||
Ok(_) => {
|
||||
// Increment by number of bytes actually read
|
||||
read_count += limit - read_count;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to read slice: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let write_result = self.tap.write(&self.tx.frame_buf[..read_count as usize]);
|
||||
match write_result {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("net: tx: error failed to write to tap: {}", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if used_count > 0 {
|
||||
for &(desc_index, _) in &used_desc_heads[..used_count] {
|
||||
vring.mut_queue().add_used(&mem, desc_index, 0);
|
||||
}
|
||||
vring.signal_used_queue().unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_tap(&mut self) -> io::Result<usize> {
|
||||
self.tap.read(&mut self.rx.frame_buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserBackend for VhostUserNetBackend {
|
||||
fn num_queues(&self) -> usize {
|
||||
NUM_QUEUES
|
||||
}
|
||||
|
||||
fn max_queue_size(&self) -> usize {
|
||||
QUEUE_SIZE
|
||||
}
|
||||
|
||||
fn features(&self) -> u64 {
|
||||
1 << VIRTIO_NET_F_GUEST_CSUM
|
||||
| 1 << VIRTIO_NET_F_CSUM
|
||||
| 1 << VIRTIO_NET_F_GUEST_TSO4
|
||||
| 1 << VIRTIO_NET_F_GUEST_UFO
|
||||
| 1 << VIRTIO_NET_F_HOST_TSO4
|
||||
| 1 << VIRTIO_NET_F_HOST_UFO
|
||||
| 1 << VIRTIO_F_VERSION_1
|
||||
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
|
||||
}
|
||||
|
||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
||||
self.mem = Some(mem);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
vrings: &[Arc<RwLock<Vring>>],
|
||||
) -> VhostUserBackendResult<bool> {
|
||||
if evset != epoll::Events::EPOLLIN {
|
||||
return Err(Error::HandleEventNotEpollIn.into());
|
||||
}
|
||||
|
||||
match device_event {
|
||||
RX_QUEUE_EVENT => {
|
||||
let mut vring = vrings[0].write().unwrap();
|
||||
self.resume_rx(&mut vring)?;
|
||||
|
||||
if !self.rx_tap_listening {
|
||||
self.vring_worker.as_ref().unwrap().register_listener(
|
||||
self.tap.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(RX_TAP_EVENT),
|
||||
)?;
|
||||
self.rx_tap_listening = true;
|
||||
}
|
||||
}
|
||||
TX_QUEUE_EVENT => {
|
||||
let mut vring = vrings[1].write().unwrap();
|
||||
self.process_tx(&mut vring)?;
|
||||
}
|
||||
RX_TAP_EVENT => {
|
||||
let mut vring = vrings[0].write().unwrap();
|
||||
if self.rx.deferred_frame
|
||||
// Process a deferred frame first if available. Don't read from tap again
|
||||
// until we manage to receive this deferred frame.
|
||||
{
|
||||
if self.rx_single_frame(&mut vring)? {
|
||||
self.rx.deferred_frame = false;
|
||||
self.process_rx(&mut vring)?;
|
||||
} else if self.rx.deferred_irqs {
|
||||
self.rx.deferred_irqs = false;
|
||||
vring.signal_used_queue()?;
|
||||
}
|
||||
} else {
|
||||
self.process_rx(&mut vring)?;
|
||||
}
|
||||
}
|
||||
KILL_EVENT => {
|
||||
self.kill_evt.read().unwrap();
|
||||
return Ok(true);
|
||||
}
|
||||
_ => return Err(Error::HandleEventUnknownEvent.into()),
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VhostUserNetBackendConfig<'a> {
|
||||
pub ip: Ipv4Addr,
|
||||
pub mask: Ipv4Addr,
|
||||
pub sock: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> VhostUserNetBackendConfig<'a> {
|
||||
pub fn parse(backend: &'a str) -> Result<Self> {
|
||||
let params_list: Vec<&str> = backend.split(',').collect();
|
||||
|
||||
let mut ip_str: &str = "";
|
||||
let mut mask_str: &str = "";
|
||||
let mut sock: &str = "";
|
||||
|
||||
for param in params_list.iter() {
|
||||
if param.starts_with("ip=") {
|
||||
ip_str = ¶m[3..];
|
||||
} else if param.starts_with("mask=") {
|
||||
mask_str = ¶m[5..];
|
||||
} else if param.starts_with("sock=") {
|
||||
sock = ¶m[5..];
|
||||
}
|
||||
}
|
||||
|
||||
let mut ip: Ipv4Addr = Ipv4Addr::new(192, 168, 100, 1);
|
||||
let mut mask: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0);
|
||||
|
||||
if sock.is_empty() {
|
||||
return Err(Error::ParseSockParam);
|
||||
}
|
||||
if !ip_str.is_empty() {
|
||||
ip = ip_str.parse().map_err(|_| Error::ParseIpParam)?;
|
||||
}
|
||||
if !mask_str.is_empty() {
|
||||
mask = mask_str.parse().map_err(|_| Error::ParseMaskParam)?;
|
||||
}
|
||||
|
||||
Ok(VhostUserNetBackendConfig { ip, mask, sock })
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cmd_arguments = App::new("vhost-user-net backend")
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.about("Launch a vhost-user-net backend.")
|
||||
.arg(
|
||||
Arg::with_name("backend")
|
||||
.long("backend")
|
||||
.help(
|
||||
"Backend parameters \"ip=<ip_addr>,\
|
||||
mask=<net_mask>,sock=<socket_path>\"",
|
||||
)
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let vhost_user_net_backend = cmd_arguments.value_of("backend").unwrap();
|
||||
|
||||
let backend_config = match VhostUserNetBackendConfig::parse(vhost_user_net_backend) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
println!("Failed parsing parameters {:?}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let net_backend = Arc::new(RwLock::new(
|
||||
VhostUserNetBackend::new(backend_config.ip, backend_config.mask).unwrap(),
|
||||
));
|
||||
let name = "vhost-user-net-backend";
|
||||
let mut net_daemon = VhostUserDaemon::new(
|
||||
name.to_string(),
|
||||
backend_config.sock.to_string(),
|
||||
net_backend.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let vring_worker = net_daemon.get_vring_worker();
|
||||
|
||||
if let Err(e) = vring_worker.register_listener(
|
||||
net_backend.read().unwrap().kill_evt.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(KILL_EVENT),
|
||||
) {
|
||||
println!("failed to register listener for kill event: {:?}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
net_backend.write().unwrap().vring_worker = Some(vring_worker);
|
||||
|
||||
if let Err(e) = net_daemon.start() {
|
||||
println!(
|
||||
"failed to start daemon for vhost-user-net with error: {:?}",
|
||||
e
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
net_daemon.wait().unwrap();
|
||||
}
|
||||
2543
src/main.rs
2543
src/main.rs
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
#cloud-config
|
||||
users:
|
||||
- name: admin
|
||||
- name: cloud
|
||||
passwd: $6$7125787751a8d18a$sHwGySomUA1PawiNFWVCKYQN.Ec.Wzz0JtPPL1MvzFrkwmop2dq7.4CYf03A5oemPQ4pOFCCrtCelvFBEle/K.
|
||||
sudo:
|
||||
- ALL=(ALL) NOPASSWD:ALL
|
||||
@@ -47,10 +47,8 @@ write_files:
|
||||
content: |
|
||||
#!/bin/bash
|
||||
|
||||
mount -t 9p -o trans=virtio cloud_hypervisor /mnt -oversion=9p2000.L,posixacl,cache=loose
|
||||
modprobe vfio_iommu_type1 allow_unsafe_interrupts
|
||||
modprobe vfio_pci
|
||||
bash -c "echo 0000:00:03.0 > /sys/bus/pci/devices/0000\:00\:03.0/driver/unbind"
|
||||
mount -t virtio_fs virtiofs /mnt -o rootmode=040000,user_id=0,group_id=0,dax
|
||||
bash -c "echo 0000:00:05.0 > /sys/bus/pci/devices/0000\:00\:05.0/driver/unbind"
|
||||
bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id"
|
||||
|
||||
/mnt/cloud-hypervisor --console off --serial tty --kernel /mnt/vmlinux --cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda2 VFIOTAG" --disk /mnt/clear-29810-cloud.img /mnt/cloudinit.img --cpus 1 --memory size=512M --rng --device /sys/bus/pci/devices/0000:00:03.0/
|
||||
/mnt/cloud-hypervisor --kernel /mnt/vmlinux --cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda2 VFIOTAG" --disk path=/mnt/clear-cloudguest.img path=/mnt/cloudinit.img --cpus 1 --memory size=512M --rng --device path=/sys/bus/pci/devices/0000:00:05.0/
|
||||
2
test_data/cloud-init/ubuntu/meta-data
Executable file
2
test_data/cloud-init/ubuntu/meta-data
Executable file
@@ -0,0 +1,2 @@
|
||||
instance-id: cloud
|
||||
local-hostname: cloud
|
||||
12
test_data/cloud-init/ubuntu/network-config
Normal file
12
test_data/cloud-init/ubuntu/network-config
Normal file
@@ -0,0 +1,12 @@
|
||||
network:
|
||||
version: 1
|
||||
config:
|
||||
- type: physical
|
||||
name: eth0
|
||||
mac_address: 12:34:56:78:90:ab
|
||||
subnets:
|
||||
- type: static
|
||||
address: 192.168.2.2/24
|
||||
gateway: 192.168.2.1
|
||||
dns_nameservers:
|
||||
- 192.168.2.1
|
||||
10
test_data/cloud-init/ubuntu/user-data
Executable file
10
test_data/cloud-init/ubuntu/user-data
Executable file
@@ -0,0 +1,10 @@
|
||||
#cloud-config
|
||||
users:
|
||||
- name: cloud
|
||||
passwd: $6$7125787751a8d18a$sHwGySomUA1PawiNFWVCKYQN.Ec.Wzz0JtPPL1MvzFrkwmop2dq7.4CYf03A5oemPQ4pOFCCrtCelvFBEle/K.
|
||||
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||
lock_passwd: False
|
||||
inactive: False
|
||||
shell: /bin/bash
|
||||
|
||||
ssh_pwauth: True
|
||||
@@ -1,12 +0,0 @@
|
||||
[package]
|
||||
name = "vfio-bindings"
|
||||
version = "0.0.1"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
||||
[features]
|
||||
default = ["v5_0_0"]
|
||||
v5_0_0 = []
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
// generated with bindgen linux/uapi/linux/vfio.h --constified-enum '*' --with-derive-default
|
||||
#[cfg(feature = "v5_0_0")]
|
||||
mod v5_0_0;
|
||||
|
||||
pub mod bindings {
|
||||
#[cfg(feature = "v5_0_0")]
|
||||
pub use super::v5_0_0::*;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#![allow(clippy::all)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
pub mod vfio;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,16 +4,17 @@ version = "0.0.1"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = ">=1.2.1"
|
||||
byteorder = "1.3.2"
|
||||
devices = { path = "../devices" }
|
||||
kvm-bindings = "0.1"
|
||||
kvm-bindings = "0.1.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = ">=0.2.39"
|
||||
log = "*"
|
||||
libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
pci = { path = "../pci" }
|
||||
vfio-bindings = { path = "../vfio-bindings" }
|
||||
vfio-bindings = "0.1.0"
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
[dependencies.vm-memory]
|
||||
git = "https://github.com/rust-vmm/vm-memory"
|
||||
|
||||
@@ -9,10 +9,12 @@ extern crate byteorder;
|
||||
extern crate devices;
|
||||
extern crate kvm_bindings;
|
||||
extern crate kvm_ioctls;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate pci;
|
||||
extern crate vfio_bindings;
|
||||
extern crate vm_allocator;
|
||||
extern crate vm_device;
|
||||
extern crate vm_memory;
|
||||
#[macro_use]
|
||||
extern crate vmm_sys_util;
|
||||
@@ -23,7 +25,7 @@ mod vfio_pci;
|
||||
|
||||
use std::mem::size_of;
|
||||
|
||||
pub use vfio_device::{VfioDevice, VfioError};
|
||||
pub use vfio_device::{VfioContainer, VfioDevice, VfioDmaMapping, VfioError};
|
||||
pub use vfio_pci::{VfioPciDevice, VfioPciError};
|
||||
|
||||
// Returns a `Vec<T>` with a size in bytes at least as large as `size_in_bytes`.
|
||||
|
||||
@@ -14,13 +14,15 @@ use std::mem;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::os::unix::prelude::FileExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::result;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::u32;
|
||||
use vfio_bindings::bindings::vfio::*;
|
||||
use vfio_ioctls::*;
|
||||
use vm_memory::{Address, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
|
||||
use vm_device::ExternalDmaMapping;
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::ioctl::*;
|
||||
use vmm_sys_util::EventFd;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VfioError {
|
||||
@@ -103,7 +105,7 @@ struct vfio_region_info_with_cap {
|
||||
cap_info: __IncompleteArrayField<u8>,
|
||||
}
|
||||
|
||||
struct VfioContainer {
|
||||
pub struct VfioContainer {
|
||||
container: File,
|
||||
}
|
||||
|
||||
@@ -151,7 +153,7 @@ impl VfioContainer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
|
||||
pub fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
|
||||
let dma_map = vfio_iommu_type1_dma_map {
|
||||
argsz: mem::size_of::<vfio_iommu_type1_dma_map>() as u32,
|
||||
flags: VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
|
||||
@@ -170,7 +172,7 @@ impl VfioContainer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
|
||||
pub fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
|
||||
let mut dma_unmap = vfio_iommu_type1_dma_unmap {
|
||||
argsz: mem::size_of::<vfio_iommu_type1_dma_unmap>() as u32,
|
||||
flags: 0,
|
||||
@@ -198,7 +200,7 @@ impl AsRawFd for VfioContainer {
|
||||
struct VfioGroup {
|
||||
group: File,
|
||||
device: Arc<DeviceFd>,
|
||||
container: VfioContainer,
|
||||
container: Arc<VfioContainer>,
|
||||
}
|
||||
|
||||
impl VfioGroup {
|
||||
@@ -225,7 +227,7 @@ impl VfioGroup {
|
||||
return Err(VfioError::GroupViable);
|
||||
}
|
||||
|
||||
let container = VfioContainer::new()?;
|
||||
let container = Arc::new(VfioContainer::new()?);
|
||||
if container.get_api_version() as u32 != VFIO_API_VERSION {
|
||||
return Err(VfioError::VfioApiVersion);
|
||||
}
|
||||
@@ -506,6 +508,68 @@ impl VfioDeviceInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// This structure implements the ExternalDmaMapping trait. It is meant to
|
||||
/// be used when the caller tries to provide a way to update the mappings
|
||||
/// associated with a specific VFIO container.
|
||||
pub struct VfioDmaMapping {
|
||||
container: Arc<VfioContainer>,
|
||||
memory: Arc<RwLock<GuestMemoryMmap>>,
|
||||
}
|
||||
|
||||
impl VfioDmaMapping {
|
||||
pub fn new(container: Arc<VfioContainer>, memory: Arc<RwLock<GuestMemoryMmap>>) -> Self {
|
||||
VfioDmaMapping { container, memory }
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternalDmaMapping for VfioDmaMapping {
|
||||
fn map(&self, iova: u64, gpa: u64, size: u64) -> result::Result<(), io::Error> {
|
||||
let user_addr = if let Some(addr) = self
|
||||
.memory
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_host_address(GuestAddress(gpa))
|
||||
{
|
||||
addr as u64
|
||||
} else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"failed to convert guest address 0x{:x} into \
|
||||
host user virtual address",
|
||||
gpa
|
||||
),
|
||||
));
|
||||
};
|
||||
|
||||
self.container
|
||||
.vfio_dma_map(iova, size, user_addr)
|
||||
.map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"failed to map memory for VFIO container, \
|
||||
iova 0x{:x}, gpa 0x{:x}, size 0x{:x}: {:?}",
|
||||
iova, gpa, size, e
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn unmap(&self, iova: u64, size: u64) -> result::Result<(), io::Error> {
|
||||
self.container.vfio_dma_unmap(iova, size).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"failed to unmap memory for VFIO container, \
|
||||
iova 0x{:x}, size 0x{:x}: {:?}",
|
||||
iova, size, e
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Vfio device for exposing regions which could be read/write to kernel vfio device.
|
||||
pub struct VfioDevice {
|
||||
device: File,
|
||||
@@ -513,14 +577,20 @@ pub struct VfioDevice {
|
||||
group: VfioGroup,
|
||||
regions: Vec<VfioRegion>,
|
||||
irqs: HashMap<u32, VfioIrq>,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
iommu_attached: bool,
|
||||
}
|
||||
|
||||
impl VfioDevice {
|
||||
/// Create a new vfio device, then guest read/write on this device could be
|
||||
/// transfered into kernel vfio.
|
||||
/// sysfspath specify the vfio device path in sys file system.
|
||||
pub fn new(sysfspath: &Path, device_fd: Arc<DeviceFd>, mem: GuestMemoryMmap) -> Result<Self> {
|
||||
pub fn new(
|
||||
sysfspath: &Path,
|
||||
device_fd: Arc<DeviceFd>,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
iommu_attached: bool,
|
||||
) -> Result<Self> {
|
||||
let uuid_path: PathBuf = [sysfspath, Path::new("iommu_group")].iter().collect();
|
||||
let group_path = uuid_path.read_link().map_err(|_| VfioError::InvalidPath)?;
|
||||
let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?;
|
||||
@@ -541,6 +611,7 @@ impl VfioDevice {
|
||||
regions,
|
||||
irqs,
|
||||
mem,
|
||||
iommu_attached,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -758,6 +829,10 @@ impl VfioDevice {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_container(&self) -> Arc<VfioContainer> {
|
||||
self.group.container.clone()
|
||||
}
|
||||
|
||||
fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
|
||||
self.group.container.vfio_dma_map(iova, size, user_addr)
|
||||
}
|
||||
@@ -769,22 +844,26 @@ impl VfioDevice {
|
||||
/// Add all guest memory regions into vfio container's iommu table,
|
||||
/// then vfio kernel driver could access guest memory from gfn
|
||||
pub fn setup_dma_map(&self) -> Result<()> {
|
||||
self.mem.with_regions(|_index, region| {
|
||||
self.vfio_dma_map(
|
||||
region.start_addr().raw_value(),
|
||||
region.len() as u64,
|
||||
region.as_ptr() as u64,
|
||||
)
|
||||
})?;
|
||||
if !self.iommu_attached {
|
||||
self.mem.read().unwrap().with_regions(|_index, region| {
|
||||
self.vfio_dma_map(
|
||||
region.start_addr().raw_value(),
|
||||
region.len() as u64,
|
||||
region.as_ptr() as u64,
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// remove all guest memory regions from vfio containers iommu table
|
||||
/// then vfio kernel driver couldn't access this guest memory
|
||||
pub fn unset_dma_map(&self) -> Result<()> {
|
||||
self.mem.with_regions(|_index, region| {
|
||||
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64)
|
||||
})?;
|
||||
if !self.iommu_attached {
|
||||
self.mem.read().unwrap().with_regions(|_index, region| {
|
||||
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ use std::{fmt, io};
|
||||
use vfio_bindings::bindings::vfio::*;
|
||||
use vm_allocator::SystemAllocator;
|
||||
use vm_memory::{Address, GuestAddress, GuestUsize};
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VfioPciError {
|
||||
@@ -207,12 +207,14 @@ impl Interrupt {
|
||||
|
||||
fn msix_write_table(&mut self, offset: u64, data: &[u8]) {
|
||||
if let Some(ref mut msix) = &mut self.msix {
|
||||
let offset = offset - u64::from(msix.cap.table_offset());
|
||||
msix.bar.write_table(offset, data)
|
||||
}
|
||||
}
|
||||
|
||||
fn msix_read_table(&self, offset: u64, data: &mut [u8]) {
|
||||
if let Some(msix) = &self.msix {
|
||||
let offset = offset - u64::from(msix.cap.table_offset());
|
||||
msix.bar.read_table(offset, data)
|
||||
}
|
||||
}
|
||||
@@ -386,11 +388,9 @@ impl VfioPciDevice {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
entry.u.msi.address_lo = route.msi_vector.msg_addr_lo;
|
||||
entry.u.msi.address_hi = route.msi_vector.msg_addr_hi;
|
||||
entry.u.msi.data = route.msi_vector.msg_data;
|
||||
};
|
||||
entry.u.msi.address_lo = route.msi_vector.msg_addr_lo;
|
||||
entry.u.msi.address_hi = route.msi_vector.msg_addr_hi;
|
||||
entry.u.msi.data = route.msi_vector.msg_data;
|
||||
|
||||
entry_vec.push(entry);
|
||||
}
|
||||
@@ -694,8 +694,6 @@ impl BusDevice for VfioPciDevice {
|
||||
|
||||
// First BAR offset in the PCI config space.
|
||||
const PCI_CONFIG_BAR_OFFSET: u32 = 0x10;
|
||||
// First BAR register index
|
||||
const PCI_CONFIG_BAR0_INDEX: usize = 4;
|
||||
// Capability register offset in the PCI config space.
|
||||
const PCI_CONFIG_CAPABILITY_OFFSET: u32 = 0x34;
|
||||
// IO BAR when first BAR bit is 1.
|
||||
@@ -708,6 +706,10 @@ const PCI_CONFIG_MEMORY_BAR_64BIT: u32 = 0x4;
|
||||
const PCI_CONFIG_REGISTER_SIZE: usize = 4;
|
||||
// Number of BARs for a PCI device
|
||||
const BAR_NUMS: usize = 6;
|
||||
// PCI Header Type register index
|
||||
const PCI_HEADER_TYPE_REG_INDEX: usize = 3;
|
||||
// First BAR register index
|
||||
const PCI_CONFIG_BAR0_INDEX: usize = 4;
|
||||
// PCI ROM expansion BAR register index
|
||||
const PCI_ROM_EXP_BAR_INDEX: usize = 12;
|
||||
// PCI interrupt pin and line register index
|
||||
@@ -726,14 +728,18 @@ impl PciDevice for VfioPciDevice {
|
||||
// We're not saving the BAR address to restore it, because we
|
||||
// are going to allocate a guest address for each BAR and write
|
||||
// that new address back.
|
||||
while bar_id < VFIO_PCI_ROM_REGION_INDEX {
|
||||
while bar_id < VFIO_PCI_CONFIG_REGION_INDEX {
|
||||
let mut lsb_size: u32 = 0xffff_ffff;
|
||||
let mut msb_size = 0;
|
||||
let mut region_size: u64;
|
||||
let bar_addr: GuestAddress;
|
||||
|
||||
// Read the BAR size (Starts by all 1s to the BAR)
|
||||
let bar_offset = PCI_CONFIG_BAR_OFFSET + bar_id * 4;
|
||||
let bar_offset = if bar_id == VFIO_PCI_ROM_REGION_INDEX {
|
||||
(PCI_ROM_EXP_BAR_INDEX * 4) as u32
|
||||
} else {
|
||||
PCI_CONFIG_BAR_OFFSET + bar_id * 4
|
||||
};
|
||||
|
||||
self.vfio_pci_configuration
|
||||
.write_config_dword(lsb_size, bar_offset);
|
||||
@@ -748,15 +754,23 @@ impl PciDevice for VfioPciDevice {
|
||||
}
|
||||
|
||||
// Is this an IO BAR?
|
||||
let io_bar = match lsb_flag & PCI_CONFIG_IO_BAR {
|
||||
PCI_CONFIG_IO_BAR => true,
|
||||
_ => false,
|
||||
let io_bar = if bar_id != VFIO_PCI_ROM_REGION_INDEX {
|
||||
match lsb_flag & PCI_CONFIG_IO_BAR {
|
||||
PCI_CONFIG_IO_BAR => true,
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Is this a 64-bit BAR?
|
||||
let is_64bit_bar = match lsb_flag & PCI_CONFIG_MEMORY_BAR_64BIT {
|
||||
PCI_CONFIG_MEMORY_BAR_64BIT => true,
|
||||
_ => false,
|
||||
let is_64bit_bar = if bar_id != VFIO_PCI_ROM_REGION_INDEX {
|
||||
match lsb_flag & PCI_CONFIG_MEMORY_BAR_64BIT {
|
||||
PCI_CONFIG_MEMORY_BAR_64BIT => true,
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// By default, the region type is 32 bits memory BAR.
|
||||
@@ -808,14 +822,15 @@ impl PciDevice for VfioPciDevice {
|
||||
// In case the BAR is mappable directly, this means it might be
|
||||
// set as KVM user memory region, which expects to deal with 4K
|
||||
// pages. Therefore, the aligment has to be set accordingly.
|
||||
let bar_alignment =
|
||||
if self.device.get_region_flags(bar_id) & VFIO_REGION_INFO_FLAG_MMAP != 0 {
|
||||
// 4K alignment
|
||||
0x1000
|
||||
} else {
|
||||
// Default 16 bytes alignment
|
||||
0x10
|
||||
};
|
||||
let bar_alignment = if (bar_id == VFIO_PCI_ROM_REGION_INDEX)
|
||||
|| (self.device.get_region_flags(bar_id) & VFIO_REGION_INFO_FLAG_MMAP != 0)
|
||||
{
|
||||
// 4K alignment
|
||||
0x1000
|
||||
} else {
|
||||
// Default 16 bytes alignment
|
||||
0x10
|
||||
};
|
||||
if is_64bit_bar {
|
||||
bar_addr = allocator
|
||||
.allocate_mmio_addresses(None, region_size, Some(bar_alignment))
|
||||
@@ -827,16 +842,28 @@ impl PciDevice for VfioPciDevice {
|
||||
}
|
||||
}
|
||||
|
||||
let reg_idx = if bar_id == VFIO_PCI_ROM_REGION_INDEX {
|
||||
PCI_ROM_EXP_BAR_INDEX
|
||||
} else {
|
||||
bar_id as usize
|
||||
};
|
||||
|
||||
// We can now build our BAR configuration block.
|
||||
let config = PciBarConfiguration::default()
|
||||
.set_register_index(bar_id as usize)
|
||||
.set_register_index(reg_idx)
|
||||
.set_address(bar_addr.raw_value())
|
||||
.set_size(region_size)
|
||||
.set_region_type(region_type);
|
||||
|
||||
self.configuration
|
||||
.add_pci_bar(&config)
|
||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
|
||||
if bar_id == VFIO_PCI_ROM_REGION_INDEX {
|
||||
self.configuration
|
||||
.add_pci_rom_bar(&config, lsb_flag & 0x1)
|
||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
|
||||
} else {
|
||||
self.configuration
|
||||
.add_pci_bar(&config)
|
||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
|
||||
}
|
||||
|
||||
ranges.push((bar_addr, region_size, region_type));
|
||||
self.mmio_regions.push(MmioRegion {
|
||||
@@ -862,7 +889,9 @@ impl PciDevice for VfioPciDevice {
|
||||
// When the guest wants to write to a BAR, we trap it into
|
||||
// our local configuration space. We're not reprogramming
|
||||
// VFIO device.
|
||||
if reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS {
|
||||
if (reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS)
|
||||
|| reg_idx == PCI_ROM_EXP_BAR_INDEX
|
||||
{
|
||||
// We keep our local cache updated with the BARs.
|
||||
// We'll read it back from there when the guest is asking
|
||||
// for BARs (see read_config_register()).
|
||||
@@ -900,23 +929,25 @@ impl PciDevice for VfioPciDevice {
|
||||
// from our local configuration space. We want the guest to
|
||||
// use that and not the VFIO device BARs as it does not map
|
||||
// with the guest address space.
|
||||
if reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS {
|
||||
if (reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS)
|
||||
|| reg_idx == PCI_ROM_EXP_BAR_INDEX
|
||||
{
|
||||
return self.configuration.read_reg(reg_idx);
|
||||
}
|
||||
|
||||
// Since the ROM expansion BAR is not yet handled by the code, it is
|
||||
// more proper to expose it to the guest as being disabled.
|
||||
if reg_idx == PCI_ROM_EXP_BAR_INDEX {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Since we don't support INTx (only MSI and MSI-X), we should not
|
||||
// expose an invalid Interrupt Pin to the guest. By using a specific
|
||||
// mask in case the register being read correspond to the interrupt
|
||||
// register, this code makes sure to always expose an Interrupt Pin
|
||||
// value of 0, which stands for no interrupt pin support.
|
||||
//
|
||||
// Since we don't support passing multi-functions devices, we should
|
||||
// mask the multi-function bit, bit 7 of the Header Type byte on the
|
||||
// register 3.
|
||||
let mask = if reg_idx == PCI_INTX_REG_INDEX {
|
||||
0xffff_00ff
|
||||
} else if reg_idx == PCI_HEADER_TYPE_REG_INDEX {
|
||||
0xff7f_ffff
|
||||
} else {
|
||||
0xffff_ffff
|
||||
};
|
||||
|
||||
@@ -13,13 +13,13 @@ vhost-user-master = []
|
||||
vhost-user-slave = []
|
||||
|
||||
[dependencies]
|
||||
bitflags = ">=1.0.1"
|
||||
libc = ">=0.2.39"
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
bitflags = "1.1.0"
|
||||
libc = "0.2.60"
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
[dependencies.vm-memory]
|
||||
git = "https://github.com/rust-vmm/vm-memory"
|
||||
optional = true
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.0.5"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
use super::Result;
|
||||
use std::os::unix::io::RawFd;
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// Maximum number of memory regions supported.
|
||||
pub const VHOST_MAX_MEMORY_REGIONS: usize = 255;
|
||||
|
||||
@@ -15,8 +15,8 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::ptr::null;
|
||||
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestUsize};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::ioctl::{ioctl, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref};
|
||||
use vmm_sys_util::EventFd;
|
||||
|
||||
use super::{
|
||||
Error, Result, VhostBackend, VhostUserMemoryRegionInfo, VringConfigData,
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::connection::Endpoint;
|
||||
use super::message::*;
|
||||
@@ -40,11 +40,12 @@ pub trait VhostUserMaster: VhostBackend {
|
||||
offset: u32,
|
||||
size: u32,
|
||||
flags: VhostUserConfigFlags,
|
||||
) -> Result<Vec<u8>>;
|
||||
buf: &[u8],
|
||||
) -> Result<(VhostUserConfig, VhostUserConfigPayload)>;
|
||||
|
||||
/// Change the virtio device configuration space. It also can be used for live migration on the
|
||||
/// destination host to set readonly configuration space fields.
|
||||
fn set_config(&mut self, offset: u32, buf: &[u8], flags: VhostUserConfigFlags) -> Result<()>;
|
||||
fn set_config(&mut self, offset: u32, flags: VhostUserConfigFlags, buf: &[u8]) -> Result<()>;
|
||||
|
||||
/// Setup slave communication channel.
|
||||
fn set_slave_request_fd(&mut self, fd: RawFd) -> Result<()>;
|
||||
@@ -166,9 +167,12 @@ impl VhostBackend for Master {
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
// Clippy doesn't seem to know that if let with && is still experimental
|
||||
#[allow(clippy::unnecessary_unwrap)]
|
||||
fn set_log_base(&mut self, base: u64, fd: Option<RawFd>) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let val = VhostUserU64::new(base);
|
||||
|
||||
if node.acked_protocol_features & VhostUserProtocolFeatures::LOG_SHMFD.bits() != 0
|
||||
&& fd.is_some()
|
||||
{
|
||||
@@ -344,7 +348,8 @@ impl VhostUserMaster for Master {
|
||||
offset: u32,
|
||||
size: u32,
|
||||
flags: VhostUserConfigFlags,
|
||||
) -> Result<Vec<u8>> {
|
||||
buf: &[u8],
|
||||
) -> Result<(VhostUserConfig, VhostUserConfigPayload)> {
|
||||
let body = VhostUserConfig::new(offset, size, flags);
|
||||
if !body.is_valid() {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
@@ -352,28 +357,28 @@ impl VhostUserMaster for Master {
|
||||
|
||||
let mut node = self.node.lock().unwrap();
|
||||
// depends on VhostUserProtocolFeatures::CONFIG
|
||||
if node.acked_virtio_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
|
||||
// TODO: vhost-user spec states that:
|
||||
// vhost-user spec states that:
|
||||
// "Master payload: virtio device config space"
|
||||
// But what content should the payload contains for a get_config() request?
|
||||
// So current implementation doesn't conform to the spec.
|
||||
let hdr = node.send_request_with_body(MasterReq::GET_CONFIG, &body, None)?;
|
||||
let (reply, buf, rfds) = node.recv_reply_with_payload::<VhostUserConfig>(&hdr)?;
|
||||
// "Slave payload: virtio device config space"
|
||||
let hdr = node.send_request_with_payload(MasterReq::GET_CONFIG, &body, buf, None)?;
|
||||
let (body_reply, buf_reply, rfds) =
|
||||
node.recv_reply_with_payload::<VhostUserConfig>(&hdr)?;
|
||||
if rfds.is_some() {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return error_code(VhostUserError::InvalidMessage);
|
||||
} else if reply.size == 0 {
|
||||
} else if body_reply.size == 0 {
|
||||
return error_code(VhostUserError::SlaveInternalError);
|
||||
} else if reply.size != body.size || reply.size as usize != buf.len() {
|
||||
} else if body_reply.size != body.size || body_reply.size as usize != buf.len() {
|
||||
return error_code(VhostUserError::InvalidMessage);
|
||||
}
|
||||
Ok(buf)
|
||||
Ok((body_reply, buf_reply))
|
||||
}
|
||||
|
||||
fn set_config(&mut self, offset: u32, buf: &[u8], flags: VhostUserConfigFlags) -> Result<()> {
|
||||
fn set_config(&mut self, offset: u32, flags: VhostUserConfigFlags, buf: &[u8]) -> Result<()> {
|
||||
if buf.len() > MAX_MSG_SIZE {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
@@ -384,11 +389,11 @@ impl VhostUserMaster for Master {
|
||||
|
||||
let mut node = self.node.lock().unwrap();
|
||||
// depends on VhostUserProtocolFeatures::CONFIG
|
||||
if node.acked_virtio_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
|
||||
let hdr = node.send_request_with_payload(MasterReq::GET_CONFIG, &body, buf, None)?;
|
||||
let hdr = node.send_request_with_payload(MasterReq::SET_CONFIG, &body, buf, None)?;
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
@@ -550,12 +555,12 @@ impl MasterInternal {
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
let mut buf = vec![0; MAX_MSG_SIZE - mem::size_of::<T>()];
|
||||
let mut buf: Vec<u8> = vec![0; hdr.get_size() as usize - mem::size_of::<T>()];
|
||||
let (reply, body, bytes, rfds) = self.main_sock.recv_payload_into_buf::<T>(&mut buf)?;
|
||||
if !reply.is_reply_for(hdr)
|
||||
|| reply.get_size() as usize != mem::size_of::<T>() + bytes
|
||||
|| rfds.is_some()
|
||||
|| body.is_valid()
|
||||
|| !body.is_valid()
|
||||
{
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return Err(VhostUserError::InvalidMessage);
|
||||
@@ -603,7 +608,7 @@ impl MasterInternal {
|
||||
#[inline]
|
||||
fn new_request_header(request: MasterReq, size: u32) -> VhostUserMsgHeader<MasterReq> {
|
||||
// TODO: handle NEED_REPLY flag
|
||||
VhostUserMsgHeader::new(request, 0, size)
|
||||
VhostUserMsgHeader::new(request, 0x1, size)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,14 +109,14 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
||||
}
|
||||
};
|
||||
|
||||
match hdr.get_code() {
|
||||
let res = match hdr.get_code() {
|
||||
SlaveReq::CONFIG_CHANGE_MSG => {
|
||||
self.check_msg_size(&hdr, size, 0)?;
|
||||
self.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.handle_config_change()
|
||||
.map_err(Error::ReqHandlerError)?;
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
SlaveReq::FS_MAP => {
|
||||
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
||||
@@ -124,7 +124,7 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.fs_slave_map(msg, rfds.unwrap()[0])
|
||||
.map_err(Error::ReqHandlerError)?;
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
SlaveReq::FS_UNMAP => {
|
||||
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
||||
@@ -132,7 +132,7 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.fs_slave_unmap(msg)
|
||||
.map_err(Error::ReqHandlerError)?;
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
SlaveReq::FS_SYNC => {
|
||||
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
||||
@@ -140,14 +140,14 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.fs_slave_sync(msg)
|
||||
.map_err(Error::ReqHandlerError)?;
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
}
|
||||
_ => Err(Error::InvalidMessage),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
self.send_ack_message(&hdr, &res)?;
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn check_state(&self) -> Result<()> {
|
||||
@@ -217,6 +217,38 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
fn new_reply_header<T: Sized>(
|
||||
&self,
|
||||
req: &VhostUserMsgHeader<SlaveReq>,
|
||||
) -> Result<VhostUserMsgHeader<SlaveReq>> {
|
||||
if mem::size_of::<T>() > MAX_MSG_SIZE {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
Ok(VhostUserMsgHeader::new(
|
||||
req.get_code(),
|
||||
VhostUserHeaderFlag::REPLY.bits(),
|
||||
mem::size_of::<T>() as u32,
|
||||
))
|
||||
}
|
||||
|
||||
fn send_ack_message(
|
||||
&mut self,
|
||||
req: &VhostUserMsgHeader<SlaveReq>,
|
||||
res: &Result<()>,
|
||||
) -> Result<()> {
|
||||
if req.is_need_reply() {
|
||||
let hdr = self.new_reply_header::<VhostUserU64>(req)?;
|
||||
let val = match res {
|
||||
Ok(_) => 0,
|
||||
Err(_) => 1,
|
||||
};
|
||||
let msg = VhostUserU64::new(val);
|
||||
self.sub_sock.send_message(&hdr, &msg, None)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: VhostUserMasterReqHandler> AsRawFd for MasterReqHandler<S> {
|
||||
|
||||
@@ -346,6 +346,8 @@ bitflags! {
|
||||
const SLAVE_SEND_FD = 0x0000_0400;
|
||||
/// Allow the slave to register a host notifier.
|
||||
const HOST_NOTIFIER = 0x0000_0800;
|
||||
/// Support inflight shmfd.
|
||||
const INFLIGHT_SHMFD = 0x0000_1000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,12 +542,10 @@ impl VhostUserMsgValidator for VhostUserVringAddr {
|
||||
bitflags! {
|
||||
/// Flags for the device configuration message.
|
||||
pub struct VhostUserConfigFlags: u32 {
|
||||
/// TODO: seems the vhost-user spec has refined the definition, EMPTY is removed.
|
||||
const EMPTY = 0x0;
|
||||
/// Vhost master messages used for writable fields
|
||||
const WRITABLE = 0x1;
|
||||
/// Mark that message is part of an ongoing live-migration operation.
|
||||
const LIVE_MIGRATION = 0x2;
|
||||
/// Vhost master messages used for writeable fields.
|
||||
const WRITABLE = 0x0;
|
||||
/// Vhost master messages used for live migration.
|
||||
const LIVE_MIGRATION = 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,10 +577,9 @@ impl VhostUserMsgValidator for VhostUserConfig {
|
||||
fn is_valid(&self) -> bool {
|
||||
if (self.flags & !VhostUserConfigFlags::all().bits()) != 0 {
|
||||
return false;
|
||||
} else if self.offset < VHOST_USER_CONFIG_OFFSET
|
||||
|| self.offset >= VHOST_USER_CONFIG_SIZE
|
||||
} else if self.offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| self.size == 0
|
||||
|| self.size > (VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET)
|
||||
|| self.size > VHOST_USER_CONFIG_SIZE
|
||||
|| self.size + self.offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return false;
|
||||
@@ -787,7 +786,7 @@ mod tests {
|
||||
let mut msg = VhostUserConfig::new(
|
||||
VHOST_USER_CONFIG_OFFSET,
|
||||
VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET,
|
||||
VhostUserConfigFlags::EMPTY,
|
||||
VhostUserConfigFlags::WRITABLE,
|
||||
);
|
||||
|
||||
assert!(msg.is_valid());
|
||||
@@ -804,7 +803,7 @@ mod tests {
|
||||
msg.size = 2;
|
||||
assert!(!msg.is_valid());
|
||||
msg.size = 1;
|
||||
msg.flags |= VhostUserConfigFlags::WRITABLE.bits();
|
||||
msg.flags |= VhostUserConfigFlags::LIVE_MIGRATION.bits();
|
||||
assert!(msg.is_valid());
|
||||
msg.flags |= 0x4;
|
||||
assert!(!msg.is_valid());
|
||||
|
||||
@@ -116,10 +116,10 @@ impl Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<vmm_sys_util::Error> for Error {
|
||||
impl std::convert::From<vmm_sys_util::errno::Error> for Error {
|
||||
/// Convert raw socket errors into meaningful vhost-user errors.
|
||||
///
|
||||
/// The vmm_sys_util::Error is a simple wrapper over the raw errno, which doesn't means much
|
||||
/// The vmm_sys_util::errno::Error is a simple wrapper over the raw errno, which doesn't means much
|
||||
/// to the vhost-user connection manager. So convert it into meaningful errors to simplify
|
||||
/// the connection manager logic.
|
||||
///
|
||||
@@ -128,7 +128,7 @@ impl std::convert::From<vmm_sys_util::Error> for Error {
|
||||
/// * - Error::SocketBroken: the underline socket is broken.
|
||||
/// * - Error::SocketError: other socket related errors.
|
||||
#[allow(unreachable_patterns)] // EWOULDBLOCK equals to EGAIN on linux
|
||||
fn from(err: vmm_sys_util::Error) -> Self {
|
||||
fn from(err: vmm_sys_util::errno::Error) -> Self {
|
||||
match err.errno() {
|
||||
// The socket is marked nonblocking and the requested operation would block.
|
||||
libc::EAGAIN => Error::SocketRetry(IOError::from_raw_os_error(libc::EAGAIN)),
|
||||
|
||||
@@ -16,7 +16,7 @@ use std::ptr::{copy_nonoverlapping, null_mut, write_unaligned};
|
||||
use libc::{
|
||||
c_long, c_void, cmsghdr, iovec, msghdr, recvmsg, sendmsg, MSG_NOSIGNAL, SCM_RIGHTS, SOL_SOCKET,
|
||||
};
|
||||
use vmm_sys_util::{Error, Result};
|
||||
use vmm_sys_util::errno::{Error, Result};
|
||||
|
||||
// Each of the following macros performs the same function as their C counterparts. They are each
|
||||
// macros because they are used to size statically allocated arrays.
|
||||
@@ -176,7 +176,7 @@ fn raw_recvmsg(fd: RawFd, iovecs: &mut [iovec], in_fds: &mut [RawFd]) -> Result<
|
||||
|
||||
// Safe because the msghdr was properly constructed from valid (or null) pointers of the
|
||||
// indicated length and we check the return value.
|
||||
let total_read = unsafe { recvmsg(fd, &mut msg, 0) };
|
||||
let total_read = unsafe { recvmsg(fd, &mut msg, libc::MSG_WAITALL) };
|
||||
|
||||
if total_read == -1 {
|
||||
return Err(Error::last());
|
||||
@@ -335,7 +335,7 @@ mod tests {
|
||||
|
||||
use libc::cmsghdr;
|
||||
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[test]
|
||||
fn buffer_len() {
|
||||
|
||||
22
vhost_user_backend/Cargo.toml
Normal file
22
vhost_user_backend/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "vhost_user_backend"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
pci_support = ["vm-virtio/pci_support"]
|
||||
mmio_support = ["vm-virtio/mmio_support"]
|
||||
|
||||
[dependencies]
|
||||
epoll = "4.1.0"
|
||||
libc = "0.2.65"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
[dependencies.vhost_rs]
|
||||
path = "../vhost_rs"
|
||||
features = ["vhost-user-slave"]
|
||||
|
||||
759
vhost_user_backend/src/lib.rs
Normal file
759
vhost_user_backend/src/lib.rs
Normal file
@@ -0,0 +1,759 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::error;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::num::Wrapping;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread;
|
||||
use vhost_rs::vhost_user::message::{
|
||||
VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures,
|
||||
VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState,
|
||||
VHOST_USER_CONFIG_OFFSET, VHOST_USER_CONFIG_SIZE,
|
||||
};
|
||||
use vhost_rs::vhost_user::{
|
||||
Error as VhostUserError, Result as VhostUserResult, SlaveListener, VhostUserSlaveReqHandler,
|
||||
};
|
||||
use vm_memory::guest_memory::FileOffset;
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
use vm_virtio::Queue;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Errors related to vhost-user daemon.
|
||||
pub enum Error {
|
||||
/// Failed to create a new vhost-user handler.
|
||||
NewVhostUserHandler(VhostUserHandlerError),
|
||||
/// Failed creating vhost-user slave listener.
|
||||
CreateSlaveListener(VhostUserError),
|
||||
/// Failed creating vhost-user slave handler.
|
||||
CreateSlaveReqHandler(VhostUserError),
|
||||
/// Failed starting daemon thread.
|
||||
StartDaemon(io::Error),
|
||||
/// Failed waiting for daemon thread.
|
||||
WaitDaemon(std::boxed::Box<dyn std::any::Any + std::marker::Send>),
|
||||
/// Failed handling a vhost-user request.
|
||||
HandleRequest(VhostUserError),
|
||||
/// Failed to process queue.
|
||||
ProcessQueue(VringEpollHandlerError),
|
||||
/// Failed to register listener.
|
||||
RegisterListener(io::Error),
|
||||
/// Failed to unregister listener.
|
||||
UnregisterListener(io::Error),
|
||||
}
|
||||
|
||||
/// Result of vhost-user daemon operations.
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// This trait must be implemented by the caller in order to provide backend
|
||||
/// specific implementation.
|
||||
pub trait VhostUserBackend: Send + Sync + 'static {
|
||||
/// Number of queues.
|
||||
fn num_queues(&self) -> usize;
|
||||
|
||||
/// Depth of each queue.
|
||||
fn max_queue_size(&self) -> usize;
|
||||
|
||||
/// Virtio features.
|
||||
fn features(&self) -> u64;
|
||||
|
||||
/// Update guest memory regions.
|
||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> result::Result<(), io::Error>;
|
||||
|
||||
/// This function gets called if the backend registered some additional
|
||||
/// listeners onto specific file descriptors. The library can handle
|
||||
/// virtqueues on its own, but does not know what to do with events
|
||||
/// happening on custom listeners.
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
vrings: &[Arc<RwLock<Vring>>],
|
||||
) -> result::Result<bool, io::Error>;
|
||||
|
||||
/// Get virtio device configuration.
|
||||
/// A default implementation is provided as we cannot expect all backends
|
||||
/// to implement this function.
|
||||
fn get_config(&self, _offset: u32, _size: u32) -> Vec<u8> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Set virtio device configuration.
|
||||
/// A default implementation is provided as we cannot expect all backends
|
||||
/// to implement this function.
|
||||
fn set_config(&mut self, _offset: u32, _buf: &[u8]) -> result::Result<(), io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This structure is the public API the backend is allowed to interact with
|
||||
/// in order to run a fully functional vhost-user daemon.
|
||||
pub struct VhostUserDaemon<S: VhostUserBackend> {
|
||||
name: String,
|
||||
sock_path: String,
|
||||
handler: Arc<Mutex<VhostUserHandler<S>>>,
|
||||
main_thread: Option<thread::JoinHandle<Result<()>>>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VhostUserDaemon<S> {
|
||||
/// Create the daemon instance, providing the backend implementation of
|
||||
/// VhostUserBackend.
|
||||
/// Under the hood, this will start a dedicated thread responsible for
|
||||
/// listening onto registered event. Those events can be vring events or
|
||||
/// custom events from the backend, but they get to be registered later
|
||||
/// during the sequence.
|
||||
pub fn new(name: String, sock_path: String, backend: Arc<RwLock<S>>) -> Result<Self> {
|
||||
let handler = Arc::new(Mutex::new(
|
||||
VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?,
|
||||
));
|
||||
|
||||
Ok(VhostUserDaemon {
|
||||
name,
|
||||
sock_path,
|
||||
handler,
|
||||
main_thread: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Connect to the vhost-user socket and run a dedicated thread handling
|
||||
/// all requests coming through this socket. This runs in an infinite loop
|
||||
/// that should be terminating once the other end of the socket (the VMM)
|
||||
/// disconnects.
|
||||
pub fn start(&mut self) -> Result<()> {
|
||||
let mut slave_listener =
|
||||
SlaveListener::new(self.sock_path.as_str(), true, self.handler.clone())
|
||||
.map_err(Error::CreateSlaveListener)?;
|
||||
let mut slave_handler = slave_listener
|
||||
.accept()
|
||||
.map_err(Error::CreateSlaveReqHandler)?
|
||||
.unwrap();
|
||||
let handle = thread::Builder::new()
|
||||
.name(self.name.clone())
|
||||
.spawn(move || loop {
|
||||
slave_handler
|
||||
.handle_request()
|
||||
.map_err(Error::HandleRequest)?;
|
||||
})
|
||||
.map_err(Error::StartDaemon)?;
|
||||
|
||||
self.main_thread = Some(handle);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wait for the thread handling the vhost-user socket connection to
|
||||
/// terminate.
|
||||
pub fn wait(&mut self) -> Result<()> {
|
||||
if let Some(handle) = self.main_thread.take() {
|
||||
let _ = handle.join().map_err(Error::WaitDaemon)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve the vring worker. This is necessary to perform further
|
||||
/// actions like registering and unregistering some extra event file
|
||||
/// descriptors.
|
||||
pub fn get_vring_worker(&self) -> Arc<VringWorker> {
|
||||
self.handler.lock().unwrap().get_vring_worker()
|
||||
}
|
||||
}
|
||||
|
||||
struct AddrMapping {
|
||||
vmm_addr: u64,
|
||||
size: u64,
|
||||
offset: u64,
|
||||
}
|
||||
|
||||
struct Memory {
|
||||
mappings: Vec<AddrMapping>,
|
||||
}
|
||||
|
||||
pub struct Vring {
|
||||
queue: Queue,
|
||||
kick: Option<EventFd>,
|
||||
call: Option<EventFd>,
|
||||
err: Option<EventFd>,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl Vring {
|
||||
fn new(max_queue_size: u16) -> Self {
|
||||
Vring {
|
||||
queue: Queue::new(max_queue_size),
|
||||
kick: None,
|
||||
call: None,
|
||||
err: None,
|
||||
enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mut_queue(&mut self) -> &mut Queue {
|
||||
&mut self.queue
|
||||
}
|
||||
|
||||
pub fn signal_used_queue(&self) -> result::Result<(), io::Error> {
|
||||
if let Some(call) = self.call.as_ref() {
|
||||
return call.write(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Errors related to vring epoll handler.
|
||||
pub enum VringEpollHandlerError {
|
||||
/// Failed to process the queue from the backend.
|
||||
ProcessQueueBackendProcessing(io::Error),
|
||||
/// Failed to signal used queue.
|
||||
SignalUsedQueue(io::Error),
|
||||
/// Failed to read the event from kick EventFd.
|
||||
HandleEventReadKick(io::Error),
|
||||
/// Failed to handle the event from the backend.
|
||||
HandleEventBackendHandling(io::Error),
|
||||
}
|
||||
|
||||
/// Result of vring epoll handler operations.
|
||||
type VringEpollHandlerResult<T> = std::result::Result<T, VringEpollHandlerError>;
|
||||
|
||||
struct VringEpollHandler<S: VhostUserBackend> {
|
||||
backend: Arc<RwLock<S>>,
|
||||
vrings: Vec<Arc<RwLock<Vring>>>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VringEpollHandler<S> {
|
||||
fn handle_event(
|
||||
&self,
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
) -> VringEpollHandlerResult<bool> {
|
||||
let num_queues = self.vrings.len();
|
||||
if (device_event as usize) < num_queues {
|
||||
if let Some(kick) = &self.vrings[device_event as usize].read().unwrap().kick {
|
||||
kick.read()
|
||||
.map_err(VringEpollHandlerError::HandleEventReadKick)?;
|
||||
}
|
||||
|
||||
// If the vring is not enabled, it should not be processed.
|
||||
// The event is only read to be discarded.
|
||||
if !self.vrings[device_event as usize].read().unwrap().enabled {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
self.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.handle_event(device_event, evset, &self.vrings)
|
||||
.map_err(VringEpollHandlerError::HandleEventBackendHandling)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Errors related to vring worker.
|
||||
enum VringWorkerError {
|
||||
/// Failed while waiting for events.
|
||||
EpollWait(io::Error),
|
||||
}
|
||||
|
||||
/// Result of vring worker operations.
|
||||
type VringWorkerResult<T> = std::result::Result<T, VringWorkerError>;
|
||||
|
||||
pub struct VringWorker {
|
||||
epoll_fd: RawFd,
|
||||
}
|
||||
|
||||
impl VringWorker {
|
||||
fn run<S: VhostUserBackend>(&self, handler: VringEpollHandler<S>) -> VringWorkerResult<()> {
|
||||
const EPOLL_EVENTS_LEN: usize = 100;
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events = match epoll::wait(self.epoll_fd, -1, &mut events[..]) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
// It's well defined from the epoll_wait() syscall
|
||||
// documentation that the epoll loop can be interrupted
|
||||
// before any of the requested events occurred or the
|
||||
// timeout expired. In both those cases, epoll_wait()
|
||||
// returns an error of type EINTR, but this should not
|
||||
// be considered as a regular error. Instead it is more
|
||||
// appropriate to retry, by calling into epoll_wait().
|
||||
continue;
|
||||
}
|
||||
return Err(VringWorkerError::EpollWait(e));
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let evset = match epoll::Events::from_bits(event.events) {
|
||||
Some(evset) => evset,
|
||||
None => {
|
||||
let evbits = event.events;
|
||||
println!("epoll: ignoring unknown event set: 0x{:x}", evbits);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let ev_type = event.data as u16;
|
||||
|
||||
if let Err(e) = handler.handle_event(ev_type, evset) {
|
||||
println!(
|
||||
"vring handler handle event {} with error {:?}\n",
|
||||
ev_type, e
|
||||
);
|
||||
break 'epoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Register a custom event only meaningful to the caller. When this event
|
||||
/// is later triggered, and because only the caller knows what to do about
|
||||
/// it, the backend implementation of `handle_event` will be called.
|
||||
/// This lets entire control to the caller about what needs to be done for
|
||||
/// this special event, without forcing it to run its own dedicated epoll
|
||||
/// loop for it.
|
||||
pub fn register_listener(
|
||||
&self,
|
||||
fd: RawFd,
|
||||
ev_type: epoll::Events,
|
||||
data: u64,
|
||||
) -> result::Result<(), io::Error> {
|
||||
epoll::ctl(
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
fd,
|
||||
epoll::Event::new(ev_type, data),
|
||||
)
|
||||
}
|
||||
|
||||
/// Unregister a custom event. If the custom event is triggered after this
|
||||
/// function has been called, nothing will happen as it will be removed
|
||||
/// from the list of file descriptors the epoll loop is listening to.
|
||||
pub fn unregister_listener(
|
||||
&self,
|
||||
fd: RawFd,
|
||||
ev_type: epoll::Events,
|
||||
data: u64,
|
||||
) -> result::Result<(), io::Error> {
|
||||
epoll::ctl(
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_DEL,
|
||||
fd,
|
||||
epoll::Event::new(ev_type, data),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Errors related to vhost-user handler.
|
||||
pub enum VhostUserHandlerError {
|
||||
/// Failed to create epoll file descriptor.
|
||||
EpollCreateFd(io::Error),
|
||||
/// Failed to spawn vring worker.
|
||||
SpawnVringWorker(io::Error),
|
||||
/// Could not find the mapping from memory regions.
|
||||
MissingMemoryMapping,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VhostUserHandlerError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
VhostUserHandlerError::EpollCreateFd(e) => write!(f, "failed creating epoll fd: {}", e),
|
||||
VhostUserHandlerError::SpawnVringWorker(e) => {
|
||||
write!(f, "failed spawning the vring worker: {}", e)
|
||||
}
|
||||
VhostUserHandlerError::MissingMemoryMapping => write!(f, "Missing memory mapping"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for VhostUserHandlerError {}
|
||||
|
||||
/// Result of vhost-user handler operations.
|
||||
type VhostUserHandlerResult<T> = std::result::Result<T, VhostUserHandlerError>;
|
||||
|
||||
struct VhostUserHandler<S: VhostUserBackend> {
|
||||
backend: Arc<RwLock<S>>,
|
||||
worker: Arc<VringWorker>,
|
||||
owned: bool,
|
||||
features_acked: bool,
|
||||
acked_features: u64,
|
||||
acked_protocol_features: u64,
|
||||
num_queues: usize,
|
||||
max_queue_size: usize,
|
||||
memory: Option<Memory>,
|
||||
vrings: Vec<Arc<RwLock<Vring>>>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VhostUserHandler<S> {
|
||||
fn new(backend: Arc<RwLock<S>>) -> VhostUserHandlerResult<Self> {
|
||||
let num_queues = backend.read().unwrap().num_queues();
|
||||
let max_queue_size = backend.read().unwrap().max_queue_size();
|
||||
|
||||
let mut vrings: Vec<Arc<RwLock<Vring>>> = Vec::new();
|
||||
for _ in 0..num_queues {
|
||||
let vring = Arc::new(RwLock::new(Vring::new(max_queue_size as u16)));
|
||||
vrings.push(vring);
|
||||
}
|
||||
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?;
|
||||
|
||||
let vring_handler = VringEpollHandler {
|
||||
backend: backend.clone(),
|
||||
vrings: vrings.clone(),
|
||||
};
|
||||
let vring_worker = Arc::new(VringWorker { epoll_fd });
|
||||
let worker = vring_worker.clone();
|
||||
|
||||
thread::Builder::new()
|
||||
.name("vring_worker".to_string())
|
||||
.spawn(move || vring_worker.run(vring_handler))
|
||||
.map_err(VhostUserHandlerError::SpawnVringWorker)?;
|
||||
|
||||
Ok(VhostUserHandler {
|
||||
backend,
|
||||
worker,
|
||||
owned: false,
|
||||
features_acked: false,
|
||||
acked_features: 0,
|
||||
acked_protocol_features: 0,
|
||||
num_queues,
|
||||
max_queue_size,
|
||||
memory: None,
|
||||
vrings,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_vring_worker(&self) -> Arc<VringWorker> {
|
||||
self.worker.clone()
|
||||
}
|
||||
|
||||
fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult<u64> {
|
||||
if let Some(memory) = &self.memory {
|
||||
for mapping in memory.mappings.iter() {
|
||||
if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size {
|
||||
return Ok(vmm_va - mapping.vmm_addr + mapping.offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(VhostUserHandlerError::MissingMemoryMapping)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
||||
fn set_owner(&mut self) -> VhostUserResult<()> {
|
||||
if self.owned {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
}
|
||||
self.owned = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_owner(&mut self) -> VhostUserResult<()> {
|
||||
self.owned = false;
|
||||
self.features_acked = false;
|
||||
self.acked_features = 0;
|
||||
self.acked_protocol_features = 0;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_features(&mut self) -> VhostUserResult<u64> {
|
||||
Ok(self.backend.read().unwrap().features())
|
||||
}
|
||||
|
||||
fn set_features(&mut self, features: u64) -> VhostUserResult<()> {
|
||||
if !self.owned {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
} else if (features & !self.backend.read().unwrap().features()) != 0 {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
self.acked_features = features;
|
||||
self.features_acked = true;
|
||||
|
||||
// If VHOST_USER_F_PROTOCOL_FEATURES has not been negotiated,
|
||||
// the ring is initialized in an enabled state.
|
||||
// If VHOST_USER_F_PROTOCOL_FEATURES has been negotiated,
|
||||
// the ring is initialized in a disabled state. Client must not
|
||||
// pass data to/from the backend until ring is enabled by
|
||||
// VHOST_USER_SET_VRING_ENABLE with parameter 1, or after it has
|
||||
// been disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0.
|
||||
let vring_enabled =
|
||||
self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0;
|
||||
for vring in self.vrings.iter_mut() {
|
||||
vring.write().unwrap().enabled = vring_enabled;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_protocol_features(&mut self) -> VhostUserResult<VhostUserProtocolFeatures> {
|
||||
Ok(VhostUserProtocolFeatures::all())
|
||||
}
|
||||
|
||||
fn set_protocol_features(&mut self, features: u64) -> VhostUserResult<()> {
|
||||
// Note: slave that reported VHOST_USER_F_PROTOCOL_FEATURES must
|
||||
// support this message even before VHOST_USER_SET_FEATURES was
|
||||
// called.
|
||||
self.acked_protocol_features = features;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_mem_table(
|
||||
&mut self,
|
||||
ctx: &[VhostUserMemoryRegion],
|
||||
fds: &[RawFd],
|
||||
) -> VhostUserResult<()> {
|
||||
// We need to create tuple of ranges from the list of VhostUserMemoryRegion
|
||||
// that we get from the caller.
|
||||
let mut regions: Vec<(GuestAddress, usize, Option<FileOffset>)> = Vec::new();
|
||||
let mut mappings: Vec<AddrMapping> = Vec::new();
|
||||
|
||||
for (idx, region) in ctx.iter().enumerate() {
|
||||
let g_addr = GuestAddress(region.guest_phys_addr);
|
||||
let len = (region.memory_size + region.mmap_offset) as usize;
|
||||
let file = unsafe { File::from_raw_fd(fds[idx]) };
|
||||
let f_off = FileOffset::new(file, 0);
|
||||
|
||||
regions.push((g_addr, len, Some(f_off)));
|
||||
mappings.push(AddrMapping {
|
||||
vmm_addr: region.user_addr,
|
||||
size: region.memory_size,
|
||||
offset: region.mmap_offset,
|
||||
});
|
||||
}
|
||||
|
||||
let mem = GuestMemoryMmap::with_files(regions).map_err(|e| {
|
||||
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||
})?;
|
||||
self.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.update_memory(mem)
|
||||
.map_err(|e| {
|
||||
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||
})?;
|
||||
self.memory = Some(Memory { mappings });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_queue_num(&mut self) -> VhostUserResult<u64> {
|
||||
Ok(self.num_queues as u64)
|
||||
}
|
||||
|
||||
fn set_vring_num(&mut self, index: u32, num: u32) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues || num == 0 || num as usize > self.max_queue_size {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
self.vrings[index as usize].write().unwrap().queue.size = num as u16;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_addr(
|
||||
&mut self,
|
||||
index: u32,
|
||||
_flags: VhostUserVringAddrFlags,
|
||||
descriptor: u64,
|
||||
used: u64,
|
||||
available: u64,
|
||||
_log: u64,
|
||||
) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
if self.memory.is_some() {
|
||||
let desc_table = self.vmm_va_to_gpa(descriptor).map_err(|e| {
|
||||
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||
})?;
|
||||
let avail_ring = self.vmm_va_to_gpa(available).map_err(|e| {
|
||||
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||
})?;
|
||||
let used_ring = self.vmm_va_to_gpa(used).map_err(|e| {
|
||||
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||
})?;
|
||||
self.vrings[index as usize]
|
||||
.write()
|
||||
.unwrap()
|
||||
.queue
|
||||
.desc_table = GuestAddress(desc_table);
|
||||
self.vrings[index as usize]
|
||||
.write()
|
||||
.unwrap()
|
||||
.queue
|
||||
.avail_ring = GuestAddress(avail_ring);
|
||||
self.vrings[index as usize].write().unwrap().queue.used_ring = GuestAddress(used_ring);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(VhostUserError::InvalidParam)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_vring_base(&mut self, index: u32, base: u32) -> VhostUserResult<()> {
|
||||
self.vrings[index as usize]
|
||||
.write()
|
||||
.unwrap()
|
||||
.queue
|
||||
.next_avail = Wrapping(base as u16);
|
||||
self.vrings[index as usize].write().unwrap().queue.next_used = Wrapping(base as u16);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_vring_base(&mut self, index: u32) -> VhostUserResult<VhostUserVringState> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
// Quote from vhost-user specification:
|
||||
// Client must start ring upon receiving a kick (that is, detecting
|
||||
// that file descriptor is readable) on the descriptor specified by
|
||||
// VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
|
||||
// VHOST_USER_GET_VRING_BASE.
|
||||
self.vrings[index as usize].write().unwrap().queue.ready = false;
|
||||
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
|
||||
self.worker
|
||||
.unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index))
|
||||
.map_err(VhostUserError::ReqHandlerError)?;
|
||||
}
|
||||
|
||||
let next_avail = self.vrings[index as usize]
|
||||
.read()
|
||||
.unwrap()
|
||||
.queue
|
||||
.next_avail
|
||||
.0 as u16;
|
||||
|
||||
Ok(VhostUserVringState::new(index, u32::from(next_avail)))
|
||||
}
|
||||
|
||||
fn set_vring_kick(&mut self, index: u8, fd: Option<RawFd>) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
if let Some(kick) = self.vrings[index as usize].write().unwrap().kick.take() {
|
||||
// Close file descriptor set by previous operations.
|
||||
let _ = unsafe { libc::close(kick.as_raw_fd()) };
|
||||
}
|
||||
self.vrings[index as usize].write().unwrap().kick =
|
||||
fd.map(|x| unsafe { EventFd::from_raw_fd(x) });
|
||||
|
||||
// Quote from vhost-user specification:
|
||||
// Client must start ring upon receiving a kick (that is, detecting
|
||||
// that file descriptor is readable) on the descriptor specified by
|
||||
// VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
|
||||
// VHOST_USER_GET_VRING_BASE.
|
||||
self.vrings[index as usize].write().unwrap().queue.ready = true;
|
||||
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
|
||||
self.worker
|
||||
.register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index))
|
||||
.map_err(VhostUserError::ReqHandlerError)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_call(&mut self, index: u8, fd: Option<RawFd>) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
if let Some(call) = self.vrings[index as usize].write().unwrap().call.take() {
|
||||
// Close file descriptor set by previous operations.
|
||||
let _ = unsafe { libc::close(call.as_raw_fd()) };
|
||||
}
|
||||
self.vrings[index as usize].write().unwrap().call =
|
||||
fd.map(|x| unsafe { EventFd::from_raw_fd(x) });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_err(&mut self, index: u8, fd: Option<RawFd>) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
if let Some(err) = self.vrings[index as usize].write().unwrap().err.take() {
|
||||
// Close file descriptor set by previous operations.
|
||||
let _ = unsafe { libc::close(err.as_raw_fd()) };
|
||||
}
|
||||
self.vrings[index as usize].write().unwrap().err =
|
||||
fd.map(|x| unsafe { EventFd::from_raw_fd(x) });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_enable(&mut self, index: u32, enable: bool) -> VhostUserResult<()> {
|
||||
// This request should be handled only when VHOST_USER_F_PROTOCOL_FEATURES
|
||||
// has been negotiated.
|
||||
if self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0 {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
} else if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
// Slave must not pass data to/from the backend until ring is
|
||||
// enabled by VHOST_USER_SET_VRING_ENABLE with parameter 1,
|
||||
// or after it has been disabled by VHOST_USER_SET_VRING_ENABLE
|
||||
// with parameter 0.
|
||||
self.vrings[index as usize].write().unwrap().enabled = enable;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_config(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
_flags: VhostUserConfigFlags,
|
||||
) -> VhostUserResult<Vec<u8>> {
|
||||
if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
} else if offset < VHOST_USER_CONFIG_OFFSET
|
||||
|| offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
|
||||
|| size + offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
Ok(self.backend.read().unwrap().get_config(offset, size))
|
||||
}
|
||||
|
||||
fn set_config(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
buf: &[u8],
|
||||
_flags: VhostUserConfigFlags,
|
||||
) -> VhostUserResult<()> {
|
||||
let size = buf.len() as u32;
|
||||
if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
} else if offset < VHOST_USER_CONFIG_OFFSET
|
||||
|| offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
|
||||
|| size + offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
self.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_config(offset, buf)
|
||||
.map_err(VhostUserError::ReqHandlerError)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
[package]
|
||||
name = "virtio-bindings"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
@@ -1,31 +0,0 @@
|
||||
From 67247b7abc56a11d8cae7eb354994d818fb72e93 Mon Sep 17 00:00:00 2001
|
||||
From: Andreea Florescu <fandree@amazon.com>
|
||||
Date: Tue, 15 Jan 2019 18:30:07 +0200
|
||||
Subject: [PATCH] virtio_gen: remove derive Debug from packed struct
|
||||
|
||||
Bindgen automatically adds derive debug on virtio_net_ctrl_mac, a packed
|
||||
structure. This generates a warning while building.
|
||||
|
||||
Manually remove the Debug derive.
|
||||
|
||||
Signed-off-by: Andreea Florescu <fandree@amazon.com>
|
||||
---
|
||||
virtio_gen/src/virtio_net.rs | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/virtio_gen/src/virtio_net.rs b/virtio_gen/src/virtio_net.rs
|
||||
index 0b68d09..a1c9dca 100644
|
||||
--- a/virtio_gen/src/virtio_net.rs
|
||||
+++ b/virtio_gen/src/virtio_net.rs
|
||||
@@ -681,7 +681,7 @@ fn bindgen_test_layout_virtio_net_ctrl_hdr() {
|
||||
}
|
||||
pub type virtio_net_ctrl_ack = __u8;
|
||||
#[repr(C, packed)]
|
||||
-#[derive(Debug, Default)]
|
||||
+#[derive(Default)]
|
||||
pub struct virtio_net_ctrl_mac {
|
||||
pub entries: __virtio32,
|
||||
pub macs: __IncompleteArrayField<[__u8; 6usize]>,
|
||||
--
|
||||
2.7.4
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
pub mod virtio_blk;
|
||||
pub mod virtio_net;
|
||||
pub mod virtio_ring;
|
||||
@@ -1,486 +0,0 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* automatically generated by rust-bindgen */
|
||||
|
||||
pub const __BITS_PER_LONG: u32 = 64;
|
||||
pub const __FD_SETSIZE: u32 = 1024;
|
||||
pub const VIRTIO_ID_NET: u32 = 1;
|
||||
pub const VIRTIO_ID_BLOCK: u32 = 2;
|
||||
pub const VIRTIO_ID_CONSOLE: u32 = 3;
|
||||
pub const VIRTIO_ID_RNG: u32 = 4;
|
||||
pub const VIRTIO_ID_BALLOON: u32 = 5;
|
||||
pub const VIRTIO_ID_RPMSG: u32 = 7;
|
||||
pub const VIRTIO_ID_SCSI: u32 = 8;
|
||||
pub const VIRTIO_ID_9P: u32 = 9;
|
||||
pub const VIRTIO_ID_RPROC_SERIAL: u32 = 11;
|
||||
pub const VIRTIO_ID_CAIF: u32 = 12;
|
||||
pub const VIRTIO_ID_GPU: u32 = 16;
|
||||
pub const VIRTIO_ID_INPUT: u32 = 18;
|
||||
pub const VIRTIO_CONFIG_S_ACKNOWLEDGE: u32 = 1;
|
||||
pub const VIRTIO_CONFIG_S_DRIVER: u32 = 2;
|
||||
pub const VIRTIO_CONFIG_S_DRIVER_OK: u32 = 4;
|
||||
pub const VIRTIO_CONFIG_S_FEATURES_OK: u32 = 8;
|
||||
pub const VIRTIO_CONFIG_S_FAILED: u32 = 128;
|
||||
pub const VIRTIO_TRANSPORT_F_START: u32 = 28;
|
||||
pub const VIRTIO_TRANSPORT_F_END: u32 = 33;
|
||||
pub const VIRTIO_F_NOTIFY_ON_EMPTY: u32 = 24;
|
||||
pub const VIRTIO_F_ANY_LAYOUT: u32 = 27;
|
||||
pub const VIRTIO_F_VERSION_1: u32 = 32;
|
||||
pub const VIRTIO_BLK_F_SIZE_MAX: u32 = 1;
|
||||
pub const VIRTIO_BLK_F_SEG_MAX: u32 = 2;
|
||||
pub const VIRTIO_BLK_F_GEOMETRY: u32 = 4;
|
||||
pub const VIRTIO_BLK_F_RO: u32 = 5;
|
||||
pub const VIRTIO_BLK_F_BLK_SIZE: u32 = 6;
|
||||
pub const VIRTIO_BLK_F_TOPOLOGY: u32 = 10;
|
||||
pub const VIRTIO_BLK_F_MQ: u32 = 12;
|
||||
pub const VIRTIO_BLK_F_BARRIER: u32 = 0;
|
||||
pub const VIRTIO_BLK_F_SCSI: u32 = 7;
|
||||
pub const VIRTIO_BLK_F_FLUSH: u32 = 9;
|
||||
pub const VIRTIO_BLK_F_CONFIG_WCE: u32 = 11;
|
||||
pub const VIRTIO_BLK_F_WCE: u32 = 9;
|
||||
pub const VIRTIO_BLK_ID_BYTES: u32 = 20;
|
||||
pub const VIRTIO_BLK_T_IN: u32 = 0;
|
||||
pub const VIRTIO_BLK_T_OUT: u32 = 1;
|
||||
pub const VIRTIO_BLK_T_SCSI_CMD: u32 = 2;
|
||||
pub const VIRTIO_BLK_T_FLUSH: u32 = 4;
|
||||
pub const VIRTIO_BLK_T_GET_ID: u32 = 8;
|
||||
pub const VIRTIO_BLK_T_BARRIER: u32 = 2147483648;
|
||||
pub const VIRTIO_BLK_S_OK: u32 = 0;
|
||||
pub const VIRTIO_BLK_S_IOERR: u32 = 1;
|
||||
pub const VIRTIO_BLK_S_UNSUPP: u32 = 2;
|
||||
pub type __s8 = ::std::os::raw::c_schar;
|
||||
pub type __u8 = ::std::os::raw::c_uchar;
|
||||
pub type __s16 = ::std::os::raw::c_short;
|
||||
pub type __u16 = ::std::os::raw::c_ushort;
|
||||
pub type __s32 = ::std::os::raw::c_int;
|
||||
pub type __u32 = ::std::os::raw::c_uint;
|
||||
pub type __s64 = ::std::os::raw::c_longlong;
|
||||
pub type __u64 = ::std::os::raw::c_ulonglong;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct __kernel_fd_set {
|
||||
pub fds_bits: [::std::os::raw::c_ulong; 16usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout___kernel_fd_set() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<__kernel_fd_set>(),
|
||||
128usize,
|
||||
concat!("Size of: ", stringify!(__kernel_fd_set))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<__kernel_fd_set>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(__kernel_fd_set))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<__kernel_fd_set>())).fds_bits as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(__kernel_fd_set),
|
||||
"::",
|
||||
stringify!(fds_bits)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type __kernel_sighandler_t =
|
||||
::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
|
||||
pub type __kernel_key_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_mqd_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
|
||||
pub type __kernel_long_t = ::std::os::raw::c_long;
|
||||
pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
|
||||
pub type __kernel_ino_t = __kernel_ulong_t;
|
||||
pub type __kernel_mode_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_pid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_uid_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_gid_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_suseconds_t = __kernel_long_t;
|
||||
pub type __kernel_daddr_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_uid32_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_gid32_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_size_t = __kernel_ulong_t;
|
||||
pub type __kernel_ssize_t = __kernel_long_t;
|
||||
pub type __kernel_ptrdiff_t = __kernel_long_t;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct __kernel_fsid_t {
|
||||
pub val: [::std::os::raw::c_int; 2usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout___kernel_fsid_t() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<__kernel_fsid_t>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(__kernel_fsid_t))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<__kernel_fsid_t>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(__kernel_fsid_t))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<__kernel_fsid_t>())).val as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(__kernel_fsid_t),
|
||||
"::",
|
||||
stringify!(val)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type __kernel_off_t = __kernel_long_t;
|
||||
pub type __kernel_loff_t = ::std::os::raw::c_longlong;
|
||||
pub type __kernel_time_t = __kernel_long_t;
|
||||
pub type __kernel_clock_t = __kernel_long_t;
|
||||
pub type __kernel_timer_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_clockid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
|
||||
pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
|
||||
pub type __le16 = __u16;
|
||||
pub type __be16 = __u16;
|
||||
pub type __le32 = __u32;
|
||||
pub type __be32 = __u32;
|
||||
pub type __le64 = __u64;
|
||||
pub type __be64 = __u64;
|
||||
pub type __sum16 = __u16;
|
||||
pub type __wsum = __u32;
|
||||
pub type __virtio16 = __u16;
|
||||
pub type __virtio32 = __u32;
|
||||
pub type __virtio64 = __u64;
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_blk_config {
|
||||
pub capacity: __u64,
|
||||
pub size_max: __u32,
|
||||
pub seg_max: __u32,
|
||||
pub geometry: virtio_blk_config_virtio_blk_geometry,
|
||||
pub blk_size: __u32,
|
||||
pub physical_block_exp: __u8,
|
||||
pub alignment_offset: __u8,
|
||||
pub min_io_size: __u16,
|
||||
pub opt_io_size: __u32,
|
||||
pub wce: __u8,
|
||||
pub unused: __u8,
|
||||
pub num_queues: __u16,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_blk_config_virtio_blk_geometry {
|
||||
pub cylinders: __u16,
|
||||
pub heads: __u8,
|
||||
pub sectors: __u8,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_blk_config_virtio_blk_geometry() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_blk_config_virtio_blk_geometry>(),
|
||||
4usize,
|
||||
concat!(
|
||||
"Size of: ",
|
||||
stringify!(virtio_blk_config_virtio_blk_geometry)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_blk_config_virtio_blk_geometry>(),
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of ",
|
||||
stringify!(virtio_blk_config_virtio_blk_geometry)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_blk_config_virtio_blk_geometry>())).cylinders as *const _
|
||||
as usize
|
||||
},
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config_virtio_blk_geometry),
|
||||
"::",
|
||||
stringify!(cylinders)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_blk_config_virtio_blk_geometry>())).heads as *const _
|
||||
as usize
|
||||
},
|
||||
2usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config_virtio_blk_geometry),
|
||||
"::",
|
||||
stringify!(heads)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_blk_config_virtio_blk_geometry>())).sectors as *const _
|
||||
as usize
|
||||
},
|
||||
3usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config_virtio_blk_geometry),
|
||||
"::",
|
||||
stringify!(sectors)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_blk_config() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_blk_config>(),
|
||||
36usize,
|
||||
concat!("Size of: ", stringify!(virtio_blk_config))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_blk_config>(),
|
||||
1usize,
|
||||
concat!("Alignment of ", stringify!(virtio_blk_config))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).capacity as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(capacity)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).size_max as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(size_max)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).seg_max as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(seg_max)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).geometry as *const _ as usize },
|
||||
16usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(geometry)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).blk_size as *const _ as usize },
|
||||
20usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(blk_size)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_blk_config>())).physical_block_exp as *const _ as usize
|
||||
},
|
||||
24usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(physical_block_exp)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_blk_config>())).alignment_offset as *const _ as usize
|
||||
},
|
||||
25usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(alignment_offset)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).min_io_size as *const _ as usize },
|
||||
26usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(min_io_size)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).opt_io_size as *const _ as usize },
|
||||
28usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(opt_io_size)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).wce as *const _ as usize },
|
||||
32usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(wce)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).unused as *const _ as usize },
|
||||
33usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(unused)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).num_queues as *const _ as usize },
|
||||
34usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_config),
|
||||
"::",
|
||||
stringify!(num_queues)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_blk_outhdr {
|
||||
pub type_: __virtio32,
|
||||
pub ioprio: __virtio32,
|
||||
pub sector: __virtio64,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_blk_outhdr() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_blk_outhdr>(),
|
||||
16usize,
|
||||
concat!("Size of: ", stringify!(virtio_blk_outhdr))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_blk_outhdr>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(virtio_blk_outhdr))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_outhdr>())).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_outhdr),
|
||||
"::",
|
||||
stringify!(type_)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_outhdr>())).ioprio as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_outhdr),
|
||||
"::",
|
||||
stringify!(ioprio)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_blk_outhdr>())).sector as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_blk_outhdr),
|
||||
"::",
|
||||
stringify!(sector)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_scsi_inhdr {
|
||||
pub errors: __virtio32,
|
||||
pub data_len: __virtio32,
|
||||
pub sense_len: __virtio32,
|
||||
pub residual: __virtio32,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_scsi_inhdr() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_scsi_inhdr>(),
|
||||
16usize,
|
||||
concat!("Size of: ", stringify!(virtio_scsi_inhdr))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_scsi_inhdr>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(virtio_scsi_inhdr))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_scsi_inhdr>())).errors as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_scsi_inhdr),
|
||||
"::",
|
||||
stringify!(errors)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_scsi_inhdr>())).data_len as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_scsi_inhdr),
|
||||
"::",
|
||||
stringify!(data_len)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_scsi_inhdr>())).sense_len as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_scsi_inhdr),
|
||||
"::",
|
||||
stringify!(sense_len)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_scsi_inhdr>())).residual as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_scsi_inhdr),
|
||||
"::",
|
||||
stringify!(residual)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,734 +0,0 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* automatically generated by rust-bindgen */
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
|
||||
impl<T> __IncompleteArrayField<T> {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
__IncompleteArrayField(::std::marker::PhantomData, [])
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_ptr(&self) -> *const T {
|
||||
::std::mem::transmute(self)
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
|
||||
::std::mem::transmute(self)
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
|
||||
::std::slice::from_raw_parts(self.as_ptr(), len)
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
|
||||
::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
|
||||
}
|
||||
}
|
||||
impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
|
||||
fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
fmt.write_str("__IncompleteArrayField")
|
||||
}
|
||||
}
|
||||
impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
pub const __BITS_PER_LONG: u32 = 64;
|
||||
pub const __FD_SETSIZE: u32 = 1024;
|
||||
pub const VIRTIO_ID_NET: u32 = 1;
|
||||
pub const VIRTIO_ID_BLOCK: u32 = 2;
|
||||
pub const VIRTIO_ID_CONSOLE: u32 = 3;
|
||||
pub const VIRTIO_ID_RNG: u32 = 4;
|
||||
pub const VIRTIO_ID_BALLOON: u32 = 5;
|
||||
pub const VIRTIO_ID_RPMSG: u32 = 7;
|
||||
pub const VIRTIO_ID_SCSI: u32 = 8;
|
||||
pub const VIRTIO_ID_9P: u32 = 9;
|
||||
pub const VIRTIO_ID_RPROC_SERIAL: u32 = 11;
|
||||
pub const VIRTIO_ID_CAIF: u32 = 12;
|
||||
pub const VIRTIO_ID_GPU: u32 = 16;
|
||||
pub const VIRTIO_ID_INPUT: u32 = 18;
|
||||
pub const VIRTIO_CONFIG_S_ACKNOWLEDGE: u32 = 1;
|
||||
pub const VIRTIO_CONFIG_S_DRIVER: u32 = 2;
|
||||
pub const VIRTIO_CONFIG_S_DRIVER_OK: u32 = 4;
|
||||
pub const VIRTIO_CONFIG_S_FEATURES_OK: u32 = 8;
|
||||
pub const VIRTIO_CONFIG_S_FAILED: u32 = 128;
|
||||
pub const VIRTIO_TRANSPORT_F_START: u32 = 28;
|
||||
pub const VIRTIO_TRANSPORT_F_END: u32 = 33;
|
||||
pub const VIRTIO_F_NOTIFY_ON_EMPTY: u32 = 24;
|
||||
pub const VIRTIO_F_ANY_LAYOUT: u32 = 27;
|
||||
pub const VIRTIO_F_VERSION_1: u32 = 32;
|
||||
pub const ETH_ALEN: u32 = 6;
|
||||
pub const ETH_TLEN: u32 = 2;
|
||||
pub const ETH_HLEN: u32 = 14;
|
||||
pub const ETH_ZLEN: u32 = 60;
|
||||
pub const ETH_DATA_LEN: u32 = 1500;
|
||||
pub const ETH_FRAME_LEN: u32 = 1514;
|
||||
pub const ETH_FCS_LEN: u32 = 4;
|
||||
pub const ETH_P_LOOP: u32 = 96;
|
||||
pub const ETH_P_PUP: u32 = 512;
|
||||
pub const ETH_P_PUPAT: u32 = 513;
|
||||
pub const ETH_P_TSN: u32 = 8944;
|
||||
pub const ETH_P_IP: u32 = 2048;
|
||||
pub const ETH_P_X25: u32 = 2053;
|
||||
pub const ETH_P_ARP: u32 = 2054;
|
||||
pub const ETH_P_BPQ: u32 = 2303;
|
||||
pub const ETH_P_IEEEPUP: u32 = 2560;
|
||||
pub const ETH_P_IEEEPUPAT: u32 = 2561;
|
||||
pub const ETH_P_BATMAN: u32 = 17157;
|
||||
pub const ETH_P_DEC: u32 = 24576;
|
||||
pub const ETH_P_DNA_DL: u32 = 24577;
|
||||
pub const ETH_P_DNA_RC: u32 = 24578;
|
||||
pub const ETH_P_DNA_RT: u32 = 24579;
|
||||
pub const ETH_P_LAT: u32 = 24580;
|
||||
pub const ETH_P_DIAG: u32 = 24581;
|
||||
pub const ETH_P_CUST: u32 = 24582;
|
||||
pub const ETH_P_SCA: u32 = 24583;
|
||||
pub const ETH_P_TEB: u32 = 25944;
|
||||
pub const ETH_P_RARP: u32 = 32821;
|
||||
pub const ETH_P_ATALK: u32 = 32923;
|
||||
pub const ETH_P_AARP: u32 = 33011;
|
||||
pub const ETH_P_8021Q: u32 = 33024;
|
||||
pub const ETH_P_IPX: u32 = 33079;
|
||||
pub const ETH_P_IPV6: u32 = 34525;
|
||||
pub const ETH_P_PAUSE: u32 = 34824;
|
||||
pub const ETH_P_SLOW: u32 = 34825;
|
||||
pub const ETH_P_WCCP: u32 = 34878;
|
||||
pub const ETH_P_MPLS_UC: u32 = 34887;
|
||||
pub const ETH_P_MPLS_MC: u32 = 34888;
|
||||
pub const ETH_P_ATMMPOA: u32 = 34892;
|
||||
pub const ETH_P_PPP_DISC: u32 = 34915;
|
||||
pub const ETH_P_PPP_SES: u32 = 34916;
|
||||
pub const ETH_P_LINK_CTL: u32 = 34924;
|
||||
pub const ETH_P_ATMFATE: u32 = 34948;
|
||||
pub const ETH_P_PAE: u32 = 34958;
|
||||
pub const ETH_P_AOE: u32 = 34978;
|
||||
pub const ETH_P_8021AD: u32 = 34984;
|
||||
pub const ETH_P_802_EX1: u32 = 34997;
|
||||
pub const ETH_P_TIPC: u32 = 35018;
|
||||
pub const ETH_P_8021AH: u32 = 35047;
|
||||
pub const ETH_P_MVRP: u32 = 35061;
|
||||
pub const ETH_P_1588: u32 = 35063;
|
||||
pub const ETH_P_PRP: u32 = 35067;
|
||||
pub const ETH_P_FCOE: u32 = 35078;
|
||||
pub const ETH_P_TDLS: u32 = 35085;
|
||||
pub const ETH_P_FIP: u32 = 35092;
|
||||
pub const ETH_P_80221: u32 = 35095;
|
||||
pub const ETH_P_LOOPBACK: u32 = 36864;
|
||||
pub const ETH_P_QINQ1: u32 = 37120;
|
||||
pub const ETH_P_QINQ2: u32 = 37376;
|
||||
pub const ETH_P_QINQ3: u32 = 37632;
|
||||
pub const ETH_P_EDSA: u32 = 56026;
|
||||
pub const ETH_P_AF_IUCV: u32 = 64507;
|
||||
pub const ETH_P_802_3_MIN: u32 = 1536;
|
||||
pub const ETH_P_802_3: u32 = 1;
|
||||
pub const ETH_P_AX25: u32 = 2;
|
||||
pub const ETH_P_ALL: u32 = 3;
|
||||
pub const ETH_P_802_2: u32 = 4;
|
||||
pub const ETH_P_SNAP: u32 = 5;
|
||||
pub const ETH_P_DDCMP: u32 = 6;
|
||||
pub const ETH_P_WAN_PPP: u32 = 7;
|
||||
pub const ETH_P_PPP_MP: u32 = 8;
|
||||
pub const ETH_P_LOCALTALK: u32 = 9;
|
||||
pub const ETH_P_CAN: u32 = 12;
|
||||
pub const ETH_P_CANFD: u32 = 13;
|
||||
pub const ETH_P_PPPTALK: u32 = 16;
|
||||
pub const ETH_P_TR_802_2: u32 = 17;
|
||||
pub const ETH_P_MOBITEX: u32 = 21;
|
||||
pub const ETH_P_CONTROL: u32 = 22;
|
||||
pub const ETH_P_IRDA: u32 = 23;
|
||||
pub const ETH_P_ECONET: u32 = 24;
|
||||
pub const ETH_P_HDLC: u32 = 25;
|
||||
pub const ETH_P_ARCNET: u32 = 26;
|
||||
pub const ETH_P_DSA: u32 = 27;
|
||||
pub const ETH_P_TRAILER: u32 = 28;
|
||||
pub const ETH_P_PHONET: u32 = 245;
|
||||
pub const ETH_P_IEEE802154: u32 = 246;
|
||||
pub const ETH_P_CAIF: u32 = 247;
|
||||
pub const ETH_P_XDSA: u32 = 248;
|
||||
pub const VIRTIO_NET_F_CSUM: u32 = 0;
|
||||
pub const VIRTIO_NET_F_GUEST_CSUM: u32 = 1;
|
||||
pub const VIRTIO_NET_F_CTRL_GUEST_OFFLOADS: u32 = 2;
|
||||
pub const VIRTIO_NET_F_MTU: u32 = 3;
|
||||
pub const VIRTIO_NET_F_MAC: u32 = 5;
|
||||
pub const VIRTIO_NET_F_GUEST_TSO4: u32 = 7;
|
||||
pub const VIRTIO_NET_F_GUEST_TSO6: u32 = 8;
|
||||
pub const VIRTIO_NET_F_GUEST_ECN: u32 = 9;
|
||||
pub const VIRTIO_NET_F_GUEST_UFO: u32 = 10;
|
||||
pub const VIRTIO_NET_F_HOST_TSO4: u32 = 11;
|
||||
pub const VIRTIO_NET_F_HOST_TSO6: u32 = 12;
|
||||
pub const VIRTIO_NET_F_HOST_ECN: u32 = 13;
|
||||
pub const VIRTIO_NET_F_HOST_UFO: u32 = 14;
|
||||
pub const VIRTIO_NET_F_MRG_RXBUF: u32 = 15;
|
||||
pub const VIRTIO_NET_F_STATUS: u32 = 16;
|
||||
pub const VIRTIO_NET_F_CTRL_VQ: u32 = 17;
|
||||
pub const VIRTIO_NET_F_CTRL_RX: u32 = 18;
|
||||
pub const VIRTIO_NET_F_CTRL_VLAN: u32 = 19;
|
||||
pub const VIRTIO_NET_F_CTRL_RX_EXTRA: u32 = 20;
|
||||
pub const VIRTIO_NET_F_GUEST_ANNOUNCE: u32 = 21;
|
||||
pub const VIRTIO_NET_F_MQ: u32 = 22;
|
||||
pub const VIRTIO_NET_F_CTRL_MAC_ADDR: u32 = 23;
|
||||
pub const VIRTIO_NET_F_GSO: u32 = 6;
|
||||
pub const VIRTIO_NET_S_LINK_UP: u32 = 1;
|
||||
pub const VIRTIO_NET_S_ANNOUNCE: u32 = 2;
|
||||
pub const VIRTIO_NET_HDR_F_NEEDS_CSUM: u32 = 1;
|
||||
pub const VIRTIO_NET_HDR_F_DATA_VALID: u32 = 2;
|
||||
pub const VIRTIO_NET_HDR_GSO_NONE: u32 = 0;
|
||||
pub const VIRTIO_NET_HDR_GSO_TCPV4: u32 = 1;
|
||||
pub const VIRTIO_NET_HDR_GSO_UDP: u32 = 3;
|
||||
pub const VIRTIO_NET_HDR_GSO_TCPV6: u32 = 4;
|
||||
pub const VIRTIO_NET_HDR_GSO_ECN: u32 = 128;
|
||||
pub const VIRTIO_NET_OK: u32 = 0;
|
||||
pub const VIRTIO_NET_ERR: u32 = 1;
|
||||
pub const VIRTIO_NET_CTRL_RX: u32 = 0;
|
||||
pub const VIRTIO_NET_CTRL_RX_PROMISC: u32 = 0;
|
||||
pub const VIRTIO_NET_CTRL_RX_ALLMULTI: u32 = 1;
|
||||
pub const VIRTIO_NET_CTRL_RX_ALLUNI: u32 = 2;
|
||||
pub const VIRTIO_NET_CTRL_RX_NOMULTI: u32 = 3;
|
||||
pub const VIRTIO_NET_CTRL_RX_NOUNI: u32 = 4;
|
||||
pub const VIRTIO_NET_CTRL_RX_NOBCAST: u32 = 5;
|
||||
pub const VIRTIO_NET_CTRL_MAC: u32 = 1;
|
||||
pub const VIRTIO_NET_CTRL_MAC_TABLE_SET: u32 = 0;
|
||||
pub const VIRTIO_NET_CTRL_MAC_ADDR_SET: u32 = 1;
|
||||
pub const VIRTIO_NET_CTRL_VLAN: u32 = 2;
|
||||
pub const VIRTIO_NET_CTRL_VLAN_ADD: u32 = 0;
|
||||
pub const VIRTIO_NET_CTRL_VLAN_DEL: u32 = 1;
|
||||
pub const VIRTIO_NET_CTRL_ANNOUNCE: u32 = 3;
|
||||
pub const VIRTIO_NET_CTRL_ANNOUNCE_ACK: u32 = 0;
|
||||
pub const VIRTIO_NET_CTRL_MQ: u32 = 4;
|
||||
pub const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET: u32 = 0;
|
||||
pub const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN: u32 = 1;
|
||||
pub const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX: u32 = 32768;
|
||||
pub const VIRTIO_NET_CTRL_GUEST_OFFLOADS: u32 = 5;
|
||||
pub const VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET: u32 = 0;
|
||||
pub type __s8 = ::std::os::raw::c_schar;
|
||||
pub type __u8 = ::std::os::raw::c_uchar;
|
||||
pub type __s16 = ::std::os::raw::c_short;
|
||||
pub type __u16 = ::std::os::raw::c_ushort;
|
||||
pub type __s32 = ::std::os::raw::c_int;
|
||||
pub type __u32 = ::std::os::raw::c_uint;
|
||||
pub type __s64 = ::std::os::raw::c_longlong;
|
||||
pub type __u64 = ::std::os::raw::c_ulonglong;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct __kernel_fd_set {
|
||||
pub fds_bits: [::std::os::raw::c_ulong; 16usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout___kernel_fd_set() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<__kernel_fd_set>(),
|
||||
128usize,
|
||||
concat!("Size of: ", stringify!(__kernel_fd_set))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<__kernel_fd_set>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(__kernel_fd_set))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<__kernel_fd_set>())).fds_bits as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(__kernel_fd_set),
|
||||
"::",
|
||||
stringify!(fds_bits)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type __kernel_sighandler_t =
|
||||
::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
|
||||
pub type __kernel_key_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_mqd_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
|
||||
pub type __kernel_long_t = ::std::os::raw::c_long;
|
||||
pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
|
||||
pub type __kernel_ino_t = __kernel_ulong_t;
|
||||
pub type __kernel_mode_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_pid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_uid_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_gid_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_suseconds_t = __kernel_long_t;
|
||||
pub type __kernel_daddr_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_uid32_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_gid32_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_size_t = __kernel_ulong_t;
|
||||
pub type __kernel_ssize_t = __kernel_long_t;
|
||||
pub type __kernel_ptrdiff_t = __kernel_long_t;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct __kernel_fsid_t {
|
||||
pub val: [::std::os::raw::c_int; 2usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout___kernel_fsid_t() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<__kernel_fsid_t>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(__kernel_fsid_t))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<__kernel_fsid_t>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(__kernel_fsid_t))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<__kernel_fsid_t>())).val as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(__kernel_fsid_t),
|
||||
"::",
|
||||
stringify!(val)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type __kernel_off_t = __kernel_long_t;
|
||||
pub type __kernel_loff_t = ::std::os::raw::c_longlong;
|
||||
pub type __kernel_time_t = __kernel_long_t;
|
||||
pub type __kernel_clock_t = __kernel_long_t;
|
||||
pub type __kernel_timer_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_clockid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
|
||||
pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
|
||||
pub type __le16 = __u16;
|
||||
pub type __be16 = __u16;
|
||||
pub type __le32 = __u32;
|
||||
pub type __be32 = __u32;
|
||||
pub type __le64 = __u64;
|
||||
pub type __be64 = __u64;
|
||||
pub type __sum16 = __u16;
|
||||
pub type __wsum = __u32;
|
||||
pub type __virtio16 = __u16;
|
||||
pub type __virtio32 = __u32;
|
||||
pub type __virtio64 = __u64;
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct ethhdr {
|
||||
pub h_dest: [::std::os::raw::c_uchar; 6usize],
|
||||
pub h_source: [::std::os::raw::c_uchar; 6usize],
|
||||
pub h_proto: __be16,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_ethhdr() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<ethhdr>(),
|
||||
14usize,
|
||||
concat!("Size of: ", stringify!(ethhdr))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<ethhdr>(),
|
||||
1usize,
|
||||
concat!("Alignment of ", stringify!(ethhdr))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<ethhdr>())).h_dest as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(ethhdr),
|
||||
"::",
|
||||
stringify!(h_dest)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<ethhdr>())).h_source as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(ethhdr),
|
||||
"::",
|
||||
stringify!(h_source)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<ethhdr>())).h_proto as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(ethhdr),
|
||||
"::",
|
||||
stringify!(h_proto)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_net_config {
|
||||
pub mac: [__u8; 6usize],
|
||||
pub status: __u16,
|
||||
pub max_virtqueue_pairs: __u16,
|
||||
pub mtu: __u16,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_net_config() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_net_config>(),
|
||||
12usize,
|
||||
concat!("Size of: ", stringify!(virtio_net_config))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_net_config>(),
|
||||
1usize,
|
||||
concat!("Alignment of ", stringify!(virtio_net_config))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_config>())).mac as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_config),
|
||||
"::",
|
||||
stringify!(mac)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_config>())).status as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_config),
|
||||
"::",
|
||||
stringify!(status)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_net_config>())).max_virtqueue_pairs as *const _ as usize
|
||||
},
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_config),
|
||||
"::",
|
||||
stringify!(max_virtqueue_pairs)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_config>())).mtu as *const _ as usize },
|
||||
10usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_config),
|
||||
"::",
|
||||
stringify!(mtu)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_net_hdr_v1 {
|
||||
pub flags: __u8,
|
||||
pub gso_type: __u8,
|
||||
pub hdr_len: __virtio16,
|
||||
pub gso_size: __virtio16,
|
||||
pub csum_start: __virtio16,
|
||||
pub csum_offset: __virtio16,
|
||||
pub num_buffers: __virtio16,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_net_hdr_v1() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_net_hdr_v1>(),
|
||||
12usize,
|
||||
concat!("Size of: ", stringify!(virtio_net_hdr_v1))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_net_hdr_v1>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(virtio_net_hdr_v1))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).flags as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_v1),
|
||||
"::",
|
||||
stringify!(flags)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).gso_type as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_v1),
|
||||
"::",
|
||||
stringify!(gso_type)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).hdr_len as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_v1),
|
||||
"::",
|
||||
stringify!(hdr_len)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).gso_size as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_v1),
|
||||
"::",
|
||||
stringify!(gso_size)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).csum_start as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_v1),
|
||||
"::",
|
||||
stringify!(csum_start)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).csum_offset as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_v1),
|
||||
"::",
|
||||
stringify!(csum_offset)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).num_buffers as *const _ as usize },
|
||||
10usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_v1),
|
||||
"::",
|
||||
stringify!(num_buffers)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_net_hdr {
|
||||
pub flags: __u8,
|
||||
pub gso_type: __u8,
|
||||
pub hdr_len: __virtio16,
|
||||
pub gso_size: __virtio16,
|
||||
pub csum_start: __virtio16,
|
||||
pub csum_offset: __virtio16,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_net_hdr() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_net_hdr>(),
|
||||
10usize,
|
||||
concat!("Size of: ", stringify!(virtio_net_hdr))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_net_hdr>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(virtio_net_hdr))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).flags as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr),
|
||||
"::",
|
||||
stringify!(flags)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).gso_type as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr),
|
||||
"::",
|
||||
stringify!(gso_type)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).hdr_len as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr),
|
||||
"::",
|
||||
stringify!(hdr_len)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).gso_size as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr),
|
||||
"::",
|
||||
stringify!(gso_size)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).csum_start as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr),
|
||||
"::",
|
||||
stringify!(csum_start)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).csum_offset as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr),
|
||||
"::",
|
||||
stringify!(csum_offset)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_net_hdr_mrg_rxbuf {
|
||||
pub hdr: virtio_net_hdr,
|
||||
pub num_buffers: __virtio16,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_net_hdr_mrg_rxbuf() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_net_hdr_mrg_rxbuf>(),
|
||||
12usize,
|
||||
concat!("Size of: ", stringify!(virtio_net_hdr_mrg_rxbuf))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_net_hdr_mrg_rxbuf>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(virtio_net_hdr_mrg_rxbuf))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_mrg_rxbuf>())).hdr as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_mrg_rxbuf),
|
||||
"::",
|
||||
stringify!(hdr)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_net_hdr_mrg_rxbuf>())).num_buffers as *const _ as usize
|
||||
},
|
||||
10usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_hdr_mrg_rxbuf),
|
||||
"::",
|
||||
stringify!(num_buffers)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_net_ctrl_hdr {
|
||||
pub class: __u8,
|
||||
pub cmd: __u8,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_net_ctrl_hdr() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_net_ctrl_hdr>(),
|
||||
2usize,
|
||||
concat!("Size of: ", stringify!(virtio_net_ctrl_hdr))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_net_ctrl_hdr>(),
|
||||
1usize,
|
||||
concat!("Alignment of ", stringify!(virtio_net_ctrl_hdr))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_ctrl_hdr>())).class as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_ctrl_hdr),
|
||||
"::",
|
||||
stringify!(class)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<virtio_net_ctrl_hdr>())).cmd as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_ctrl_hdr),
|
||||
"::",
|
||||
stringify!(cmd)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type virtio_net_ctrl_ack = __u8;
|
||||
#[repr(C, packed)]
|
||||
#[derive(Default)]
|
||||
pub struct virtio_net_ctrl_mac {
|
||||
pub entries: __virtio32,
|
||||
pub macs: __IncompleteArrayField<[__u8; 6usize]>,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_net_ctrl_mac() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_net_ctrl_mac>(),
|
||||
4usize,
|
||||
concat!("Size of: ", stringify!(virtio_net_ctrl_mac))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_net_ctrl_mac>(),
|
||||
1usize,
|
||||
concat!("Alignment of ", stringify!(virtio_net_ctrl_mac))
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct virtio_net_ctrl_mq {
|
||||
pub virtqueue_pairs: __virtio16,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_virtio_net_ctrl_mq() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<virtio_net_ctrl_mq>(),
|
||||
2usize,
|
||||
concat!("Size of: ", stringify!(virtio_net_ctrl_mq))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<virtio_net_ctrl_mq>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(virtio_net_ctrl_mq))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
&(*(::std::ptr::null::<virtio_net_ctrl_mq>())).virtqueue_pairs as *const _ as usize
|
||||
},
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(virtio_net_ctrl_mq),
|
||||
"::",
|
||||
stringify!(virtqueue_pairs)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,453 +0,0 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* automatically generated by rust-bindgen */
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
|
||||
impl<T> __IncompleteArrayField<T> {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
__IncompleteArrayField(::std::marker::PhantomData, [])
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_ptr(&self) -> *const T {
|
||||
::std::mem::transmute(self)
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
|
||||
::std::mem::transmute(self)
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
|
||||
::std::slice::from_raw_parts(self.as_ptr(), len)
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
|
||||
::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
|
||||
}
|
||||
}
|
||||
impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
|
||||
fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
fmt.write_str("__IncompleteArrayField")
|
||||
}
|
||||
}
|
||||
impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
pub const _STDINT_H: u32 = 1;
|
||||
pub const _FEATURES_H: u32 = 1;
|
||||
pub const _DEFAULT_SOURCE: u32 = 1;
|
||||
pub const __USE_ISOC11: u32 = 1;
|
||||
pub const __USE_ISOC99: u32 = 1;
|
||||
pub const __USE_ISOC95: u32 = 1;
|
||||
pub const __USE_POSIX_IMPLICITLY: u32 = 1;
|
||||
pub const _POSIX_SOURCE: u32 = 1;
|
||||
pub const _POSIX_C_SOURCE: u32 = 200809;
|
||||
pub const __USE_POSIX: u32 = 1;
|
||||
pub const __USE_POSIX2: u32 = 1;
|
||||
pub const __USE_POSIX199309: u32 = 1;
|
||||
pub const __USE_POSIX199506: u32 = 1;
|
||||
pub const __USE_XOPEN2K: u32 = 1;
|
||||
pub const __USE_XOPEN2K8: u32 = 1;
|
||||
pub const _ATFILE_SOURCE: u32 = 1;
|
||||
pub const __USE_MISC: u32 = 1;
|
||||
pub const __USE_ATFILE: u32 = 1;
|
||||
pub const __USE_FORTIFY_LEVEL: u32 = 0;
|
||||
pub const _STDC_PREDEF_H: u32 = 1;
|
||||
pub const __STDC_IEC_559__: u32 = 1;
|
||||
pub const __STDC_IEC_559_COMPLEX__: u32 = 1;
|
||||
pub const __STDC_ISO_10646__: u32 = 201505;
|
||||
pub const __STDC_NO_THREADS__: u32 = 1;
|
||||
pub const __GNU_LIBRARY__: u32 = 6;
|
||||
pub const __GLIBC__: u32 = 2;
|
||||
pub const __GLIBC_MINOR__: u32 = 23;
|
||||
pub const _SYS_CDEFS_H: u32 = 1;
|
||||
pub const __WORDSIZE: u32 = 64;
|
||||
pub const __WORDSIZE_TIME64_COMPAT32: u32 = 1;
|
||||
pub const __SYSCALL_WORDSIZE: u32 = 64;
|
||||
pub const _BITS_WCHAR_H: u32 = 1;
|
||||
pub const INT8_MIN: i32 = -128;
|
||||
pub const INT16_MIN: i32 = -32768;
|
||||
pub const INT32_MIN: i32 = -2147483648;
|
||||
pub const INT8_MAX: u32 = 127;
|
||||
pub const INT16_MAX: u32 = 32767;
|
||||
pub const INT32_MAX: u32 = 2147483647;
|
||||
pub const UINT8_MAX: u32 = 255;
|
||||
pub const UINT16_MAX: u32 = 65535;
|
||||
pub const UINT32_MAX: u32 = 4294967295;
|
||||
pub const INT_LEAST8_MIN: i32 = -128;
|
||||
pub const INT_LEAST16_MIN: i32 = -32768;
|
||||
pub const INT_LEAST32_MIN: i32 = -2147483648;
|
||||
pub const INT_LEAST8_MAX: u32 = 127;
|
||||
pub const INT_LEAST16_MAX: u32 = 32767;
|
||||
pub const INT_LEAST32_MAX: u32 = 2147483647;
|
||||
pub const UINT_LEAST8_MAX: u32 = 255;
|
||||
pub const UINT_LEAST16_MAX: u32 = 65535;
|
||||
pub const UINT_LEAST32_MAX: u32 = 4294967295;
|
||||
pub const INT_FAST8_MIN: i32 = -128;
|
||||
pub const INT_FAST16_MIN: i64 = -9223372036854775808;
|
||||
pub const INT_FAST32_MIN: i64 = -9223372036854775808;
|
||||
pub const INT_FAST8_MAX: u32 = 127;
|
||||
pub const INT_FAST16_MAX: u64 = 9223372036854775807;
|
||||
pub const INT_FAST32_MAX: u64 = 9223372036854775807;
|
||||
pub const UINT_FAST8_MAX: u32 = 255;
|
||||
pub const UINT_FAST16_MAX: i32 = -1;
|
||||
pub const UINT_FAST32_MAX: i32 = -1;
|
||||
pub const INTPTR_MIN: i64 = -9223372036854775808;
|
||||
pub const INTPTR_MAX: u64 = 9223372036854775807;
|
||||
pub const UINTPTR_MAX: i32 = -1;
|
||||
pub const PTRDIFF_MIN: i64 = -9223372036854775808;
|
||||
pub const PTRDIFF_MAX: u64 = 9223372036854775807;
|
||||
pub const SIG_ATOMIC_MIN: i32 = -2147483648;
|
||||
pub const SIG_ATOMIC_MAX: u32 = 2147483647;
|
||||
pub const SIZE_MAX: i32 = -1;
|
||||
pub const WINT_MIN: u32 = 0;
|
||||
pub const WINT_MAX: u32 = 4294967295;
|
||||
pub const __BITS_PER_LONG: u32 = 64;
|
||||
pub const __FD_SETSIZE: u32 = 1024;
|
||||
pub const VRING_DESC_F_NEXT: u32 = 1;
|
||||
pub const VRING_DESC_F_WRITE: u32 = 2;
|
||||
pub const VRING_DESC_F_INDIRECT: u32 = 4;
|
||||
pub const VRING_USED_F_NO_NOTIFY: u32 = 1;
|
||||
pub const VRING_AVAIL_F_NO_INTERRUPT: u32 = 1;
|
||||
pub const VIRTIO_RING_F_INDIRECT_DESC: u32 = 28;
|
||||
pub const VIRTIO_RING_F_EVENT_IDX: u32 = 29;
|
||||
pub const VRING_AVAIL_ALIGN_SIZE: u32 = 2;
|
||||
pub const VRING_USED_ALIGN_SIZE: u32 = 4;
|
||||
pub const VRING_DESC_ALIGN_SIZE: u32 = 16;
|
||||
pub type int_least8_t = ::std::os::raw::c_schar;
|
||||
pub type int_least16_t = ::std::os::raw::c_short;
|
||||
pub type int_least32_t = ::std::os::raw::c_int;
|
||||
pub type int_least64_t = ::std::os::raw::c_long;
|
||||
pub type uint_least8_t = ::std::os::raw::c_uchar;
|
||||
pub type uint_least16_t = ::std::os::raw::c_ushort;
|
||||
pub type uint_least32_t = ::std::os::raw::c_uint;
|
||||
pub type uint_least64_t = ::std::os::raw::c_ulong;
|
||||
pub type int_fast8_t = ::std::os::raw::c_schar;
|
||||
pub type int_fast16_t = ::std::os::raw::c_long;
|
||||
pub type int_fast32_t = ::std::os::raw::c_long;
|
||||
pub type int_fast64_t = ::std::os::raw::c_long;
|
||||
pub type uint_fast8_t = ::std::os::raw::c_uchar;
|
||||
pub type uint_fast16_t = ::std::os::raw::c_ulong;
|
||||
pub type uint_fast32_t = ::std::os::raw::c_ulong;
|
||||
pub type uint_fast64_t = ::std::os::raw::c_ulong;
|
||||
pub type intmax_t = ::std::os::raw::c_long;
|
||||
pub type uintmax_t = ::std::os::raw::c_ulong;
|
||||
pub type __s8 = ::std::os::raw::c_schar;
|
||||
pub type __u8 = ::std::os::raw::c_uchar;
|
||||
pub type __s16 = ::std::os::raw::c_short;
|
||||
pub type __u16 = ::std::os::raw::c_ushort;
|
||||
pub type __s32 = ::std::os::raw::c_int;
|
||||
pub type __u32 = ::std::os::raw::c_uint;
|
||||
pub type __s64 = ::std::os::raw::c_longlong;
|
||||
pub type __u64 = ::std::os::raw::c_ulonglong;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct __kernel_fd_set {
|
||||
pub fds_bits: [::std::os::raw::c_ulong; 16usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout___kernel_fd_set() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<__kernel_fd_set>(),
|
||||
128usize,
|
||||
concat!("Size of: ", stringify!(__kernel_fd_set))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<__kernel_fd_set>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(__kernel_fd_set))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<__kernel_fd_set>())).fds_bits as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(__kernel_fd_set),
|
||||
"::",
|
||||
stringify!(fds_bits)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type __kernel_sighandler_t =
|
||||
::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
|
||||
pub type __kernel_key_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_mqd_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
|
||||
pub type __kernel_long_t = ::std::os::raw::c_long;
|
||||
pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
|
||||
pub type __kernel_ino_t = __kernel_ulong_t;
|
||||
pub type __kernel_mode_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_pid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_uid_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_gid_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_suseconds_t = __kernel_long_t;
|
||||
pub type __kernel_daddr_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_uid32_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_gid32_t = ::std::os::raw::c_uint;
|
||||
pub type __kernel_size_t = __kernel_ulong_t;
|
||||
pub type __kernel_ssize_t = __kernel_long_t;
|
||||
pub type __kernel_ptrdiff_t = __kernel_long_t;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct __kernel_fsid_t {
|
||||
pub val: [::std::os::raw::c_int; 2usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout___kernel_fsid_t() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<__kernel_fsid_t>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(__kernel_fsid_t))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<__kernel_fsid_t>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(__kernel_fsid_t))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<__kernel_fsid_t>())).val as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(__kernel_fsid_t),
|
||||
"::",
|
||||
stringify!(val)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type __kernel_off_t = __kernel_long_t;
|
||||
pub type __kernel_loff_t = ::std::os::raw::c_longlong;
|
||||
pub type __kernel_time_t = __kernel_long_t;
|
||||
pub type __kernel_clock_t = __kernel_long_t;
|
||||
pub type __kernel_timer_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_clockid_t = ::std::os::raw::c_int;
|
||||
pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
|
||||
pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
|
||||
pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
|
||||
pub type __le16 = __u16;
|
||||
pub type __be16 = __u16;
|
||||
pub type __le32 = __u32;
|
||||
pub type __be32 = __u32;
|
||||
pub type __le64 = __u64;
|
||||
pub type __be64 = __u64;
|
||||
pub type __sum16 = __u16;
|
||||
pub type __wsum = __u32;
|
||||
pub type __virtio16 = __u16;
|
||||
pub type __virtio32 = __u32;
|
||||
pub type __virtio64 = __u64;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct vring_desc {
|
||||
pub addr: __virtio64,
|
||||
pub len: __virtio32,
|
||||
pub flags: __virtio16,
|
||||
pub next: __virtio16,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_vring_desc() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vring_desc>(),
|
||||
16usize,
|
||||
concat!("Size of: ", stringify!(vring_desc))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vring_desc>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vring_desc))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring_desc>())).addr as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring_desc),
|
||||
"::",
|
||||
stringify!(addr)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring_desc>())).len as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring_desc),
|
||||
"::",
|
||||
stringify!(len)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring_desc>())).flags as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring_desc),
|
||||
"::",
|
||||
stringify!(flags)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring_desc>())).next as *const _ as usize },
|
||||
14usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring_desc),
|
||||
"::",
|
||||
stringify!(next)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct vring_avail {
|
||||
pub flags: __virtio16,
|
||||
pub idx: __virtio16,
|
||||
pub ring: __IncompleteArrayField<__virtio16>,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_vring_avail() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vring_avail>(),
|
||||
4usize,
|
||||
concat!("Size of: ", stringify!(vring_avail))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vring_avail>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(vring_avail))
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct vring_used_elem {
|
||||
pub id: __virtio32,
|
||||
pub len: __virtio32,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_vring_used_elem() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vring_used_elem>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(vring_used_elem))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vring_used_elem>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(vring_used_elem))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring_used_elem>())).id as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring_used_elem),
|
||||
"::",
|
||||
stringify!(id)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring_used_elem>())).len as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring_used_elem),
|
||||
"::",
|
||||
stringify!(len)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct vring_used {
|
||||
pub flags: __virtio16,
|
||||
pub idx: __virtio16,
|
||||
pub ring: __IncompleteArrayField<vring_used_elem>,
|
||||
pub __bindgen_align: [u32; 0usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_vring_used() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vring_used>(),
|
||||
4usize,
|
||||
concat!("Size of: ", stringify!(vring_used))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vring_used>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(vring_used))
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct vring {
|
||||
pub num: ::std::os::raw::c_uint,
|
||||
pub desc: *mut vring_desc,
|
||||
pub avail: *mut vring_avail,
|
||||
pub used: *mut vring_used,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_vring() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vring>(),
|
||||
32usize,
|
||||
concat!("Size of: ", stringify!(vring))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vring>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vring))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring>())).num as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring),
|
||||
"::",
|
||||
stringify!(num)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring>())).desc as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring),
|
||||
"::",
|
||||
stringify!(desc)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring>())).avail as *const _ as usize },
|
||||
16usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring),
|
||||
"::",
|
||||
stringify!(avail)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(::std::ptr::null::<vring>())).used as *const _ as usize },
|
||||
24usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(vring),
|
||||
"::",
|
||||
stringify!(used)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Default for vring {
|
||||
fn default() -> Self {
|
||||
unsafe { ::std::mem::zeroed() }
|
||||
}
|
||||
}
|
||||
@@ -5,5 +5,5 @@ authors = ["The Chromium OS Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
libc = "*"
|
||||
libc = "0.2.60"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
|
||||
7
vm-device/Cargo.toml
Normal file
7
vm-device/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "vm-device"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
11
vm-device/src/lib.rs
Normal file
11
vm-device/src/lib.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
/// Trait meant for triggering the DMA mapping update related to an external
|
||||
/// device not managed fully through virtio. It is dedicated to virtio-iommu
|
||||
/// in order to trigger the map update anytime the mapping is updated from the
|
||||
/// guest.
|
||||
pub trait ExternalDmaMapping: Send + Sync {
|
||||
/// Map a memory range
|
||||
fn map(&self, iova: u64, gpa: u64, size: u64) -> std::result::Result<(), std::io::Error>;
|
||||
|
||||
/// Unmap a memory range
|
||||
fn unmap(&self, iova: u64, size: u64) -> std::result::Result<(), std::io::Error>;
|
||||
}
|
||||
@@ -4,19 +4,25 @@ version = "0.1.0"
|
||||
authors = ["Samuel Ortiz <sameo@linux.intel.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
pci_support = ["pci"]
|
||||
mmio_support = []
|
||||
|
||||
[dependencies]
|
||||
byteorder = "=1.2.1"
|
||||
byteorder = "1.3.2"
|
||||
devices = { path = "../devices" }
|
||||
epoll = "=4.0.1"
|
||||
libc = ">=0.2.39"
|
||||
log = "*"
|
||||
epoll = "4.1.0"
|
||||
libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
net_gen = { path = "../net_gen" }
|
||||
net_util = { path = "../net_util" }
|
||||
pci = { path = "../pci" }
|
||||
tempfile = ">=3.0.2"
|
||||
virtio-bindings = { path = "../virtio-bindings" }
|
||||
pci = { path = "../pci", optional = true }
|
||||
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" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
[dependencies.vhost_rs]
|
||||
path = "../vhost_rs"
|
||||
|
||||
@@ -17,19 +17,18 @@ use std::os::linux::fs::MetadataExt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::PathBuf;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
|
||||
VirtioDeviceType, INTERRUPT_STATUS_USED_RING,
|
||||
VirtioDeviceType, VirtioInterruptType,
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use virtio_bindings::virtio_blk::*;
|
||||
use virtio_bindings::bindings::virtio_blk::*;
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const CONFIG_SPACE_SIZE: usize = 8;
|
||||
const SECTOR_SHIFT: u8 = 9;
|
||||
@@ -322,10 +321,9 @@ impl Request {
|
||||
|
||||
struct BlockEpollHandler<T: DiskFile> {
|
||||
queues: Vec<Queue>,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
disk_image: T,
|
||||
disk_nsectors: u64,
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
disk_image_id: Vec<u8>,
|
||||
}
|
||||
@@ -336,14 +334,15 @@ impl<T: DiskFile> BlockEpollHandler<T> {
|
||||
|
||||
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
|
||||
let mut used_count = 0;
|
||||
for avail_desc in queue.iter(&self.mem) {
|
||||
let mem = self.mem.read().unwrap();
|
||||
for avail_desc in queue.iter(&mem) {
|
||||
let len;
|
||||
match Request::parse(&avail_desc, &self.mem) {
|
||||
match Request::parse(&avail_desc, &mem) {
|
||||
Ok(request) => {
|
||||
let status = match request.execute(
|
||||
&mut self.disk_image,
|
||||
self.disk_nsectors,
|
||||
&self.mem,
|
||||
&mem,
|
||||
&self.disk_image_id,
|
||||
) {
|
||||
Ok(l) => {
|
||||
@@ -358,7 +357,7 @@ impl<T: DiskFile> BlockEpollHandler<T> {
|
||||
};
|
||||
// We use unwrap because the request parsing process already checked that the
|
||||
// status_addr was valid.
|
||||
self.mem.write_obj(status, request.status_addr).unwrap();
|
||||
mem.write_obj(status, request.status_addr).unwrap();
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to parse available descriptor chain: {:?}", e);
|
||||
@@ -370,18 +369,18 @@ impl<T: DiskFile> BlockEpollHandler<T> {
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
queue.add_used(&self.mem, desc_index, len);
|
||||
queue.add_used(&mem, desc_index, len);
|
||||
}
|
||||
used_count > 0
|
||||
}
|
||||
|
||||
fn signal_used_queue(&self, queue_index: usize) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_status
|
||||
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
|
||||
(self.interrupt_cb)(&self.queues[queue_index]).map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queues[queue_index])).map_err(
|
||||
|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -424,8 +423,22 @@ impl<T: DiskFile> BlockEpollHandler<T> {
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events =
|
||||
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
|
||||
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;
|
||||
@@ -490,6 +503,7 @@ impl<T: DiskFile> Block<T> {
|
||||
mut disk_image: T,
|
||||
disk_path: PathBuf,
|
||||
is_disk_read_only: bool,
|
||||
iommu: bool,
|
||||
) -> io::Result<Block<T>> {
|
||||
let disk_size = disk_image.seek(SeekFrom::End(0))? as u64;
|
||||
if disk_size % SECTOR_SIZE != 0 {
|
||||
@@ -502,6 +516,10 @@ impl<T: DiskFile> Block<T> {
|
||||
|
||||
let mut avail_features = (1u64 << VIRTIO_F_VERSION_1) | (1u64 << VIRTIO_BLK_F_FLUSH);
|
||||
|
||||
if iommu {
|
||||
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
||||
}
|
||||
|
||||
if is_disk_read_only {
|
||||
avail_features |= 1u64 << VIRTIO_BLK_F_RO;
|
||||
};
|
||||
@@ -598,9 +616,8 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
status: Arc<AtomicUsize>,
|
||||
queues: Vec<Queue>,
|
||||
mut queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
@@ -644,7 +661,6 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
|
||||
mem,
|
||||
disk_image,
|
||||
disk_nsectors: self.disk_nsectors,
|
||||
interrupt_status: status,
|
||||
interrupt_cb,
|
||||
disk_image_id,
|
||||
};
|
||||
|
||||
@@ -8,20 +8,21 @@ use std::cmp;
|
||||
use std::collections::VecDeque;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::ops::DerefMut;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread;
|
||||
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
|
||||
INTERRUPT_STATUS_USED_RING, VIRTIO_F_VERSION_1,
|
||||
VirtioInterruptType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use vm_memory::{Bytes, GuestMemoryMmap};
|
||||
use vmm_sys_util::EventFd;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use vm_memory::{ByteValued, Bytes, GuestMemoryMmap};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
const NUM_QUEUES: usize = 2;
|
||||
@@ -34,17 +35,34 @@ const OUTPUT_QUEUE_EVENT: DeviceEventT = 1;
|
||||
const INPUT_EVENT: DeviceEventT = 2;
|
||||
// The device has been dropped.
|
||||
const KILL_EVENT: DeviceEventT = 3;
|
||||
// Console configuration change event is triggered.
|
||||
const CONFIG_EVENT: DeviceEventT = 4;
|
||||
|
||||
//Console size feature bit
|
||||
const VIRTIO_CONSOLE_F_SIZE: u64 = 0;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioConsoleConfig {
|
||||
cols: u16,
|
||||
rows: u16,
|
||||
max_nr_ports: u32,
|
||||
emerg_wr: u32,
|
||||
}
|
||||
|
||||
// Safe because it only has data and has no implicit padding.
|
||||
unsafe impl ByteValued for VirtioConsoleConfig {}
|
||||
|
||||
struct ConsoleEpollHandler {
|
||||
queues: Vec<Queue>,
|
||||
mem: GuestMemoryMmap,
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
in_buffer: Arc<Mutex<VecDeque<u8>>>,
|
||||
out: Box<io::Write + Send>,
|
||||
out: Arc<Mutex<Box<dyn io::Write + Send + Sync + 'static>>>,
|
||||
input_queue_evt: EventFd,
|
||||
output_queue_evt: EventFd,
|
||||
input_evt: EventFd,
|
||||
config_evt: EventFd,
|
||||
kill_evt: EventFd,
|
||||
}
|
||||
|
||||
@@ -63,14 +81,15 @@ impl ConsoleEpollHandler {
|
||||
let mut used_count = 0;
|
||||
let mut write_count = 0;
|
||||
|
||||
for avail_desc in recv_queue.iter(&self.mem) {
|
||||
let mem = self.mem.read().unwrap();
|
||||
for avail_desc in recv_queue.iter(&mem) {
|
||||
let len;
|
||||
|
||||
let limit = cmp::min(write_count + avail_desc.len as u32, count as u32);
|
||||
let source_slice = in_buffer
|
||||
.drain(write_count as usize..limit as usize)
|
||||
.collect::<Vec<u8>>();
|
||||
let write_result = self.mem.write_slice(&source_slice[..], avail_desc.addr);
|
||||
let write_result = mem.write_slice(&source_slice[..], avail_desc.addr);
|
||||
|
||||
match write_result {
|
||||
Ok(_) => {
|
||||
@@ -92,7 +111,7 @@ impl ConsoleEpollHandler {
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
recv_queue.add_used(&self.mem, desc_index, len);
|
||||
recv_queue.add_used(&mem, desc_index, len);
|
||||
}
|
||||
used_count > 0
|
||||
}
|
||||
@@ -109,12 +128,16 @@ impl ConsoleEpollHandler {
|
||||
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
|
||||
let mut used_count = 0;
|
||||
|
||||
for avail_desc in trans_queue.iter(&self.mem) {
|
||||
let mem = self.mem.read().unwrap();
|
||||
for avail_desc in trans_queue.iter(&mem) {
|
||||
let len;
|
||||
let _ = self
|
||||
.mem
|
||||
.write_to(avail_desc.addr, &mut self.out, avail_desc.len as usize);
|
||||
let _ = self.out.flush();
|
||||
let mut out = self.out.lock().unwrap();
|
||||
let _ = mem.write_to(
|
||||
avail_desc.addr,
|
||||
&mut out.deref_mut(),
|
||||
avail_desc.len as usize,
|
||||
);
|
||||
let _ = out.flush();
|
||||
|
||||
len = avail_desc.len;
|
||||
used_desc_heads[used_count] = (avail_desc.index, len);
|
||||
@@ -122,15 +145,13 @@ impl ConsoleEpollHandler {
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
trans_queue.add_used(&self.mem, desc_index, len);
|
||||
trans_queue.add_used(&mem, desc_index, len);
|
||||
}
|
||||
used_count > 0
|
||||
}
|
||||
|
||||
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_status
|
||||
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
|
||||
(self.interrupt_cb)(&self.queues[0]).map_err(|e| {
|
||||
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queues[0])).map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
@@ -162,6 +183,14 @@ impl ConsoleEpollHandler {
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(INPUT_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.config_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(CONFIG_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
@@ -174,8 +203,22 @@ impl ConsoleEpollHandler {
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events =
|
||||
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
|
||||
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;
|
||||
@@ -206,6 +249,17 @@ impl ConsoleEpollHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
CONFIG_EVENT => {
|
||||
if let Err(e) = self.config_evt.read() {
|
||||
error!("Failed to get config event: {:?}", e);
|
||||
break 'epoll;
|
||||
} else if let Err(e) =
|
||||
(self.interrupt_cb)(&VirtioInterruptType::Config, None)
|
||||
{
|
||||
error!("Failed to signal console driver: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
KILL_EVENT => {
|
||||
debug!("KILL_EVENT received, stopping epoll loop");
|
||||
break 'epoll;
|
||||
@@ -221,19 +275,13 @@ impl ConsoleEpollHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Virtio device for exposing console to the guest OS through virtio.
|
||||
pub struct Console {
|
||||
kill_evt: Option<EventFd>,
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
input: Arc<ConsoleInput>,
|
||||
out: Option<Box<io::Write + Send>>,
|
||||
}
|
||||
|
||||
/// Input device.
|
||||
pub struct ConsoleInput {
|
||||
input_evt: EventFd,
|
||||
config_evt: EventFd,
|
||||
in_buffer: Arc<Mutex<VecDeque<u8>>>,
|
||||
config: Arc<Mutex<VirtioConsoleConfig>>,
|
||||
acked_features: AtomicU64,
|
||||
}
|
||||
|
||||
impl ConsoleInput {
|
||||
@@ -242,18 +290,71 @@ impl ConsoleInput {
|
||||
in_buffer.extend(input);
|
||||
let _ = self.input_evt.write(1);
|
||||
}
|
||||
|
||||
pub fn update_console_size(&self, cols: u16, rows: u16) {
|
||||
if self
|
||||
.acked_features
|
||||
.fetch_and(1u64 << VIRTIO_CONSOLE_F_SIZE, Ordering::SeqCst)
|
||||
!= 0
|
||||
{
|
||||
self.config.lock().unwrap().update_console_size(cols, rows);
|
||||
//Send the interrupt to the driver
|
||||
let _ = self.config_evt.write(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioConsoleConfig {
|
||||
pub fn new(cols: u16, rows: u16) -> Self {
|
||||
VirtioConsoleConfig {
|
||||
cols,
|
||||
rows,
|
||||
max_nr_ports: 1u32,
|
||||
emerg_wr: 0u32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_console_size(&mut self, cols: u16, rows: u16) {
|
||||
self.cols = cols;
|
||||
self.rows = rows;
|
||||
}
|
||||
}
|
||||
|
||||
/// Virtio device for exposing console to the guest OS through virtio.
|
||||
pub struct Console {
|
||||
kill_evt: Option<EventFd>,
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
config: Arc<Mutex<VirtioConsoleConfig>>,
|
||||
input: Arc<ConsoleInput>,
|
||||
out: Arc<Mutex<Box<dyn io::Write + Send + Sync + 'static>>>,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
}
|
||||
|
||||
impl Console {
|
||||
/// Create a new virtio console device that gets random data from /dev/urandom.
|
||||
pub fn new(out: Option<Box<io::Write + Send>>) -> io::Result<(Console, Arc<ConsoleInput>)> {
|
||||
let avail_features = 1u64 << VIRTIO_F_VERSION_1;
|
||||
pub fn new(
|
||||
out: Box<dyn io::Write + Send + Sync + 'static>,
|
||||
cols: u16,
|
||||
rows: u16,
|
||||
iommu: bool,
|
||||
) -> io::Result<(Console, Arc<ConsoleInput>)> {
|
||||
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_CONSOLE_F_SIZE;
|
||||
|
||||
if iommu {
|
||||
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
||||
}
|
||||
|
||||
let input_evt = EventFd::new(EFD_NONBLOCK).unwrap();
|
||||
|
||||
let config_evt = EventFd::new(EFD_NONBLOCK).unwrap();
|
||||
let console_config = Arc::new(Mutex::new(VirtioConsoleConfig::new(cols, rows)));
|
||||
let console_input = Arc::new(ConsoleInput {
|
||||
input_evt,
|
||||
config_evt,
|
||||
in_buffer: Arc::new(Mutex::new(VecDeque::new())),
|
||||
config: console_config.clone(),
|
||||
acked_features: AtomicU64::new(0),
|
||||
});
|
||||
|
||||
Ok((
|
||||
@@ -261,8 +362,11 @@ impl Console {
|
||||
kill_evt: None,
|
||||
avail_features,
|
||||
acked_features: 0u64,
|
||||
config: console_config,
|
||||
input: console_input.clone(),
|
||||
out,
|
||||
out: Arc::new(Mutex::new(out)),
|
||||
queue_evts: None,
|
||||
interrupt_cb: None,
|
||||
},
|
||||
console_input,
|
||||
))
|
||||
@@ -321,19 +425,30 @@ impl VirtioDevice for Console {
|
||||
self.acked_features |= v;
|
||||
}
|
||||
|
||||
fn read_config(&self, _offset: u64, _data: &mut [u8]) {
|
||||
warn!("Device specific configuration is not defined yet");
|
||||
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!("Device specific configuration is not defined yet");
|
||||
warn!("No device specific configration requires write");
|
||||
}
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
status: Arc<AtomicUsize>,
|
||||
queues: Vec<Queue>,
|
||||
mut queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
@@ -356,31 +471,66 @@ impl VirtioDevice for Console {
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
if let Some(out) = self.out.take() {
|
||||
let mut handler = ConsoleEpollHandler {
|
||||
queues,
|
||||
mem,
|
||||
interrupt_status: status,
|
||||
interrupt_cb,
|
||||
in_buffer: self.input.in_buffer.clone(),
|
||||
out,
|
||||
input_queue_evt: queue_evts.remove(0),
|
||||
output_queue_evt: queue_evts.remove(0),
|
||||
input_evt: self.input.input_evt.try_clone().unwrap(),
|
||||
kill_evt,
|
||||
};
|
||||
// Save the interrupt EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||
|
||||
let worker_result = thread::Builder::new()
|
||||
.name("virtio_console".to_string())
|
||||
.spawn(move || handler.run());
|
||||
|
||||
if let Err(e) = worker_result {
|
||||
error!("failed to spawn virtio_console worker: {}", e);
|
||||
return Err(ActivateError::BadActivate);;
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
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
|
||||
})?);
|
||||
}
|
||||
Err(ActivateError::BadActivate)
|
||||
self.queue_evts = Some(tmp_queue_evts);
|
||||
|
||||
self.input
|
||||
.acked_features
|
||||
.store(self.acked_features, Ordering::Relaxed);
|
||||
|
||||
if (self.acked_features & (1u64 << VIRTIO_CONSOLE_F_SIZE)) != 0 {
|
||||
if let Err(e) = (interrupt_cb)(&VirtioInterruptType::Config, None) {
|
||||
error!("Failed to signal console driver: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let mut handler = ConsoleEpollHandler {
|
||||
queues,
|
||||
mem,
|
||||
interrupt_cb,
|
||||
in_buffer: self.input.in_buffer.clone(),
|
||||
out: self.out.clone(),
|
||||
input_queue_evt: queue_evts.remove(0),
|
||||
output_queue_evt: queue_evts.remove(0),
|
||||
input_evt: self.input.input_evt.try_clone().unwrap(),
|
||||
config_evt: self.input.config_evt.try_clone().unwrap(),
|
||||
kill_evt,
|
||||
};
|
||||
|
||||
let worker_result = thread::Builder::new()
|
||||
.name("virtio_console".to_string())
|
||||
.spawn(move || handler.run());
|
||||
|
||||
if let Err(e) = worker_result {
|
||||
error!("failed to spawn virtio_console worker: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
// Return the interrupt and queue EventFDs
|
||||
Some((
|
||||
self.interrupt_cb.take().unwrap(),
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,36 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use super::*;
|
||||
use pci::{PciBarConfiguration, PciCapability};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::Arc;
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
use vmm_sys_util::EventFd;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap, GuestUsize};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub type VirtioInterrupt = Box<Fn(&Queue) -> std::result::Result<(), std::io::Error> + Send + Sync>;
|
||||
pub enum VirtioInterruptType {
|
||||
Config,
|
||||
Queue,
|
||||
}
|
||||
|
||||
pub type VirtioInterrupt = Box<
|
||||
dyn Fn(&VirtioInterruptType, Option<&Queue>) -> std::result::Result<(), std::io::Error>
|
||||
+ Send
|
||||
+ Sync,
|
||||
>;
|
||||
|
||||
pub type VirtioIommuRemapping =
|
||||
Box<dyn Fn(u64) -> std::result::Result<u64, std::io::Error> + Send + Sync>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VirtioSharedMemory {
|
||||
pub offset: u64,
|
||||
pub len: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VirtioSharedMemoryList {
|
||||
pub addr: GuestAddress,
|
||||
pub len: GuestUsize,
|
||||
pub region_list: Vec<VirtioSharedMemory>,
|
||||
}
|
||||
|
||||
/// Trait for virtio devices to be driven by a virtio transport.
|
||||
///
|
||||
@@ -47,9 +70,8 @@ pub trait VirtioDevice: Send {
|
||||
/// Activates this device for real usage.
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_evt: Arc<VirtioInterrupt>,
|
||||
status: Arc<AtomicUsize>,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult;
|
||||
@@ -60,13 +82,22 @@ pub trait VirtioDevice: Send {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns any additional BAR configuration required by the device.
|
||||
fn get_device_bars(&self) -> Vec<PciBarConfiguration> {
|
||||
Vec::new()
|
||||
/// Returns the list of shared memory regions required by the device.
|
||||
fn get_shm_regions(&self) -> Option<VirtioSharedMemoryList> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns any additional capabilities required by the device.
|
||||
fn get_device_caps(&self) -> Vec<Box<dyn PciCapability>> {
|
||||
Vec::new()
|
||||
fn iommu_translate(&self, addr: u64) -> u64 {
|
||||
addr
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait providing address translation the same way a physical DMA remapping
|
||||
/// table would provide translation between an IOVA and a physical address.
|
||||
/// The goal of this trait is to be used by virtio devices to perform the
|
||||
/// address translation before they try to read from the guest physical address.
|
||||
/// On the other side, the implementation itself should be provided by the code
|
||||
/// emulating the IOMMU for the guest.
|
||||
pub trait DmaRemapping: Send + Sync {
|
||||
fn translate(&self, id: u32, addr: u64) -> std::result::Result<u64, std::io::Error>;
|
||||
}
|
||||
|
||||
@@ -1,438 +0,0 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType,
|
||||
INTERRUPT_STATUS_USED_RING,
|
||||
};
|
||||
use crate::{VirtioInterrupt, VIRTIO_F_VERSION_1_BITMASK};
|
||||
use epoll;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std::cmp;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use vhost_rs::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
|
||||
use vhost_rs::vhost_user::{Master, VhostUserMaster};
|
||||
use vhost_rs::{VhostBackend, VhostUserMemoryRegionInfo, VringConfigData};
|
||||
use vm_memory::{Address, Error as MmapError, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
|
||||
use vmm_sys_util::EventFd;
|
||||
|
||||
const CONFIG_SPACE_TAG_SIZE: usize = 36;
|
||||
const CONFIG_SPACE_NUM_QUEUES_SIZE: usize = 4;
|
||||
const CONFIG_SPACE_SIZE: usize = CONFIG_SPACE_TAG_SIZE + CONFIG_SPACE_NUM_QUEUES_SIZE;
|
||||
const NUM_QUEUE_OFFSET: usize = 1;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// common
|
||||
|
||||
/// Invalid descriptor table address.
|
||||
DescriptorTableAddress,
|
||||
/// Invalid used address.
|
||||
UsedAddress,
|
||||
/// Invalid available address.
|
||||
AvailAddress,
|
||||
|
||||
/// vhost
|
||||
|
||||
/// Creating kill eventfd failed.
|
||||
CreateKillEventFd(io::Error),
|
||||
/// Cloning kill eventfd failed.
|
||||
CloneKillEventFd(io::Error),
|
||||
/// Error while polling for events.
|
||||
PollError(io::Error),
|
||||
/// Failed to create irq eventfd.
|
||||
IrqEventCreate(io::Error),
|
||||
/// Failed to read vhost eventfd.
|
||||
VhostIrqRead(io::Error),
|
||||
|
||||
/// vhost-user
|
||||
|
||||
/// Connection to socket failed.
|
||||
VhostUserConnect(vhost_rs::Error),
|
||||
/// Get features failed.
|
||||
VhostUserGetFeatures(vhost_rs::Error),
|
||||
/// Get protocol features failed.
|
||||
VhostUserGetProtocolFeatures(vhost_rs::Error),
|
||||
/// Set owner failed.
|
||||
VhostUserSetOwner(vhost_rs::Error),
|
||||
/// Set features failed.
|
||||
VhostUserSetFeatures(vhost_rs::Error),
|
||||
/// Set protocol features failed.
|
||||
VhostUserSetProtocolFeatures(vhost_rs::Error),
|
||||
/// Set mem table failed.
|
||||
VhostUserSetMemTable(vhost_rs::Error),
|
||||
/// Set vring num failed.
|
||||
VhostUserSetVringNum(vhost_rs::Error),
|
||||
/// Set vring addr failed.
|
||||
VhostUserSetVringAddr(vhost_rs::Error),
|
||||
/// Set vring base failed.
|
||||
VhostUserSetVringBase(vhost_rs::Error),
|
||||
/// Set vring call failed.
|
||||
VhostUserSetVringCall(vhost_rs::Error),
|
||||
/// Set vring kick failed.
|
||||
VhostUserSetVringKick(vhost_rs::Error),
|
||||
|
||||
/// Invalid features provided from vhost-user backend.
|
||||
InvalidFeatures,
|
||||
|
||||
/// Missing file descriptor.
|
||||
FdMissing,
|
||||
|
||||
/// Failure going through memory regions.
|
||||
MemoryRegions(MmapError),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
struct FsEpollHandler {
|
||||
vu_call_evt_queue_list: Vec<(EventFd, Queue)>,
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
kill_evt: EventFd,
|
||||
}
|
||||
|
||||
impl FsEpollHandler {
|
||||
fn run(&mut self) -> result::Result<(), DeviceError> {
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
||||
|
||||
for (evt_index, vu_call_evt_queue) in self.vu_call_evt_queue_list.iter().enumerate() {
|
||||
// Add events
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
vu_call_evt_queue.0.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, evt_index as u64),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
}
|
||||
|
||||
let kill_evt_index = self.vu_call_evt_queue_list.len();
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.kill_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, kill_evt_index as u64),
|
||||
)
|
||||
.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 =
|
||||
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let ev_type = event.data as usize;
|
||||
|
||||
match ev_type {
|
||||
x if (x < kill_evt_index) => {
|
||||
if let Err(e) = self.vu_call_evt_queue_list[x].0.read() {
|
||||
error!("Failed to get queue event: {:?}", e);
|
||||
break 'epoll;
|
||||
} else {
|
||||
self.interrupt_status
|
||||
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
|
||||
if let Err(e) = (self.interrupt_cb)(&self.vu_call_evt_queue_list[x].1) {
|
||||
error!(
|
||||
"Failed to signal used queue: {:?}",
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
);
|
||||
break 'epoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
x if (x == kill_evt_index) => {
|
||||
debug!("KILL_EVENT received, stopping epoll loop");
|
||||
break 'epoll;
|
||||
}
|
||||
_ => {
|
||||
error!("Unknown event for virtio-fs");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Fs {
|
||||
vu: Master,
|
||||
queue_sizes: Vec<u16>,
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
config_space: Vec<u8>,
|
||||
kill_evt: Option<EventFd>,
|
||||
}
|
||||
|
||||
impl Fs {
|
||||
/// Create a new virtio-fs device.
|
||||
pub fn new(path: &str, tag: &str, req_num_queues: usize, queue_size: u16) -> Result<Fs> {
|
||||
// Calculate the actual number of queues needed.
|
||||
let num_queues = NUM_QUEUE_OFFSET + req_num_queues;
|
||||
// Connect to the vhost-user socket.
|
||||
let mut master =
|
||||
Master::connect(path, num_queues as u64).map_err(Error::VhostUserConnect)?;
|
||||
// Retrieve available features only when connecting the first time.
|
||||
let mut avail_features = master.get_features().map_err(Error::VhostUserGetFeatures)?;
|
||||
// Let only ack features we expect, that is VIRTIO_F_VERSION_1.
|
||||
if (avail_features & VIRTIO_F_VERSION_1_BITMASK) != VIRTIO_F_VERSION_1_BITMASK {
|
||||
return Err(Error::InvalidFeatures);
|
||||
}
|
||||
avail_features =
|
||||
VIRTIO_F_VERSION_1_BITMASK | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
master
|
||||
.set_features(avail_features)
|
||||
.map_err(Error::VhostUserSetFeatures)?;
|
||||
// Identify if protocol features are supported by the slave.
|
||||
if (avail_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits())
|
||||
== VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
|
||||
{
|
||||
let mut protocol_features = master
|
||||
.get_protocol_features()
|
||||
.map_err(Error::VhostUserGetProtocolFeatures)?;
|
||||
protocol_features &= VhostUserProtocolFeatures::MQ;
|
||||
master
|
||||
.set_protocol_features(protocol_features)
|
||||
.map_err(Error::VhostUserSetProtocolFeatures)?;
|
||||
}
|
||||
// Create virtio device config space.
|
||||
// First by adding the tag.
|
||||
let mut config_space = tag.to_string().into_bytes();
|
||||
config_space.resize(CONFIG_SPACE_SIZE, 0);
|
||||
// And then by copying the number of queues.
|
||||
let num_queues_slice = (req_num_queues as u32).to_le_bytes();
|
||||
config_space[CONFIG_SPACE_TAG_SIZE..CONFIG_SPACE_SIZE].copy_from_slice(&num_queues_slice);
|
||||
|
||||
Ok(Fs {
|
||||
vu: master,
|
||||
queue_sizes: vec![queue_size; num_queues],
|
||||
avail_features,
|
||||
acked_features: 0u64,
|
||||
config_space,
|
||||
kill_evt: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn setup_vu(
|
||||
&mut self,
|
||||
mem: &GuestMemoryMmap,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
) -> Result<Vec<(EventFd, Queue)>> {
|
||||
// Set vhost-user owner.
|
||||
self.vu.set_owner().map_err(Error::VhostUserSetOwner)?;
|
||||
|
||||
// Set backend features.
|
||||
self.vu
|
||||
.set_features(self.acked_features)
|
||||
.map_err(Error::VhostUserSetFeatures)?;
|
||||
|
||||
let mut regions: Vec<VhostUserMemoryRegionInfo> = Vec::new();
|
||||
|
||||
mem.with_regions_mut(|_, region| {
|
||||
let (mmap_handle, mmap_offset) = match region.file_offset() {
|
||||
Some(fo) => (fo.file().as_raw_fd(), fo.start()),
|
||||
None => return Err(MmapError::NoMemoryRegion),
|
||||
};
|
||||
|
||||
let vu_mem_reg = VhostUserMemoryRegionInfo {
|
||||
guest_phys_addr: region.start_addr().raw_value(),
|
||||
memory_size: region.len() as u64,
|
||||
userspace_addr: region.as_ptr() as u64,
|
||||
mmap_offset,
|
||||
mmap_handle,
|
||||
};
|
||||
|
||||
regions.push(vu_mem_reg);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(Error::MemoryRegions)?;
|
||||
|
||||
self.vu
|
||||
.set_mem_table(regions.as_slice())
|
||||
.map_err(Error::VhostUserSetMemTable)?;
|
||||
|
||||
let mut result = Vec::new();
|
||||
for (queue_index, queue) in queues.into_iter().enumerate() {
|
||||
self.vu
|
||||
.set_vring_num(queue_index, queue.get_max_size())
|
||||
.map_err(Error::VhostUserSetVringNum)?;
|
||||
|
||||
let vring_config = VringConfigData {
|
||||
queue_max_size: queue.get_max_size(),
|
||||
queue_size: queue.size,
|
||||
flags: 0u32,
|
||||
desc_table_addr: mem
|
||||
.get_host_address(queue.desc_table)
|
||||
.ok_or_else(|| Error::DescriptorTableAddress)?
|
||||
as u64,
|
||||
used_ring_addr: mem
|
||||
.get_host_address(queue.used_ring)
|
||||
.ok_or_else(|| Error::UsedAddress)? as u64,
|
||||
avail_ring_addr: mem
|
||||
.get_host_address(queue.avail_ring)
|
||||
.ok_or_else(|| Error::AvailAddress)? as u64,
|
||||
log_addr: None,
|
||||
};
|
||||
|
||||
self.vu
|
||||
.set_vring_addr(queue_index, &vring_config)
|
||||
.map_err(Error::VhostUserSetVringAddr)?;
|
||||
|
||||
self.vu
|
||||
.set_vring_base(queue_index, 0u16)
|
||||
.map_err(Error::VhostUserSetVringBase)?;
|
||||
|
||||
let vu_call_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::IrqEventCreate)?;
|
||||
|
||||
self.vu
|
||||
.set_vring_call(queue_index, &vu_call_evt)
|
||||
.map_err(Error::VhostUserSetVringCall)?;
|
||||
|
||||
result.push((vu_call_evt, queue));
|
||||
|
||||
self.vu
|
||||
.set_vring_kick(queue_index, &queue_evts[queue_index])
|
||||
.map_err(Error::VhostUserSetVringKick)?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Fs {
|
||||
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 Fs {
|
||||
fn device_type(&self) -> u32 {
|
||||
VirtioDeviceType::TYPE_FS as u32
|
||||
}
|
||||
|
||||
fn queue_max_sizes(&self) -> &[u16] {
|
||||
&self.queue_sizes.as_slice()
|
||||
}
|
||||
|
||||
fn features(&self, page: u32) -> u32 {
|
||||
match page {
|
||||
// Get the lower 32-bits of the features bitfield.
|
||||
0 => self.avail_features as u32,
|
||||
// Get the upper 32-bits of the features bitfield.
|
||||
1 => (self.avail_features >> 32) as u32,
|
||||
_ => {
|
||||
warn!("fs: Received request for unknown features page: {}", page);
|
||||
0u32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ack_features(&mut self, page: u32, value: u32) {
|
||||
let mut v = match page {
|
||||
0 => u64::from(value),
|
||||
1 => u64::from(value) << 32,
|
||||
_ => {
|
||||
warn!("fs: Cannot acknowledge unknown features page: {}", page);
|
||||
0u64
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the guest is ACK'ing a feature that we didn't claim to have.
|
||||
let unrequested_features = v & !self.avail_features;
|
||||
if unrequested_features != 0 {
|
||||
warn!("fs: virtio-fs got unknown feature ack: {:x}", v);
|
||||
|
||||
// Don't count these features as acked.
|
||||
v &= !unrequested_features;
|
||||
}
|
||||
self.acked_features |= v;
|
||||
}
|
||||
|
||||
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
|
||||
let config_len = self.config_space.len() as u64;
|
||||
if offset >= config_len {
|
||||
error!("Failed to read config space");
|
||||
return;
|
||||
}
|
||||
if let Some(end) = offset.checked_add(data.len() as u64) {
|
||||
// This write can't fail, offset and end are checked against config_len.
|
||||
data.write_all(&self.config_space[offset as usize..cmp::min(end, config_len) as usize])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn write_config(&mut self, offset: u64, data: &[u8]) {
|
||||
let data_len = data.len() as u64;
|
||||
let config_len = self.config_space.len() as u64;
|
||||
if offset + data_len > config_len {
|
||||
error!("Failed to write config space");
|
||||
return;
|
||||
}
|
||||
let (_, right) = self.config_space.split_at_mut(offset as usize);
|
||||
right.copy_from_slice(&data[..]);
|
||||
}
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryMmap,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
status: Arc<AtomicUsize>,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
if queues.len() != self.queue_sizes.len() || queue_evts.len() != self.queue_sizes.len() {
|
||||
error!(
|
||||
"Cannot perform activate. Expected {} queue(s), got {}",
|
||||
self.queue_sizes.len(),
|
||||
queues.len()
|
||||
);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
let (self_kill_evt, kill_evt) =
|
||||
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed creating kill EventFd pair: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
let vu_call_evt_queue_list = self
|
||||
.setup_vu(&mem, queues, queue_evts)
|
||||
.map_err(ActivateError::VhostUserSetup)?;
|
||||
|
||||
let mut handler = FsEpollHandler {
|
||||
vu_call_evt_queue_list,
|
||||
interrupt_status: status,
|
||||
interrupt_cb,
|
||||
kill_evt,
|
||||
};
|
||||
|
||||
let worker_result = thread::Builder::new()
|
||||
.name("virtio_fs".to_string())
|
||||
.spawn(move || handler.run());
|
||||
|
||||
if let Err(e) = worker_result {
|
||||
error!("failed to spawn virtio_blk worker: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
914
vm-virtio/src/iommu.rs
Normal file
914
vm-virtio/src/iommu.rs
Normal file
@@ -0,0 +1,914 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use epoll;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std::cmp;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{self, Display};
|
||||
use std::io::{self, Write};
|
||||
use std::mem::size_of;
|
||||
use std::ops::Bound::Included;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::result;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
|
||||
VirtioDeviceType, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::{DmaRemapping, VirtioInterrupt, VirtioInterruptType};
|
||||
use vm_device::ExternalDmaMapping;
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// Queues sizes
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
const NUM_QUEUES: usize = 2;
|
||||
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
|
||||
|
||||
/// New descriptors are pending on the request queue.
|
||||
/// "requestq" is meant to be used anytime an action is required to be
|
||||
/// performed on behalf of the guest driver.
|
||||
const REQUEST_Q_EVENT: DeviceEventT = 0;
|
||||
/// New descriptors are pending on the event queue.
|
||||
/// "eventq" lets the device report any fault or other asynchronous event to
|
||||
/// the guest driver.
|
||||
const EVENT_Q_EVENT: DeviceEventT = 1;
|
||||
/// The device has been dropped.
|
||||
const KILL_EVENT: DeviceEventT = 2;
|
||||
|
||||
/// Virtio IOMMU features
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_F_INPUT_RANGE: u32 = 0;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_F_DOMAIN_BITS: u32 = 1;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_F_MAP_UNMAP: u32 = 2;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_F_BYPASS: u32 = 3;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_F_PROBE: u32 = 4;
|
||||
|
||||
// Support 2MiB and 4KiB page sizes.
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_PAGE_SIZE_MASK: u64 = (2 << 20) | (4 << 10);
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuRange {
|
||||
start: u64,
|
||||
end: u64,
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuRange {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuConfig {
|
||||
page_size_mask: u64,
|
||||
input_range: VirtioIommuRange,
|
||||
domain_bits: u8,
|
||||
padding: [u8; 3],
|
||||
probe_size: u32,
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuConfig {}
|
||||
|
||||
/// Virtio IOMMU request type
|
||||
const VIRTIO_IOMMU_T_ATTACH: u8 = 1;
|
||||
const VIRTIO_IOMMU_T_DETACH: u8 = 2;
|
||||
const VIRTIO_IOMMU_T_MAP: u8 = 3;
|
||||
const VIRTIO_IOMMU_T_UNMAP: u8 = 4;
|
||||
const VIRTIO_IOMMU_T_PROBE: u8 = 5;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuReqHead {
|
||||
type_: u8,
|
||||
reserved: [u8; 3],
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuReqHead {}
|
||||
|
||||
/// Virtio IOMMU request status
|
||||
const VIRTIO_IOMMU_S_OK: u8 = 0;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_S_IOERR: u8 = 1;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_S_UNSUPP: u8 = 2;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_S_DEVERR: u8 = 3;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_S_INVAL: u8 = 4;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_S_RANGE: u8 = 5;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_S_NOENT: u8 = 6;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_S_FAULT: u8 = 7;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuReqTail {
|
||||
status: u8,
|
||||
reserved: [u8; 3],
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuReqTail {}
|
||||
|
||||
/// ATTACH request
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuReqAttach {
|
||||
domain: u32,
|
||||
endpoint: u32,
|
||||
reserved: [u8; 8],
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuReqAttach {}
|
||||
|
||||
/// DETACH request
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuReqDetach {
|
||||
domain: u32,
|
||||
endpoint: u32,
|
||||
reserved: [u8; 8],
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuReqDetach {}
|
||||
|
||||
/// Virtio IOMMU request MAP flags
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_MAP_F_READ: u32 = 1;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_MAP_F_WRITE: u32 = 1 << 1;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_MAP_F_EXEC: u32 = 1 << 2;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_MAP_F_MMIO: u32 = 1 << 3;
|
||||
|
||||
/// MAP request
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuReqMap {
|
||||
domain: u32,
|
||||
virt_start: u64,
|
||||
virt_end: u64,
|
||||
phys_start: u64,
|
||||
flags: u32,
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuReqMap {}
|
||||
|
||||
/// UNMAP request
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuReqUnmap {
|
||||
domain: u32,
|
||||
virt_start: u64,
|
||||
virt_end: u64,
|
||||
reserved: [u8; 4],
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuReqUnmap {}
|
||||
|
||||
/// Virtio IOMMU request PROBE types
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_PROBE_T_MASK: u32 = 0xfff;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_PROBE_T_NONE: u32 = 0;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_PROBE_T_RESV_MEM: u32 = 1;
|
||||
|
||||
/// PROBE request
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuReqProbe {
|
||||
endpoint: u32,
|
||||
reserved: [u64; 8],
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuReqProbe {}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuProbeProperty {
|
||||
type_: u16,
|
||||
length: u16,
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuProbeProperty {}
|
||||
|
||||
/// Virtio IOMMU request PROBE property RESV_MEM subtypes
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_RESV_MEM_T_RESERVED: u32 = 0;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_RESV_MEM_T_MSI: u32 = 1;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuProbeResvMem {
|
||||
head: VirtioIommuProbeProperty,
|
||||
subtype: u8,
|
||||
reserved: [u8; 3],
|
||||
start: u64,
|
||||
end: u64,
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuProbeResvMem {}
|
||||
|
||||
/// Virtio IOMMU fault flags
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_FAULT_F_READ: u32 = 1;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_FAULT_F_WRITE: u32 = 1 << 1;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_FAULT_F_EXEC: u32 = 1 << 2;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_FAULT_F_ADDRESS: u32 = 1 << 8;
|
||||
|
||||
/// Virtio IOMMU fault reasons
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_FAULT_R_UNKNOWN: u32 = 0;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_FAULT_R_DOMAIN: u32 = 1;
|
||||
#[allow(unused)]
|
||||
const VIRTIO_IOMMU_FAULT_R_MAPPING: u32 = 2;
|
||||
|
||||
/// Fault reporting through eventq
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(packed)]
|
||||
struct VirtioIommuFault {
|
||||
reason: u8,
|
||||
reserved: [u8; 3],
|
||||
flags: u32,
|
||||
endpoint: u32,
|
||||
reserved1: u32,
|
||||
address: u64,
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioIommuFault {}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
/// Guest gave us bad memory addresses.
|
||||
GuestMemory(GuestMemoryError),
|
||||
/// Guest gave us a write only descriptor that protocol says to read from.
|
||||
UnexpectedWriteOnlyDescriptor,
|
||||
/// Guest gave us a read only descriptor that protocol says to write to.
|
||||
UnexpectedReadOnlyDescriptor,
|
||||
/// Guest gave us too few descriptors in a descriptor chain.
|
||||
DescriptorChainTooShort,
|
||||
/// Guest gave us a buffer that was too short to use.
|
||||
BufferLengthTooSmall,
|
||||
/// Guest sent us invalid request.
|
||||
InvalidRequest,
|
||||
/// Guest sent us invalid ATTACH request.
|
||||
InvalidAttachRequest,
|
||||
/// Guest sent us invalid DETACH request.
|
||||
InvalidDetachRequest,
|
||||
/// Guest sent us invalid MAP request.
|
||||
InvalidMapRequest,
|
||||
/// Guest sent us invalid UNMAP request.
|
||||
InvalidUnmapRequest,
|
||||
/// Guest sent us invalid PROBE request.
|
||||
InvalidProbeRequest,
|
||||
/// Failed to performing external mapping.
|
||||
ExternalMapping(io::Error),
|
||||
/// Failed to performing external unmapping.
|
||||
ExternalUnmapping(io::Error),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
match self {
|
||||
BufferLengthTooSmall => write!(f, "buffer length too small"),
|
||||
DescriptorChainTooShort => write!(f, "descriptor chain too short"),
|
||||
GuestMemory(e) => write!(f, "bad guest memory address: {}", e),
|
||||
InvalidRequest => write!(f, "invalid request"),
|
||||
InvalidAttachRequest => write!(f, "invalid attach request"),
|
||||
InvalidDetachRequest => write!(f, "invalid detach request"),
|
||||
InvalidMapRequest => write!(f, "invalid map request"),
|
||||
InvalidUnmapRequest => write!(f, "invalid unmap request"),
|
||||
InvalidProbeRequest => write!(f, "invalid probe request"),
|
||||
UnexpectedReadOnlyDescriptor => write!(f, "unexpected read-only descriptor"),
|
||||
UnexpectedWriteOnlyDescriptor => write!(f, "unexpected write-only descriptor"),
|
||||
ExternalMapping(e) => write!(f, "failed performing external mapping: {}", e),
|
||||
ExternalUnmapping(e) => write!(f, "failed performing external unmapping: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum RequestType {
|
||||
Attach,
|
||||
Detach,
|
||||
Map,
|
||||
Unmap,
|
||||
Probe,
|
||||
}
|
||||
|
||||
struct Request {
|
||||
#[allow(unused)]
|
||||
type_: RequestType,
|
||||
status_addr: GuestAddress,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
// Parse the available vring buffer. Based on the hashmap table of external
|
||||
// mappings required from various devices such as VFIO or vhost-user ones,
|
||||
// this function might update the hashmap table of external mappings per
|
||||
// domain.
|
||||
// Basically, the VMM knows about the device_id <=> mapping relationship
|
||||
// before running the VM, but at runtime, a new domain <=> mapping hashmap
|
||||
// is created based on the information provided from the guest driver for
|
||||
// virtio-iommu (giving the link device_id <=> domain).
|
||||
fn parse(
|
||||
avail_desc: &DescriptorChain,
|
||||
mem: &GuestMemoryMmap,
|
||||
mapping: &Arc<IommuMapping>,
|
||||
ext_mapping: &BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
|
||||
ext_domain_mapping: &mut BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
|
||||
) -> result::Result<Request, Error> {
|
||||
// The head contains the request type which MUST be readable.
|
||||
if avail_desc.is_write_only() {
|
||||
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
||||
}
|
||||
|
||||
if (avail_desc.len as usize) < size_of::<VirtioIommuReqHead>() {
|
||||
return Err(Error::InvalidRequest);
|
||||
}
|
||||
|
||||
let req_head: VirtioIommuReqHead =
|
||||
mem.read_obj(avail_desc.addr).map_err(Error::GuestMemory)?;
|
||||
let req_offset = size_of::<VirtioIommuReqHead>();
|
||||
let desc_size_left = (avail_desc.len as usize) - req_offset;
|
||||
let req_addr = if let Some(addr) = avail_desc.addr.checked_add(req_offset as u64) {
|
||||
addr
|
||||
} else {
|
||||
return Err(Error::InvalidRequest);
|
||||
};
|
||||
|
||||
let request_type = match req_head.type_ {
|
||||
VIRTIO_IOMMU_T_ATTACH => {
|
||||
if desc_size_left != size_of::<VirtioIommuReqAttach>() {
|
||||
return Err(Error::InvalidAttachRequest);
|
||||
}
|
||||
|
||||
let req: VirtioIommuReqAttach = mem
|
||||
.read_obj(req_addr as GuestAddress)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
debug!("Attach request {:?}", req);
|
||||
|
||||
// Copy the value to use it as a proper reference.
|
||||
let domain = req.domain;
|
||||
let endpoint = req.endpoint;
|
||||
|
||||
// Add endpoint associated with specific domain
|
||||
mapping.endpoints.write().unwrap().insert(endpoint, domain);
|
||||
|
||||
// If the endpoint is part of the list of devices with an
|
||||
// external mapping, insert a new entry for the corresponding
|
||||
// domain, with the same reference to the trait.
|
||||
if let Some(map) = ext_mapping.get(&endpoint) {
|
||||
ext_domain_mapping.insert(domain, map.clone());
|
||||
}
|
||||
|
||||
// Add new domain with no mapping if the entry didn't exist yet
|
||||
let mut mappings = mapping.mappings.write().unwrap();
|
||||
if !mappings.contains_key(&domain) {
|
||||
mappings.insert(domain, BTreeMap::new());
|
||||
}
|
||||
|
||||
RequestType::Attach
|
||||
}
|
||||
VIRTIO_IOMMU_T_DETACH => {
|
||||
if desc_size_left != size_of::<VirtioIommuReqDetach>() {
|
||||
return Err(Error::InvalidDetachRequest);
|
||||
}
|
||||
|
||||
let req: VirtioIommuReqDetach = mem
|
||||
.read_obj(req_addr as GuestAddress)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
debug!("Detach request {:?}", req);
|
||||
|
||||
// Copy the value to use it as a proper reference.
|
||||
let domain = req.domain;
|
||||
let endpoint = req.endpoint;
|
||||
|
||||
// If the endpoint is part of the list of devices with an
|
||||
// external mapping, remove the entry for the corresponding
|
||||
// domain.
|
||||
if ext_mapping.contains_key(&endpoint) {
|
||||
ext_domain_mapping.remove(&domain);
|
||||
}
|
||||
|
||||
// Remove endpoint associated with specific domain
|
||||
mapping.endpoints.write().unwrap().remove(&endpoint);
|
||||
|
||||
RequestType::Detach
|
||||
}
|
||||
VIRTIO_IOMMU_T_MAP => {
|
||||
if desc_size_left != size_of::<VirtioIommuReqMap>() {
|
||||
return Err(Error::InvalidMapRequest);
|
||||
}
|
||||
|
||||
let req: VirtioIommuReqMap = mem
|
||||
.read_obj(req_addr as GuestAddress)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
debug!("Map request {:?}", req);
|
||||
|
||||
// Copy the value to use it as a proper reference.
|
||||
let domain = req.domain;
|
||||
|
||||
// Trigger external mapping if necessary.
|
||||
if let Some(ext_map) = ext_domain_mapping.get(&domain) {
|
||||
let size = req.virt_end - req.virt_start + 1;
|
||||
ext_map
|
||||
.map(req.virt_start, req.phys_start, size)
|
||||
.map_err(Error::ExternalMapping)?;
|
||||
}
|
||||
|
||||
// Add new mapping associated with the domain
|
||||
if let Some(entry) = mapping.mappings.write().unwrap().get_mut(&domain) {
|
||||
entry.insert(
|
||||
req.virt_start,
|
||||
Mapping {
|
||||
gpa: req.phys_start,
|
||||
size: req.virt_end - req.virt_start + 1,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return Err(Error::InvalidMapRequest);
|
||||
}
|
||||
|
||||
RequestType::Map
|
||||
}
|
||||
VIRTIO_IOMMU_T_UNMAP => {
|
||||
if desc_size_left != size_of::<VirtioIommuReqUnmap>() {
|
||||
return Err(Error::InvalidUnmapRequest);
|
||||
}
|
||||
|
||||
let req: VirtioIommuReqUnmap = mem
|
||||
.read_obj(req_addr as GuestAddress)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
debug!("Unmap request {:?}", req);
|
||||
|
||||
// Copy the value to use it as a proper reference.
|
||||
let domain = req.domain;
|
||||
let virt_start = req.virt_start;
|
||||
|
||||
// Trigger external unmapping if necessary.
|
||||
if let Some(ext_map) = ext_domain_mapping.get(&domain) {
|
||||
let size = req.virt_end - virt_start + 1;
|
||||
ext_map
|
||||
.unmap(virt_start, size)
|
||||
.map_err(Error::ExternalUnmapping)?;
|
||||
}
|
||||
|
||||
// Add new mapping associated with the domain
|
||||
if let Some(entry) = mapping.mappings.write().unwrap().get_mut(&domain) {
|
||||
entry.remove(&virt_start);
|
||||
}
|
||||
|
||||
RequestType::Unmap
|
||||
}
|
||||
VIRTIO_IOMMU_T_PROBE => {
|
||||
if desc_size_left != size_of::<VirtioIommuReqProbe>() {
|
||||
return Err(Error::InvalidProbeRequest);
|
||||
}
|
||||
|
||||
let req: VirtioIommuReqProbe = mem
|
||||
.read_obj(req_addr as GuestAddress)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
debug!("Probe request {:?}", req);
|
||||
|
||||
RequestType::Probe
|
||||
}
|
||||
_ => return Err(Error::InvalidRequest),
|
||||
};
|
||||
|
||||
let status_desc = avail_desc
|
||||
.next_descriptor()
|
||||
.ok_or(Error::DescriptorChainTooShort)?;
|
||||
|
||||
// The status MUST always be writable
|
||||
if !status_desc.is_write_only() {
|
||||
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||
}
|
||||
|
||||
if (status_desc.len as usize) < size_of::<VirtioIommuReqTail>() {
|
||||
return Err(Error::BufferLengthTooSmall);
|
||||
}
|
||||
|
||||
Ok(Request {
|
||||
type_: request_type,
|
||||
status_addr: status_desc.addr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct IommuEpollHandler {
|
||||
queues: Vec<Queue>,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
kill_evt: EventFd,
|
||||
mapping: Arc<IommuMapping>,
|
||||
ext_mapping: BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
|
||||
ext_domain_mapping: BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
|
||||
}
|
||||
|
||||
impl IommuEpollHandler {
|
||||
fn request_queue(&mut self) -> bool {
|
||||
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
|
||||
let mut used_count = 0;
|
||||
let mem = self.mem.read().unwrap();
|
||||
for avail_desc in self.queues[0].iter(&mem) {
|
||||
let len = match Request::parse(
|
||||
&avail_desc,
|
||||
&mem,
|
||||
&self.mapping,
|
||||
&self.ext_mapping,
|
||||
&mut self.ext_domain_mapping,
|
||||
) {
|
||||
Ok(ref req) => {
|
||||
let reply = VirtioIommuReqTail {
|
||||
status: VIRTIO_IOMMU_S_OK,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match mem.write_obj(reply, req.status_addr) {
|
||||
Ok(_) => size_of::<VirtioIommuReqTail>() as u32,
|
||||
Err(e) => {
|
||||
error!("bad guest memory address: {}", e);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to parse available descriptor chain: {:?}", e);
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
used_desc_heads[used_count] = (avail_desc.index, len);
|
||||
used_count += 1;
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
self.queues[0].add_used(&mem, desc_index, len);
|
||||
}
|
||||
used_count > 0
|
||||
}
|
||||
|
||||
fn event_queue(&mut self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> {
|
||||
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(queue)).map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&mut self) -> result::Result<(), DeviceError> {
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
||||
|
||||
// Add events
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.queue_evts[0].as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(REQUEST_Q_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.queue_evts[1].as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(EVENT_Q_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.kill_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
|
||||
const EPOLL_EVENTS_LEN: usize = 100;
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
// It's well defined from the epoll_wait() syscall
|
||||
// documentation that the epoll loop can be interrupted
|
||||
// before any of the requested events occurred or the
|
||||
// timeout expired. In both those cases, epoll_wait()
|
||||
// returns an error of type EINTR, but this should not
|
||||
// be considered as a regular error. Instead it is more
|
||||
// appropriate to retry, by calling into epoll_wait().
|
||||
continue;
|
||||
}
|
||||
return Err(DeviceError::EpollWait(e));
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let ev_type = event.data as u16;
|
||||
|
||||
match ev_type {
|
||||
REQUEST_Q_EVENT => {
|
||||
if let Err(e) = self.queue_evts[0].read() {
|
||||
error!("Failed to get queue event: {:?}", e);
|
||||
break 'epoll;
|
||||
} else if self.request_queue() {
|
||||
if let Err(e) = self.signal_used_queue(&self.queues[0]) {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
break 'epoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
EVENT_Q_EVENT => {
|
||||
if let Err(e) = self.queue_evts[1].read() {
|
||||
error!("Failed to get queue event: {:?}", e);
|
||||
break 'epoll;
|
||||
} else if self.event_queue() {
|
||||
if let Err(e) = self.signal_used_queue(&self.queues[1]) {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
break 'epoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
KILL_EVENT => {
|
||||
debug!("kill_evt received, stopping epoll loop");
|
||||
break 'epoll;
|
||||
}
|
||||
_ => {
|
||||
error!("Unknown event for virtio-iommu");
|
||||
break 'epoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("Exit epoll loop");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Mapping {
|
||||
gpa: u64,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
pub struct IommuMapping {
|
||||
// Domain related to an endpoint.
|
||||
endpoints: Arc<RwLock<BTreeMap<u32, u32>>>,
|
||||
// List of mappings per domain.
|
||||
mappings: Arc<RwLock<BTreeMap<u32, BTreeMap<u64, Mapping>>>>,
|
||||
}
|
||||
|
||||
impl DmaRemapping for IommuMapping {
|
||||
fn translate(&self, id: u32, addr: u64) -> std::result::Result<u64, std::io::Error> {
|
||||
debug!("Translate addr 0x{:x}", addr);
|
||||
if let Some(domain) = self.endpoints.read().unwrap().get(&id) {
|
||||
if let Some(mapping) = self.mappings.read().unwrap().get(domain) {
|
||||
let range_start = if VIRTIO_IOMMU_PAGE_SIZE_MASK > addr {
|
||||
0
|
||||
} else {
|
||||
addr - VIRTIO_IOMMU_PAGE_SIZE_MASK
|
||||
};
|
||||
for (&key, &value) in mapping.range((Included(&range_start), Included(&addr))) {
|
||||
if addr >= key && addr < key + value.size {
|
||||
let new_addr = addr - key + value.gpa;
|
||||
debug!("Into new_addr 0x{:x}", new_addr);
|
||||
return Ok(new_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Into same addr...");
|
||||
Ok(addr)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Iommu {
|
||||
kill_evt: Option<EventFd>,
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
config: VirtioIommuConfig,
|
||||
mapping: Arc<IommuMapping>,
|
||||
ext_mapping: BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
}
|
||||
|
||||
impl Iommu {
|
||||
pub fn new() -> io::Result<(Self, Arc<IommuMapping>)> {
|
||||
let config = VirtioIommuConfig {
|
||||
page_size_mask: VIRTIO_IOMMU_PAGE_SIZE_MASK,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mapping = Arc::new(IommuMapping {
|
||||
endpoints: Arc::new(RwLock::new(BTreeMap::new())),
|
||||
mappings: Arc::new(RwLock::new(BTreeMap::new())),
|
||||
});
|
||||
|
||||
Ok((
|
||||
Iommu {
|
||||
kill_evt: None,
|
||||
avail_features: 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_IOMMU_F_MAP_UNMAP,
|
||||
acked_features: 0u64,
|
||||
config,
|
||||
mapping: mapping.clone(),
|
||||
ext_mapping: BTreeMap::new(),
|
||||
queue_evts: None,
|
||||
interrupt_cb: None,
|
||||
},
|
||||
mapping,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn add_external_mapping(&mut self, device_id: u32, mapping: Arc<dyn ExternalDmaMapping>) {
|
||||
self.ext_mapping.insert(device_id, mapping);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Iommu {
|
||||
fn drop(&mut self) {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioDevice for Iommu {
|
||||
fn device_type(&self) -> u32 {
|
||||
VirtioDeviceType::TYPE_IOMMU as u32
|
||||
}
|
||||
|
||||
fn queue_max_sizes(&self) -> &[u16] {
|
||||
QUEUE_SIZES
|
||||
}
|
||||
|
||||
fn features(&self, page: u32) -> u32 {
|
||||
match page {
|
||||
// Get the lower 32-bits of the features bitfield.
|
||||
0 => self.avail_features as u32,
|
||||
// Get the upper 32-bits of the features bitfield.
|
||||
1 => (self.avail_features >> 32) as u32,
|
||||
_ => {
|
||||
warn!("Received request for unknown features page.");
|
||||
0u32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ack_features(&mut self, page: u32, value: u32) {
|
||||
let mut v = match page {
|
||||
0 => u64::from(value),
|
||||
1 => u64::from(value) << 32,
|
||||
_ => {
|
||||
warn!("Cannot acknowledge unknown features page.");
|
||||
0u64
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the guest is ACK'ing a feature that we didn't claim to have.
|
||||
let unrequested_features = v & !self.avail_features;
|
||||
if unrequested_features != 0 {
|
||||
warn!("Received acknowledge request for unknown feature.");
|
||||
|
||||
// Don't count these features as acked.
|
||||
v &= !unrequested_features;
|
||||
}
|
||||
self.acked_features |= v;
|
||||
}
|
||||
|
||||
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
|
||||
let config_slice = self.config.as_slice();
|
||||
let config_len = config_slice.len() as u64;
|
||||
if offset >= config_len {
|
||||
error!("Failed to read config space");
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(end) = offset.checked_add(data.len() as u64) {
|
||||
// This write can't fail, offset and end are checked against config_len.
|
||||
data.write_all(&config_slice[offset as usize..cmp::min(end, config_len) as usize])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn write_config(&mut self, _offset: u64, _data: &[u8]) {
|
||||
warn!("virtio-iommu device configuration is read-only");
|
||||
}
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES {
|
||||
error!(
|
||||
"Cannot perform activate. Expected {} queue(s), got {}",
|
||||
NUM_QUEUES,
|
||||
queues.len()
|
||||
);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
let (self_kill_evt, kill_evt) =
|
||||
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed creating kill EventFd pair: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
// Save the interrupt EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||
|
||||
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
|
||||
for queue_evt in queue_evts.iter() {
|
||||
// Save the queue EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
|
||||
error!("failed to clone queue EventFd: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?);
|
||||
}
|
||||
self.queue_evts = Some(tmp_queue_evts);
|
||||
|
||||
let mut handler = IommuEpollHandler {
|
||||
queues,
|
||||
mem,
|
||||
interrupt_cb,
|
||||
queue_evts,
|
||||
kill_evt,
|
||||
mapping: self.mapping.clone(),
|
||||
ext_mapping: self.ext_mapping.clone(),
|
||||
ext_domain_mapping: BTreeMap::new(),
|
||||
};
|
||||
|
||||
let worker_result = thread::Builder::new()
|
||||
.name("virtio-iommu".to_string())
|
||||
.spawn(move || handler.run());
|
||||
|
||||
if let Err(e) = worker_result {
|
||||
error!("failed to spawn virtio-iommu worker: {}", e);
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
// Return the interrupt and queue EventFDs
|
||||
Some((
|
||||
self.interrupt_cb.take().unwrap(),
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,9 @@
|
||||
extern crate epoll;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg(feature = "pci_support")]
|
||||
extern crate pci;
|
||||
extern crate vhost_rs;
|
||||
extern crate virtio_bindings;
|
||||
extern crate vm_memory;
|
||||
|
||||
@@ -22,22 +24,25 @@ use std::io;
|
||||
mod block;
|
||||
mod console;
|
||||
mod device;
|
||||
pub mod fs;
|
||||
mod iommu;
|
||||
pub mod net;
|
||||
mod pmem;
|
||||
mod queue;
|
||||
mod rng;
|
||||
pub mod vsock;
|
||||
|
||||
pub mod transport;
|
||||
pub mod vhost_user;
|
||||
|
||||
pub use self::block::*;
|
||||
pub use self::console::*;
|
||||
pub use self::device::*;
|
||||
pub use self::fs::*;
|
||||
pub use self::iommu::*;
|
||||
pub use self::net::*;
|
||||
pub use self::pmem::*;
|
||||
pub use self::queue::*;
|
||||
pub use self::rng::*;
|
||||
pub use self::vsock::*;
|
||||
|
||||
const DEVICE_INIT: u32 = 0x00;
|
||||
const DEVICE_ACKNOWLEDGE: u32 = 0x01;
|
||||
@@ -47,7 +52,8 @@ const DEVICE_FEATURES_OK: u32 = 0x08;
|
||||
const DEVICE_FAILED: u32 = 0x80;
|
||||
|
||||
const VIRTIO_F_VERSION_1: u32 = 32;
|
||||
const VIRTIO_F_VERSION_1_BITMASK: u64 = 1 << VIRTIO_F_VERSION_1;
|
||||
const VIRTIO_F_IOMMU_PLATFORM: u32 = 33;
|
||||
const VIRTIO_F_IN_ORDER: u32 = 35;
|
||||
|
||||
// Types taken from linux/virtio_ids.h
|
||||
#[derive(Copy, Clone)]
|
||||
@@ -64,6 +70,7 @@ enum VirtioDeviceType {
|
||||
TYPE_GPU = 16,
|
||||
TYPE_INPUT = 18,
|
||||
TYPE_VSOCK = 19,
|
||||
TYPE_IOMMU = 23,
|
||||
TYPE_FS = 26,
|
||||
TYPE_PMEM = 27,
|
||||
TYPE_UNKNOWN = 0xFF,
|
||||
@@ -80,6 +87,7 @@ impl From<u32> for VirtioDeviceType {
|
||||
16 => VirtioDeviceType::TYPE_GPU,
|
||||
18 => VirtioDeviceType::TYPE_INPUT,
|
||||
19 => VirtioDeviceType::TYPE_VSOCK,
|
||||
23 => VirtioDeviceType::TYPE_IOMMU,
|
||||
26 => VirtioDeviceType::TYPE_FS,
|
||||
27 => VirtioDeviceType::TYPE_PMEM,
|
||||
_ => VirtioDeviceType::TYPE_UNKNOWN,
|
||||
@@ -117,9 +125,20 @@ const INTERRUPT_STATUS_CONFIG_CHANGED: u32 = 0x2;
|
||||
pub enum ActivateError {
|
||||
EpollCtl(std::io::Error),
|
||||
BadActivate,
|
||||
|
||||
/// Queue number is not correct
|
||||
BadQueueNum,
|
||||
/// Failed to clone Kill event
|
||||
CloneKillEventFd,
|
||||
/// Failed to create Vhost-user interrupt eventfd
|
||||
VhostIrqCreate,
|
||||
/// Failed to setup vhost-user daemon.
|
||||
VhostUserSetup(fs::Error),
|
||||
VhostUserSetup(vhost_user::Error),
|
||||
/// Failed to setup vhost-user daemon.
|
||||
VhostUserNetSetup(vhost_user::Error),
|
||||
/// Failed to setup vhost-user daemon.
|
||||
VhostUserBlkSetup(vhost_user::Error),
|
||||
/// Failed to reset vhost-user daemon.
|
||||
VhostUserReset(vhost_user::Error),
|
||||
}
|
||||
|
||||
pub type ActivateResult = std::result::Result<(), ActivateError>;
|
||||
@@ -143,4 +162,5 @@ pub enum Error {
|
||||
EpollCreateFd(io::Error),
|
||||
EpollCtl(io::Error),
|
||||
EpollWait(io::Error),
|
||||
FailedSignalingDriver(io::Error),
|
||||
}
|
||||
|
||||
@@ -13,10 +13,9 @@ use std::io::Read;
|
||||
use std::io::{self, Write};
|
||||
use std::mem;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
use std::vec::Vec;
|
||||
|
||||
@@ -25,13 +24,13 @@ use net_gen;
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
|
||||
INTERRUPT_STATUS_USED_RING,
|
||||
VirtioInterruptType,
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use net_util::{MacAddr, Tap, TapError, MAC_ADDR_LEN};
|
||||
use virtio_bindings::virtio_net::*;
|
||||
use virtio_bindings::bindings::virtio_net::*;
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// The maximum buffer size when segmentation offload is enabled. This
|
||||
/// includes the 12-byte virtio net header.
|
||||
@@ -116,20 +115,19 @@ fn vnet_hdr_len() -> usize {
|
||||
}
|
||||
|
||||
struct NetEpollHandler {
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
tap: Tap,
|
||||
rx: RxVirtio,
|
||||
tx: TxVirtio,
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
kill_evt: EventFd,
|
||||
epoll_fd: RawFd,
|
||||
rx_tap_listening: bool,
|
||||
}
|
||||
|
||||
impl NetEpollHandler {
|
||||
fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_status
|
||||
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
|
||||
(self.interrupt_cb)(queue).map_err(|e| {
|
||||
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(queue)).map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
@@ -139,9 +137,15 @@ impl NetEpollHandler {
|
||||
// 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) -> bool {
|
||||
let mut next_desc = self.rx.queue.iter(&self.mem).next();
|
||||
let mem = self.mem.read().unwrap();
|
||||
let mut next_desc = self.rx.queue.iter(&mem).next();
|
||||
|
||||
if next_desc.is_none() {
|
||||
// Queue has no available descriptors
|
||||
if self.rx_tap_listening {
|
||||
self.unregister_tap_rx_listener().unwrap();
|
||||
self.rx_tap_listening = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -158,7 +162,7 @@ impl NetEpollHandler {
|
||||
}
|
||||
let limit = cmp::min(write_count + desc.len as usize, self.rx.bytes_read);
|
||||
let source_slice = &self.rx.frame_buf[write_count..limit];
|
||||
let write_result = self.mem.write_slice(source_slice, desc.addr);
|
||||
let write_result = mem.write_slice(source_slice, desc.addr);
|
||||
|
||||
match write_result {
|
||||
Ok(_) => {
|
||||
@@ -182,9 +186,7 @@ impl NetEpollHandler {
|
||||
}
|
||||
}
|
||||
|
||||
self.rx
|
||||
.queue
|
||||
.add_used(&self.mem, head_index, write_count as u32);
|
||||
self.rx.queue.add_used(&mem, head_index, write_count as u32);
|
||||
|
||||
// Mark that we have at least one pending packet and we need to interrupt the guest.
|
||||
self.rx.deferred_irqs = true;
|
||||
@@ -244,7 +246,8 @@ impl NetEpollHandler {
|
||||
}
|
||||
|
||||
fn process_tx(&mut self) -> result::Result<(), DeviceError> {
|
||||
while let Some(avail_desc) = self.tx.queue.iter(&self.mem).next() {
|
||||
let mem = self.mem.read().unwrap();
|
||||
while let Some(avail_desc) = self.tx.queue.iter(&mem).next() {
|
||||
let head_index = avail_desc.index;
|
||||
let mut read_count = 0;
|
||||
let mut next_desc = Some(avail_desc);
|
||||
@@ -266,13 +269,14 @@ impl NetEpollHandler {
|
||||
for (desc_addr, desc_len) in self.tx.iovec.drain(..) {
|
||||
let limit = cmp::min((read_count + desc_len) as usize, self.tx.frame_buf.len());
|
||||
|
||||
let read_result = self.mem.read_slice(
|
||||
let read_result = mem.read_slice(
|
||||
&mut self.tx.frame_buf[read_count..limit as usize],
|
||||
desc_addr,
|
||||
);
|
||||
match read_result {
|
||||
Ok(_) => {
|
||||
read_count += limit;
|
||||
// Increment by number of bytes actually read
|
||||
read_count += limit - read_count;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to read slice: {:?}", e);
|
||||
@@ -289,7 +293,7 @@ impl NetEpollHandler {
|
||||
}
|
||||
};
|
||||
|
||||
self.tx.queue.add_used(&self.mem, head_index, 0);
|
||||
self.tx.queue.add_used(&mem, head_index, 0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -299,34 +303,49 @@ impl NetEpollHandler {
|
||||
self.tap.read(&mut self.rx.frame_buf)
|
||||
}
|
||||
|
||||
fn register_tap_rx_listener(&self) -> std::result::Result<(), std::io::Error> {
|
||||
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)),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unregister_tap_rx_listener(&self) -> std::result::Result<(), std::io::Error> {
|
||||
epoll::ctl(
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_DEL,
|
||||
self.tap.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_TAP_EVENT)),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&mut self) -> result::Result<(), DeviceError> {
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
||||
|
||||
self.epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
||||
// Add events
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.rx.queue_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_QUEUE_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.tx.queue_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(TX_QUEUE_EVENT)),
|
||||
)
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
self.register_tap_rx_listener()
|
||||
.map_err(DeviceError::EpollCtl)?;
|
||||
self.rx_tap_listening = true;
|
||||
epoll::ctl(
|
||||
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)?;
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
self.kill_evt.as_raw_fd(),
|
||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
|
||||
@@ -337,8 +356,22 @@ impl NetEpollHandler {
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events =
|
||||
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
|
||||
let num_events = match epoll::wait(self.epoll_fd, -1, &mut events[..]) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
// It's well defined from the epoll_wait() syscall
|
||||
// documentation that the epoll loop can be interrupted
|
||||
// before any of the requested events occurred or the
|
||||
// timeout expired. In both those cases, epoll_wait()
|
||||
// returns an error of type EINTR, but this should not
|
||||
// be considered as a regular error. Instead it is more
|
||||
// appropriate to retry, by calling into epoll_wait().
|
||||
continue;
|
||||
}
|
||||
return Err(DeviceError::EpollWait(e));
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let ev_type = event.data as u16;
|
||||
@@ -352,6 +385,10 @@ impl NetEpollHandler {
|
||||
}
|
||||
|
||||
self.resume_rx().unwrap();
|
||||
if !self.rx_tap_listening {
|
||||
self.register_tap_rx_listener().unwrap();
|
||||
self.rx_tap_listening = true;
|
||||
}
|
||||
}
|
||||
TX_QUEUE_EVENT => {
|
||||
debug!("TX_QUEUE_EVENT received");
|
||||
@@ -402,11 +439,13 @@ pub struct Net {
|
||||
// The config space will only consist of the MAC address specified by the user,
|
||||
// or nothing, if no such address if provided.
|
||||
config_space: Vec<u8>,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
}
|
||||
|
||||
impl Net {
|
||||
/// Create a new virtio network device with the given TAP interface.
|
||||
pub fn new_with_tap(tap: Tap, guest_mac: Option<&MacAddr>) -> Result<Self> {
|
||||
pub fn new_with_tap(tap: Tap, guest_mac: Option<&MacAddr>, iommu: bool) -> Result<Self> {
|
||||
// Set offload flags to match the virtio features below.
|
||||
tap.set_offload(
|
||||
net_gen::TUN_F_CSUM | net_gen::TUN_F_UFO | net_gen::TUN_F_TSO4 | net_gen::TUN_F_TSO6,
|
||||
@@ -425,6 +464,10 @@ impl Net {
|
||||
| 1 << VIRTIO_NET_F_HOST_UFO
|
||||
| 1 << VIRTIO_F_VERSION_1;
|
||||
|
||||
if iommu {
|
||||
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
||||
}
|
||||
|
||||
let mut config_space;
|
||||
if let Some(mac) = guest_mac {
|
||||
config_space = Vec::with_capacity(MAC_ADDR_LEN);
|
||||
@@ -444,18 +487,25 @@ impl Net {
|
||||
avail_features,
|
||||
acked_features: 0u64,
|
||||
config_space,
|
||||
queue_evts: None,
|
||||
interrupt_cb: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new virtio network device with the given IP address and
|
||||
/// netmask.
|
||||
pub fn new(ip_addr: Ipv4Addr, netmask: Ipv4Addr, guest_mac: Option<&MacAddr>) -> Result<Self> {
|
||||
pub fn new(
|
||||
ip_addr: Ipv4Addr,
|
||||
netmask: Ipv4Addr,
|
||||
guest_mac: Option<&MacAddr>,
|
||||
iommu: bool,
|
||||
) -> Result<Self> {
|
||||
let tap = Tap::new().map_err(Error::TapOpen)?;
|
||||
tap.set_ip_addr(ip_addr).map_err(Error::TapSetIp)?;
|
||||
tap.set_netmask(netmask).map_err(Error::TapSetNetmask)?;
|
||||
tap.enable().map_err(Error::TapEnable)?;
|
||||
|
||||
Self::new_with_tap(tap, guest_mac)
|
||||
Self::new_with_tap(tap, guest_mac, iommu)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,9 +586,8 @@ impl VirtioDevice for Net {
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
status: Arc<AtomicUsize>,
|
||||
mut queues: Vec<Queue>,
|
||||
mut queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
@@ -561,7 +610,22 @@ impl VirtioDevice for Net {
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
if let Some(tap) = self.tap.take() {
|
||||
if let Some(tap) = self.tap.clone() {
|
||||
// Save the interrupt EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||
|
||||
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
|
||||
for queue_evt in queue_evts.iter() {
|
||||
// Save the queue EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
|
||||
error!("failed to clone queue EventFd: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?);
|
||||
}
|
||||
self.queue_evts = Some(tmp_queue_evts);
|
||||
|
||||
let rx_queue = queues.remove(0);
|
||||
let tx_queue = queues.remove(0);
|
||||
let rx_queue_evt = queue_evts.remove(0);
|
||||
@@ -571,9 +635,10 @@ impl VirtioDevice for Net {
|
||||
tap,
|
||||
rx: RxVirtio::new(rx_queue, rx_queue_evt),
|
||||
tx: TxVirtio::new(tx_queue, tx_queue_evt),
|
||||
interrupt_status: status,
|
||||
interrupt_cb,
|
||||
kill_evt,
|
||||
epoll_fd: 0,
|
||||
rx_tap_listening: false,
|
||||
};
|
||||
|
||||
let worker_result = thread::Builder::new()
|
||||
@@ -589,4 +654,17 @@ impl VirtioDevice for Net {
|
||||
}
|
||||
Err(ActivateError::BadActivate)
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
// Return the interrupt and queue EventFDs
|
||||
Some((
|
||||
self.interrupt_cb.take().unwrap(),
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,20 +15,19 @@ use std::io::{self, Write};
|
||||
use std::mem::size_of;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
|
||||
VirtioDeviceType, INTERRUPT_STATUS_USED_RING, VIRTIO_F_VERSION_1,
|
||||
VirtioDeviceType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use crate::{VirtioInterrupt, VirtioInterruptType};
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap, GuestUsize,
|
||||
};
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
const NUM_QUEUES: usize = 1;
|
||||
@@ -155,9 +154,8 @@ impl Request {
|
||||
|
||||
struct PmemEpollHandler {
|
||||
queue: Queue,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
disk: File,
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
queue_evt: EventFd,
|
||||
kill_evt: EventFd,
|
||||
@@ -167,8 +165,9 @@ impl PmemEpollHandler {
|
||||
fn process_queue(&mut self) -> bool {
|
||||
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
|
||||
let mut used_count = 0;
|
||||
for avail_desc in self.queue.iter(&self.mem) {
|
||||
let len = match Request::parse(&avail_desc, &self.mem) {
|
||||
let mem = self.mem.read().unwrap();
|
||||
for avail_desc in self.queue.iter(&mem) {
|
||||
let len = match Request::parse(&avail_desc, &mem) {
|
||||
Ok(ref req) if (req.type_ == RequestType::Flush) => {
|
||||
let status_code = match self.disk.sync_all() {
|
||||
Ok(()) => VIRTIO_PMEM_RESP_TYPE_OK,
|
||||
@@ -179,7 +178,7 @@ impl PmemEpollHandler {
|
||||
};
|
||||
|
||||
let resp = VirtioPmemResp { ret: status_code };
|
||||
match self.mem.write_obj(resp, req.status_addr) {
|
||||
match mem.write_obj(resp, req.status_addr) {
|
||||
Ok(_) => size_of::<VirtioPmemResp>() as u32,
|
||||
Err(e) => {
|
||||
error!("bad guest memory address: {}", e);
|
||||
@@ -203,15 +202,13 @@ impl PmemEpollHandler {
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
self.queue.add_used(&self.mem, desc_index, len);
|
||||
self.queue.add_used(&mem, desc_index, len);
|
||||
}
|
||||
used_count > 0
|
||||
}
|
||||
|
||||
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_status
|
||||
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
|
||||
(self.interrupt_cb)(&self.queue).map_err(|e| {
|
||||
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queue)).map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
@@ -241,8 +238,22 @@ impl PmemEpollHandler {
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events =
|
||||
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
|
||||
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;
|
||||
@@ -280,21 +291,31 @@ pub struct Pmem {
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
config: VirtioPmemConfig,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
}
|
||||
|
||||
impl Pmem {
|
||||
pub fn new(disk: File, addr: GuestAddress, size: GuestUsize) -> io::Result<Pmem> {
|
||||
pub fn new(disk: File, addr: GuestAddress, size: GuestUsize, iommu: bool) -> io::Result<Pmem> {
|
||||
let config = VirtioPmemConfig {
|
||||
start: addr.raw_value().to_le(),
|
||||
size: size.to_le(),
|
||||
};
|
||||
|
||||
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
|
||||
|
||||
if iommu {
|
||||
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
||||
}
|
||||
|
||||
Ok(Pmem {
|
||||
kill_evt: None,
|
||||
disk: Some(disk),
|
||||
avail_features: 1u64 << VIRTIO_F_VERSION_1,
|
||||
avail_features,
|
||||
acked_features: 0u64,
|
||||
config,
|
||||
queue_evts: None,
|
||||
interrupt_cb: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -372,9 +393,8 @@ impl VirtioDevice for Pmem {
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
status: Arc<AtomicUsize>,
|
||||
mut queues: Vec<Queue>,
|
||||
mut queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
@@ -397,12 +417,30 @@ impl VirtioDevice for Pmem {
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
if let Some(disk) = self.disk.take() {
|
||||
// Save the interrupt EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||
|
||||
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
|
||||
for queue_evt in queue_evts.iter() {
|
||||
// Save the queue EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
|
||||
error!("failed to clone queue EventFd: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?);
|
||||
}
|
||||
self.queue_evts = Some(tmp_queue_evts);
|
||||
|
||||
if let Some(disk) = self.disk.as_ref() {
|
||||
let disk = disk.try_clone().map_err(|e| {
|
||||
error!("failed cloning pmem disk: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?;
|
||||
let mut handler = PmemEpollHandler {
|
||||
queue: queues.remove(0),
|
||||
mem,
|
||||
disk,
|
||||
interrupt_status: status,
|
||||
interrupt_cb,
|
||||
queue_evt: queue_evts.remove(0),
|
||||
kill_evt,
|
||||
@@ -414,11 +452,24 @@ impl VirtioDevice for Pmem {
|
||||
|
||||
if let Err(e) = worker_result {
|
||||
error!("failed to spawn virtio_pmem worker: {}", e);
|
||||
return Err(ActivateError::BadActivate);;
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Err(ActivateError::BadActivate)
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
// Return the interrupt and queue EventFDs
|
||||
Some((
|
||||
self.interrupt_cb.take().unwrap(),
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
use std::cmp::min;
|
||||
use std::num::Wrapping;
|
||||
use std::sync::atomic::{fence, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::device::VirtioIommuRemapping;
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
|
||||
};
|
||||
@@ -41,10 +43,13 @@ unsafe impl ByteValued for Descriptor {}
|
||||
|
||||
/// A virtio descriptor chain.
|
||||
pub struct DescriptorChain<'a> {
|
||||
mem: &'a GuestMemoryMmap,
|
||||
desc_table: GuestAddress,
|
||||
queue_size: u16,
|
||||
ttl: u16, // used to prevent infinite chain cycles
|
||||
iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
|
||||
|
||||
/// Reference to guest memory
|
||||
pub mem: &'a GuestMemoryMmap,
|
||||
|
||||
/// Index into the descriptor table
|
||||
pub index: u16,
|
||||
@@ -69,6 +74,7 @@ impl<'a> DescriptorChain<'a> {
|
||||
desc_table: GuestAddress,
|
||||
queue_size: u16,
|
||||
index: u16,
|
||||
iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
|
||||
) -> Option<DescriptorChain> {
|
||||
if index >= queue_size {
|
||||
return None;
|
||||
@@ -89,16 +95,25 @@ impl<'a> DescriptorChain<'a> {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Translate address if necessary
|
||||
let desc_addr = if let Some(iommu_mapping_cb) = &iommu_mapping_cb {
|
||||
(iommu_mapping_cb)(desc.addr).unwrap()
|
||||
} else {
|
||||
desc.addr
|
||||
};
|
||||
|
||||
let chain = DescriptorChain {
|
||||
mem,
|
||||
desc_table,
|
||||
queue_size,
|
||||
ttl: queue_size,
|
||||
index,
|
||||
addr: GuestAddress(desc.addr),
|
||||
addr: GuestAddress(desc_addr),
|
||||
len: desc.len,
|
||||
flags: desc.flags,
|
||||
next: desc.next,
|
||||
iommu_mapping_cb,
|
||||
};
|
||||
|
||||
if chain.is_valid() {
|
||||
@@ -135,12 +150,17 @@ impl<'a> DescriptorChain<'a> {
|
||||
/// the head of the next _available_ descriptor chain.
|
||||
pub fn next_descriptor(&self) -> Option<DescriptorChain<'a>> {
|
||||
if self.has_next() {
|
||||
DescriptorChain::checked_new(self.mem, self.desc_table, self.queue_size, self.next).map(
|
||||
|mut c| {
|
||||
c.ttl = self.ttl - 1;
|
||||
c
|
||||
},
|
||||
DescriptorChain::checked_new(
|
||||
self.mem,
|
||||
self.desc_table,
|
||||
self.queue_size,
|
||||
self.next,
|
||||
self.iommu_mapping_cb.clone(),
|
||||
)
|
||||
.map(|mut c| {
|
||||
c.ttl = self.ttl - 1;
|
||||
c
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -156,6 +176,7 @@ pub struct AvailIter<'a, 'b> {
|
||||
last_index: Wrapping<u16>,
|
||||
queue_size: u16,
|
||||
next_avail: &'b mut Wrapping<u16>,
|
||||
iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> AvailIter<'a, 'b> {
|
||||
@@ -168,6 +189,7 @@ impl<'a, 'b> AvailIter<'a, 'b> {
|
||||
last_index: Wrapping(0),
|
||||
queue_size: 0,
|
||||
next_avail: q_next_avail,
|
||||
iommu_mapping_cb: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,8 +219,13 @@ impl<'a, 'b> Iterator for AvailIter<'a, 'b> {
|
||||
|
||||
self.next_index += Wrapping(1);
|
||||
|
||||
let ret =
|
||||
DescriptorChain::checked_new(self.mem, self.desc_table, self.queue_size, desc_index);
|
||||
let ret = DescriptorChain::checked_new(
|
||||
self.mem,
|
||||
self.desc_table,
|
||||
self.queue_size,
|
||||
desc_index,
|
||||
self.iommu_mapping_cb.clone(),
|
||||
);
|
||||
if ret.is_some() {
|
||||
*self.next_avail += Wrapping(1);
|
||||
}
|
||||
@@ -230,8 +257,10 @@ pub struct Queue {
|
||||
/// Guest physical address of the used ring
|
||||
pub used_ring: GuestAddress,
|
||||
|
||||
next_avail: Wrapping<u16>,
|
||||
next_used: Wrapping<u16>,
|
||||
pub next_avail: Wrapping<u16>,
|
||||
pub next_used: Wrapping<u16>,
|
||||
|
||||
pub iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
@@ -247,6 +276,7 @@ impl Queue {
|
||||
used_ring: GuestAddress(0),
|
||||
next_avail: Wrapping(0),
|
||||
next_used: Wrapping(0),
|
||||
iommu_mapping_cb: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +284,26 @@ impl Queue {
|
||||
self.max_size
|
||||
}
|
||||
|
||||
pub fn enable(&mut self, set: bool) {
|
||||
self.ready = set;
|
||||
|
||||
if set {
|
||||
// Translate address of descriptor table and vrings.
|
||||
if let Some(iommu_mapping_cb) = &self.iommu_mapping_cb {
|
||||
self.desc_table =
|
||||
GuestAddress((iommu_mapping_cb)(self.desc_table.raw_value()).unwrap());
|
||||
self.avail_ring =
|
||||
GuestAddress((iommu_mapping_cb)(self.avail_ring.raw_value()).unwrap());
|
||||
self.used_ring =
|
||||
GuestAddress((iommu_mapping_cb)(self.used_ring.raw_value()).unwrap());
|
||||
}
|
||||
} else {
|
||||
self.desc_table = GuestAddress(0);
|
||||
self.avail_ring = GuestAddress(0);
|
||||
self.used_ring = GuestAddress(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the actual size of the queue, as the driver may not set up a
|
||||
/// queue as big as the device allows.
|
||||
pub fn actual_size(&self) -> u16 {
|
||||
@@ -352,6 +402,7 @@ impl Queue {
|
||||
last_index: Wrapping(last_index),
|
||||
queue_size,
|
||||
next_avail: &mut self.next_avail,
|
||||
iommu_mapping_cb: self.iommu_mapping_cb.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,14 +696,16 @@ pub(crate) mod tests {
|
||||
assert!(vq.end().0 < 0x1000);
|
||||
|
||||
// index >= queue_size
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 16).is_none());
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 16, None).is_none());
|
||||
|
||||
// desc_table address is way off
|
||||
assert!(DescriptorChain::checked_new(m, GuestAddress(0x00ff_ffff_ffff), 16, 0).is_none());
|
||||
assert!(
|
||||
DescriptorChain::checked_new(m, GuestAddress(0x00ff_ffff_ffff), 16, 0, None).is_none()
|
||||
);
|
||||
|
||||
// the addr field of the descriptor is way off
|
||||
vq.dtable[0].addr.set(0x0fff_ffff_ffff);
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0).is_none());
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0, None).is_none());
|
||||
|
||||
// let's create some invalid chains
|
||||
|
||||
@@ -661,7 +714,7 @@ pub(crate) mod tests {
|
||||
vq.dtable[0].addr.set(0x1000);
|
||||
// ...but the length is too large
|
||||
vq.dtable[0].len.set(0xffff_ffff);
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0).is_none());
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0, None).is_none());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -671,7 +724,7 @@ pub(crate) mod tests {
|
||||
//..but the the index of the next descriptor is too large
|
||||
vq.dtable[0].next.set(16);
|
||||
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0).is_none());
|
||||
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0, None).is_none());
|
||||
}
|
||||
|
||||
// finally, let's test an ok chain
|
||||
@@ -680,7 +733,7 @@ pub(crate) mod tests {
|
||||
vq.dtable[0].next.set(1);
|
||||
vq.dtable[1].set(0x2000, 0x1000, 0, 0);
|
||||
|
||||
let c = DescriptorChain::checked_new(m, vq.start(), 16, 0).unwrap();
|
||||
let c = DescriptorChain::checked_new(m, vq.start(), 16, 0, None).unwrap();
|
||||
|
||||
assert_eq!(c.mem as *const GuestMemoryMmap, m as *const GuestMemoryMmap);
|
||||
assert_eq!(c.desc_table, vq.start());
|
||||
|
||||
@@ -9,18 +9,17 @@ use std::fs::File;
|
||||
use std::io;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
|
||||
use super::Error as DeviceError;
|
||||
use super::{
|
||||
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
|
||||
INTERRUPT_STATUS_USED_RING, VIRTIO_F_VERSION_1,
|
||||
VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
|
||||
};
|
||||
use crate::VirtioInterrupt;
|
||||
use crate::{VirtioInterrupt, VirtioInterruptType};
|
||||
use vm_memory::{Bytes, GuestMemoryMmap};
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
const NUM_QUEUES: usize = 1;
|
||||
@@ -33,9 +32,8 @@ const KILL_EVENT: DeviceEventT = 1;
|
||||
|
||||
struct RngEpollHandler {
|
||||
queues: Vec<Queue>,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
random_file: File,
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
queue_evt: EventFd,
|
||||
kill_evt: EventFd,
|
||||
@@ -47,14 +45,14 @@ impl RngEpollHandler {
|
||||
|
||||
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
|
||||
let mut used_count = 0;
|
||||
for avail_desc in queue.iter(&self.mem) {
|
||||
let mem = self.mem.read().unwrap();
|
||||
for avail_desc in queue.iter(&mem) {
|
||||
let mut len = 0;
|
||||
|
||||
// Drivers can only read from the random device.
|
||||
if avail_desc.is_write_only() {
|
||||
// Fill the read with data from the random device on the host.
|
||||
if self
|
||||
.mem
|
||||
if mem
|
||||
.read_from(
|
||||
avail_desc.addr,
|
||||
&mut self.random_file,
|
||||
@@ -71,15 +69,13 @@ impl RngEpollHandler {
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
queue.add_used(&self.mem, desc_index, len);
|
||||
queue.add_used(&mem, desc_index, len);
|
||||
}
|
||||
used_count > 0
|
||||
}
|
||||
|
||||
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
|
||||
self.interrupt_status
|
||||
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
|
||||
(self.interrupt_cb)(&self.queues[0]).map_err(|e| {
|
||||
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queues[0])).map_err(|e| {
|
||||
error!("Failed to signal used queue: {:?}", e);
|
||||
DeviceError::FailedSignalingUsedQueue(e)
|
||||
})
|
||||
@@ -109,8 +105,22 @@ impl RngEpollHandler {
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events =
|
||||
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
|
||||
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;
|
||||
@@ -148,19 +158,27 @@ pub struct Rng {
|
||||
random_file: Option<File>,
|
||||
avail_features: u64,
|
||||
acked_features: u64,
|
||||
queue_evts: Option<Vec<EventFd>>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
}
|
||||
|
||||
impl Rng {
|
||||
/// Create a new virtio rng device that gets random data from /dev/urandom.
|
||||
pub fn new(path: &str) -> io::Result<Rng> {
|
||||
pub fn new(path: &str, iommu: bool) -> io::Result<Rng> {
|
||||
let random_file = File::open(path)?;
|
||||
let avail_features = 1u64 << VIRTIO_F_VERSION_1;
|
||||
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
|
||||
|
||||
if iommu {
|
||||
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
||||
}
|
||||
|
||||
Ok(Rng {
|
||||
kill_evt: None,
|
||||
random_file: Some(random_file),
|
||||
avail_features,
|
||||
acked_features: 0u64,
|
||||
queue_evts: None,
|
||||
interrupt_cb: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -227,9 +245,8 @@ impl VirtioDevice for Rng {
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryMmap,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
interrupt_cb: Arc<VirtioInterrupt>,
|
||||
status: Arc<AtomicUsize>,
|
||||
queues: Vec<Queue>,
|
||||
mut queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
@@ -252,12 +269,30 @@ impl VirtioDevice for Rng {
|
||||
};
|
||||
self.kill_evt = Some(self_kill_evt);
|
||||
|
||||
if let Some(random_file) = self.random_file.take() {
|
||||
// Save the interrupt EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||
|
||||
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
|
||||
for queue_evt in queue_evts.iter() {
|
||||
// Save the queue EventFD as we need to return it on reset
|
||||
// but clone it to pass into the thread.
|
||||
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
|
||||
error!("failed to clone queue EventFd: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?);
|
||||
}
|
||||
self.queue_evts = Some(tmp_queue_evts);
|
||||
|
||||
if let Some(file) = self.random_file.as_ref() {
|
||||
let random_file = file.try_clone().map_err(|e| {
|
||||
error!("failed cloning rng source: {}", e);
|
||||
ActivateError::BadActivate
|
||||
})?;
|
||||
let mut handler = RngEpollHandler {
|
||||
queues,
|
||||
mem,
|
||||
random_file,
|
||||
interrupt_status: status,
|
||||
interrupt_cb,
|
||||
queue_evt: queue_evts.remove(0),
|
||||
kill_evt,
|
||||
@@ -269,11 +304,24 @@ impl VirtioDevice for Rng {
|
||||
|
||||
if let Err(e) = worker_result {
|
||||
error!("failed to spawn virtio_rng worker: {}", e);
|
||||
return Err(ActivateError::BadActivate);;
|
||||
return Err(ActivateError::BadActivate);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Err(ActivateError::BadActivate)
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
|
||||
if let Some(kill_evt) = self.kill_evt.take() {
|
||||
// Ignore the result because there is nothing we can do about it.
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
// Return the interrupt and queue EventFDs
|
||||
Some((
|
||||
self.interrupt_cb.take().unwrap(),
|
||||
self.queue_evts.take().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
248
vm-virtio/src/transport/mmio.rs
Normal file
248
vm-virtio/src/transport/mmio.rs
Normal file
@@ -0,0 +1,248 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use libc::EFD_NONBLOCK;
|
||||
|
||||
use crate::{
|
||||
Queue, VirtioDevice, VirtioInterrupt, VirtioInterruptType, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER,
|
||||
DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK, DEVICE_INIT,
|
||||
INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING,
|
||||
};
|
||||
use devices::{BusDevice, Interrupt};
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
use vmm_sys_util::{errno::Result, eventfd::EventFd};
|
||||
|
||||
const VENDOR_ID: u32 = 0;
|
||||
|
||||
const MMIO_MAGIC_VALUE: u32 = 0x7472_6976;
|
||||
const MMIO_VERSION: u32 = 2;
|
||||
|
||||
/// Implements the
|
||||
/// [MMIO](http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html#x1-1090002)
|
||||
/// transport for virtio devices.
|
||||
///
|
||||
/// This requires 3 points of installation to work with a VM:
|
||||
///
|
||||
/// 1. Mmio reads and writes must be sent to this device at what is referred to here as MMIO base.
|
||||
/// 1. `Mmio::queue_evts` must be installed at `virtio::NOTIFY_REG_OFFSET` offset from the MMIO
|
||||
/// base. Each event in the array must be signaled if the index is written at that offset.
|
||||
/// 1. `Mmio::interrupt_evt` must signal an interrupt that the guest driver is listening to when it
|
||||
/// is written to.
|
||||
///
|
||||
/// Typically one page (4096 bytes) of MMIO address space is sufficient to handle this transport
|
||||
/// and inner virtio device.
|
||||
pub struct MmioDevice {
|
||||
device: Box<dyn VirtioDevice>,
|
||||
device_activated: bool,
|
||||
|
||||
features_select: u32,
|
||||
acked_features_select: u32,
|
||||
queue_select: u32,
|
||||
interrupt_status: Arc<AtomicUsize>,
|
||||
interrupt_cb: Option<Arc<VirtioInterrupt>>,
|
||||
driver_status: u32,
|
||||
config_generation: u32,
|
||||
queues: Vec<Queue>,
|
||||
queue_evts: Vec<EventFd>,
|
||||
mem: Option<Arc<RwLock<GuestMemoryMmap>>>,
|
||||
}
|
||||
|
||||
impl MmioDevice {
|
||||
/// Constructs a new MMIO transport for the given virtio device.
|
||||
pub fn new(
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
device: Box<dyn VirtioDevice>,
|
||||
) -> Result<MmioDevice> {
|
||||
let mut queue_evts = Vec::new();
|
||||
for _ in device.queue_max_sizes().iter() {
|
||||
queue_evts.push(EventFd::new(EFD_NONBLOCK)?)
|
||||
}
|
||||
let queues = device
|
||||
.queue_max_sizes()
|
||||
.iter()
|
||||
.map(|&s| Queue::new(s))
|
||||
.collect();
|
||||
Ok(MmioDevice {
|
||||
device,
|
||||
device_activated: false,
|
||||
features_select: 0,
|
||||
acked_features_select: 0,
|
||||
queue_select: 0,
|
||||
interrupt_status: Arc::new(AtomicUsize::new(0)),
|
||||
interrupt_cb: None,
|
||||
driver_status: DEVICE_INIT,
|
||||
config_generation: 0,
|
||||
queues,
|
||||
queue_evts,
|
||||
mem: Some(mem),
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the list of queue events that must be triggered whenever the VM writes to
|
||||
/// `virtio::NOTIFY_REG_OFFSET` past the MMIO base. Each event must be triggered when the
|
||||
/// value being written equals the index of the event in this list.
|
||||
pub fn queue_evts(&self) -> &[EventFd] {
|
||||
self.queue_evts.as_slice()
|
||||
}
|
||||
|
||||
fn is_driver_ready(&self) -> bool {
|
||||
let ready_bits = DEVICE_ACKNOWLEDGE | DEVICE_DRIVER | DEVICE_DRIVER_OK | DEVICE_FEATURES_OK;
|
||||
self.driver_status == ready_bits && self.driver_status & DEVICE_FAILED == 0
|
||||
}
|
||||
|
||||
fn are_queues_valid(&self) -> bool {
|
||||
if let Some(mem) = self.mem.as_ref() {
|
||||
self.queues.iter().all(|q| q.is_valid(&mem.read().unwrap()))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn with_queue<U, F>(&self, d: U, f: F) -> U
|
||||
where
|
||||
F: FnOnce(&Queue) -> U,
|
||||
{
|
||||
match self.queues.get(self.queue_select as usize) {
|
||||
Some(queue) => f(queue),
|
||||
None => d,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_queue_mut<F: FnOnce(&mut Queue)>(&mut self, f: F) -> bool {
|
||||
if let Some(queue) = self.queues.get_mut(self.queue_select as usize) {
|
||||
f(queue);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assign_interrupt(&mut self, interrupt: Box<dyn Interrupt>) {
|
||||
let interrupt_status = self.interrupt_status.clone();
|
||||
let cb = Arc::new(Box::new(
|
||||
move |int_type: &VirtioInterruptType, _queue: Option<&Queue>| {
|
||||
let status = match int_type {
|
||||
VirtioInterruptType::Config => INTERRUPT_STATUS_CONFIG_CHANGED,
|
||||
VirtioInterruptType::Queue => INTERRUPT_STATUS_USED_RING,
|
||||
};
|
||||
interrupt_status.fetch_or(status as usize, Ordering::SeqCst);
|
||||
|
||||
interrupt.deliver()
|
||||
},
|
||||
) as VirtioInterrupt);
|
||||
|
||||
self.interrupt_cb = Some(cb);
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for MmioDevice {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
match offset {
|
||||
0x00..=0xff if data.len() == 4 => {
|
||||
let v = match offset {
|
||||
0x0 => MMIO_MAGIC_VALUE,
|
||||
0x04 => MMIO_VERSION,
|
||||
0x08 => self.device.device_type(),
|
||||
0x0c => VENDOR_ID, // vendor id
|
||||
0x10 => {
|
||||
self.device.features(self.features_select)
|
||||
| if self.features_select == 1 { 0x1 } else { 0x0 }
|
||||
}
|
||||
0x34 => self.with_queue(0, |q| u32::from(q.get_max_size())),
|
||||
0x44 => self.with_queue(0, |q| q.ready as u32),
|
||||
0x60 => self.interrupt_status.load(Ordering::SeqCst) as u32,
|
||||
0x70 => self.driver_status,
|
||||
0xfc => self.config_generation,
|
||||
_ => {
|
||||
warn!("unknown virtio mmio register read: 0x{:x}", offset);
|
||||
return;
|
||||
}
|
||||
};
|
||||
LittleEndian::write_u32(data, v);
|
||||
}
|
||||
0x100..=0xfff => self.device.read_config(offset - 0x100, data),
|
||||
_ => {
|
||||
warn!(
|
||||
"invalid virtio mmio read: 0x{:x}:0x{:x}",
|
||||
offset,
|
||||
data.len()
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
fn hi(v: &mut GuestAddress, x: u32) {
|
||||
*v = (*v & 0xffff_ffff) | (u64::from(x) << 32)
|
||||
}
|
||||
|
||||
fn lo(v: &mut GuestAddress, x: u32) {
|
||||
*v = (*v & !0xffff_ffff) | u64::from(x)
|
||||
}
|
||||
|
||||
let mut mut_q = false;
|
||||
match offset {
|
||||
0x00..=0xff if data.len() == 4 => {
|
||||
let v = LittleEndian::read_u32(data);
|
||||
match offset {
|
||||
0x14 => self.features_select = v,
|
||||
0x20 => self.device.ack_features(self.acked_features_select, v),
|
||||
0x24 => self.acked_features_select = v,
|
||||
0x30 => self.queue_select = v,
|
||||
0x38 => mut_q = self.with_queue_mut(|q| q.size = v as u16),
|
||||
0x44 => mut_q = self.with_queue_mut(|q| q.ready = v == 1),
|
||||
0x64 => {
|
||||
self.interrupt_status
|
||||
.fetch_and(!(v as usize), Ordering::SeqCst);
|
||||
}
|
||||
0x70 => self.driver_status = v,
|
||||
0x80 => mut_q = self.with_queue_mut(|q| lo(&mut q.desc_table, v)),
|
||||
0x84 => mut_q = self.with_queue_mut(|q| hi(&mut q.desc_table, v)),
|
||||
0x90 => mut_q = self.with_queue_mut(|q| lo(&mut q.avail_ring, v)),
|
||||
0x94 => mut_q = self.with_queue_mut(|q| hi(&mut q.avail_ring, v)),
|
||||
0xa0 => mut_q = self.with_queue_mut(|q| lo(&mut q.used_ring, v)),
|
||||
0xa4 => mut_q = self.with_queue_mut(|q| hi(&mut q.used_ring, v)),
|
||||
_ => {
|
||||
warn!("unknown virtio mmio register write: 0x{:x}", offset);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
0x100..=0xfff => return self.device.write_config(offset - 0x100, data),
|
||||
_ => {
|
||||
warn!(
|
||||
"invalid virtio mmio write: 0x{:x}:0x{:x}",
|
||||
offset,
|
||||
data.len()
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if self.device_activated && mut_q {
|
||||
warn!("virtio queue was changed after device was activated");
|
||||
}
|
||||
|
||||
if !self.device_activated && self.is_driver_ready() && self.are_queues_valid() {
|
||||
if let Some(interrupt_cb) = self.interrupt_cb.take() {
|
||||
if self.mem.is_some() {
|
||||
let mem = self.mem.as_ref().unwrap().clone();
|
||||
self.device
|
||||
.activate(
|
||||
mem,
|
||||
interrupt_cb,
|
||||
self.queues.clone(),
|
||||
self.queue_evts.split_off(0),
|
||||
)
|
||||
.expect("Failed to activate device");
|
||||
self.device_activated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,18 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(feature = "pci_support")]
|
||||
mod pci_common_config;
|
||||
#[cfg(feature = "pci_support")]
|
||||
mod pci_device;
|
||||
|
||||
#[cfg(feature = "pci_support")]
|
||||
pub use pci_common_config::VirtioPciCommonConfig;
|
||||
#[cfg(feature = "pci_support")]
|
||||
pub use pci_device::VirtioPciDevice;
|
||||
|
||||
#[cfg(feature = "mmio_support")]
|
||||
mod mmio;
|
||||
#[cfg(feature = "mmio_support")]
|
||||
pub use mmio::MmioDevice;
|
||||
#[cfg(feature = "mmio_support")]
|
||||
pub const NOTIFY_REG_OFFSET: u32 = 0x50;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
extern crate byteorder;
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
use std::sync::Arc;
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
use crate::{Queue, VirtioDevice};
|
||||
@@ -40,7 +42,7 @@ pub struct VirtioPciCommonConfig {
|
||||
pub device_feature_select: u32,
|
||||
pub driver_feature_select: u32,
|
||||
pub queue_select: u16,
|
||||
pub msix_config: u16,
|
||||
pub msix_config: Arc<AtomicU16>,
|
||||
}
|
||||
|
||||
impl VirtioPciCommonConfig {
|
||||
@@ -120,7 +122,7 @@ impl VirtioPciCommonConfig {
|
||||
fn read_common_config_word(&self, offset: u64, queues: &[Queue]) -> u16 {
|
||||
debug!("read_common_config_word: offset 0x{:x}", offset);
|
||||
match offset {
|
||||
0x10 => self.msix_config,
|
||||
0x10 => self.msix_config.load(Ordering::SeqCst),
|
||||
0x12 => queues.len() as u16, // num_queues
|
||||
0x16 => self.queue_select,
|
||||
0x18 => self.with_queue(queues, |q| q.size).unwrap_or(0),
|
||||
@@ -143,11 +145,11 @@ impl VirtioPciCommonConfig {
|
||||
fn write_common_config_word(&mut self, offset: u64, value: u16, queues: &mut Vec<Queue>) {
|
||||
debug!("write_common_config_word: offset 0x{:x}", offset);
|
||||
match offset {
|
||||
0x10 => self.msix_config = value,
|
||||
0x10 => self.msix_config.store(value, Ordering::SeqCst),
|
||||
0x16 => self.queue_select = value,
|
||||
0x18 => self.with_queue_mut(queues, |q| q.size = value),
|
||||
0x1a => self.with_queue_mut(queues, |q| q.vector = value),
|
||||
0x1c => self.with_queue_mut(queues, |q| q.ready = value == 1),
|
||||
0x1c => self.with_queue_mut(queues, |q| q.enable(value == 1)),
|
||||
_ => {
|
||||
warn!("invalid virtio register word write: 0x{:x}", offset);
|
||||
}
|
||||
@@ -252,10 +254,9 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{ActivateResult, VirtioInterrupt};
|
||||
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
use vmm_sys_util::EventFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
struct DummyDevice(u32);
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
@@ -270,9 +271,8 @@ mod tests {
|
||||
}
|
||||
fn activate(
|
||||
&mut self,
|
||||
_mem: GuestMemoryMmap,
|
||||
_mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
_interrupt_evt: Arc<VirtioInterrupt>,
|
||||
_status: Arc<AtomicUsize>,
|
||||
_queues: Vec<Queue>,
|
||||
_queue_evts: Vec<EventFd>,
|
||||
) -> ActivateResult {
|
||||
@@ -298,7 +298,7 @@ mod tests {
|
||||
device_feature_select: 0x0,
|
||||
driver_feature_select: 0x0,
|
||||
queue_select: 0xff,
|
||||
msix_config: 0,
|
||||
msix_config: Arc::new(AtomicU16::new(0)),
|
||||
};
|
||||
|
||||
let dev = &mut DummyDevice(0) as &mut dyn VirtioDevice;
|
||||
|
||||
@@ -7,15 +7,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
extern crate devices;
|
||||
#[cfg(feature = "pci_support")]
|
||||
extern crate pci;
|
||||
extern crate vm_allocator;
|
||||
extern crate vm_memory;
|
||||
extern crate vmm_sys_util;
|
||||
|
||||
use libc::EFD_NONBLOCK;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::{AtomicU16, AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
use devices::BusDevice;
|
||||
use pci::{
|
||||
@@ -26,12 +26,13 @@ use pci::{
|
||||
};
|
||||
use vm_allocator::SystemAllocator;
|
||||
use vm_memory::{Address, ByteValued, GuestAddress, GuestMemoryMmap, GuestUsize, Le32};
|
||||
use vmm_sys_util::{EventFd, Result};
|
||||
use vmm_sys_util::{errno::Result, eventfd::EventFd};
|
||||
|
||||
use super::VirtioPciCommonConfig;
|
||||
use crate::{
|
||||
Queue, VirtioDevice, VirtioDeviceType, VirtioInterrupt, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER,
|
||||
DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK, DEVICE_INIT,
|
||||
Queue, VirtioDevice, VirtioDeviceType, VirtioInterrupt, VirtioInterruptType,
|
||||
VirtioIommuRemapping, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, DEVICE_FAILED,
|
||||
DEVICE_FEATURES_OK, DEVICE_INIT, INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING,
|
||||
};
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
@@ -41,6 +42,7 @@ enum PciCapabilityType {
|
||||
IsrConfig = 3,
|
||||
DeviceConfig = 4,
|
||||
PciConfig = 5,
|
||||
SharedMemoryConfig = 8,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -50,7 +52,8 @@ struct VirtioPciCap {
|
||||
cap_len: u8, // Generic PCI field: capability length
|
||||
cfg_type: u8, // Identifies the structure.
|
||||
pci_bar: u8, // Where to find it.
|
||||
padding: [u8; 3], // Pad to full dword.
|
||||
id: u8, // Multiple capabilities of the same type
|
||||
padding: [u8; 2], // Pad to full dword.
|
||||
offset: Le32, // Offset within bar.
|
||||
length: Le32, // Length of the structure, in bytes.
|
||||
}
|
||||
@@ -67,15 +70,16 @@ impl PciCapability for VirtioPciCap {
|
||||
}
|
||||
}
|
||||
|
||||
const VIRTIO_PCI_CAPABILITY_BYTES: u8 = 16;
|
||||
const VIRTIO_PCI_CAP_LEN_OFFSET: u8 = 2;
|
||||
|
||||
impl VirtioPciCap {
|
||||
pub fn new(cfg_type: PciCapabilityType, pci_bar: u8, offset: u32, length: u32) -> Self {
|
||||
VirtioPciCap {
|
||||
cap_len: VIRTIO_PCI_CAPABILITY_BYTES,
|
||||
cap_len: (std::mem::size_of::<VirtioPciCap>() as u8) + VIRTIO_PCI_CAP_LEN_OFFSET,
|
||||
cfg_type: cfg_type as u8,
|
||||
pci_bar,
|
||||
padding: [0; 3],
|
||||
id: 0,
|
||||
padding: [0; 2],
|
||||
offset: Le32::from(offset),
|
||||
length: Le32::from(length),
|
||||
}
|
||||
@@ -112,10 +116,12 @@ impl VirtioPciNotifyCap {
|
||||
) -> Self {
|
||||
VirtioPciNotifyCap {
|
||||
cap: VirtioPciCap {
|
||||
cap_len: std::mem::size_of::<VirtioPciNotifyCap>() as u8,
|
||||
cap_len: (std::mem::size_of::<VirtioPciNotifyCap>() as u8)
|
||||
+ VIRTIO_PCI_CAP_LEN_OFFSET,
|
||||
cfg_type: cfg_type as u8,
|
||||
pci_bar,
|
||||
padding: [0; 3],
|
||||
id: 0,
|
||||
padding: [0; 2],
|
||||
offset: Le32::from(offset),
|
||||
length: Le32::from(length),
|
||||
},
|
||||
@@ -124,6 +130,45 @@ impl VirtioPciNotifyCap {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
struct VirtioPciCap64 {
|
||||
cap: VirtioPciCap,
|
||||
offset_hi: Le32,
|
||||
length_hi: Le32,
|
||||
}
|
||||
// It is safe to implement ByteValued. All members are simple numbers and any value is valid.
|
||||
unsafe impl ByteValued for VirtioPciCap64 {}
|
||||
|
||||
impl PciCapability for VirtioPciCap64 {
|
||||
fn bytes(&self) -> &[u8] {
|
||||
self.as_slice()
|
||||
}
|
||||
|
||||
fn id(&self) -> PciCapabilityID {
|
||||
PciCapabilityID::VendorSpecific
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioPciCap64 {
|
||||
pub fn new(cfg_type: PciCapabilityType, pci_bar: u8, id: u8, offset: u64, length: u64) -> Self {
|
||||
VirtioPciCap64 {
|
||||
cap: VirtioPciCap {
|
||||
cap_len: (std::mem::size_of::<VirtioPciCap64>() as u8) + VIRTIO_PCI_CAP_LEN_OFFSET,
|
||||
cfg_type: cfg_type as u8,
|
||||
pci_bar,
|
||||
id,
|
||||
padding: [0; 2],
|
||||
offset: Le32::from(offset as u32),
|
||||
length: Le32::from(length as u32),
|
||||
},
|
||||
offset_hi: Le32::from((offset >> 32) as u32),
|
||||
length_hi: Le32::from((length >> 32) as u32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum PciVirtioSubclass {
|
||||
@@ -178,7 +223,7 @@ pub struct VirtioPciDevice {
|
||||
msix_num: u16,
|
||||
|
||||
// Virtio device reference and status
|
||||
device: Box<VirtioDevice>,
|
||||
device: Box<dyn VirtioDevice>,
|
||||
device_activated: bool,
|
||||
|
||||
// PCI interrupts.
|
||||
@@ -190,15 +235,23 @@ pub struct VirtioPciDevice {
|
||||
queue_evts: Vec<EventFd>,
|
||||
|
||||
// Guest memory
|
||||
memory: Option<GuestMemoryMmap>,
|
||||
memory: Option<Arc<RwLock<GuestMemoryMmap>>>,
|
||||
|
||||
// Setting PCI BAR
|
||||
settings_bar: u8,
|
||||
|
||||
// Whether to use 64-bit bar location or 32-bit
|
||||
use_64bit_bar: bool,
|
||||
}
|
||||
|
||||
impl VirtioPciDevice {
|
||||
/// Constructs a new PCI transport for the given virtio device.
|
||||
pub fn new(memory: GuestMemoryMmap, device: Box<VirtioDevice>, msix_num: u16) -> Result<Self> {
|
||||
pub fn new(
|
||||
memory: Arc<RwLock<GuestMemoryMmap>>,
|
||||
device: Box<dyn VirtioDevice>,
|
||||
msix_num: u16,
|
||||
iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
|
||||
) -> Result<Self> {
|
||||
let mut queue_evts = Vec::new();
|
||||
for _ in device.queue_max_sizes().iter() {
|
||||
queue_evts.push(EventFd::new(EFD_NONBLOCK)?)
|
||||
@@ -206,7 +259,11 @@ impl VirtioPciDevice {
|
||||
let queues = device
|
||||
.queue_max_sizes()
|
||||
.iter()
|
||||
.map(|&s| Queue::new(s))
|
||||
.map(|&s| {
|
||||
let mut queue = Queue::new(s);
|
||||
queue.iommu_mapping_cb = iommu_mapping_cb.clone();
|
||||
queue
|
||||
})
|
||||
.collect();
|
||||
|
||||
let pci_device_id = VIRTIO_PCI_DEVICE_ID_BASE + device.device_type() as u16;
|
||||
@@ -219,18 +276,25 @@ impl VirtioPciDevice {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
// All device types *except* virtio block devices should be allocated a 64-bit bar
|
||||
// The block devices should be given a 32-bit BAR so that they are easily accessible
|
||||
// to firmware without requiring excessive identity mapping.
|
||||
let mut use_64bit_bar = true;
|
||||
let (class, subclass) = match VirtioDeviceType::from(device.device_type()) {
|
||||
VirtioDeviceType::TYPE_NET => (
|
||||
PciClassCode::NetworkController,
|
||||
&PciNetworkControllerSubclass::EthernetController as &PciSubclass,
|
||||
),
|
||||
VirtioDeviceType::TYPE_BLOCK => (
|
||||
PciClassCode::MassStorage,
|
||||
&PciMassStorageSubclass::MassStorage as &PciSubclass,
|
||||
&PciNetworkControllerSubclass::EthernetController as &dyn PciSubclass,
|
||||
),
|
||||
VirtioDeviceType::TYPE_BLOCK => {
|
||||
use_64bit_bar = false;
|
||||
(
|
||||
PciClassCode::MassStorage,
|
||||
&PciMassStorageSubclass::MassStorage as &dyn PciSubclass,
|
||||
)
|
||||
}
|
||||
_ => (
|
||||
PciClassCode::Other,
|
||||
&PciVirtioSubclass::NonTransitionalBase as &PciSubclass,
|
||||
&PciVirtioSubclass::NonTransitionalBase as &dyn PciSubclass,
|
||||
),
|
||||
};
|
||||
|
||||
@@ -254,7 +318,7 @@ impl VirtioPciDevice {
|
||||
device_feature_select: 0,
|
||||
driver_feature_select: 0,
|
||||
queue_select: 0,
|
||||
msix_config: 0,
|
||||
msix_config: Arc::new(AtomicU16::new(0)),
|
||||
},
|
||||
msix_config,
|
||||
msix_num,
|
||||
@@ -266,6 +330,7 @@ impl VirtioPciDevice {
|
||||
queue_evts,
|
||||
memory: Some(memory),
|
||||
settings_bar: 0,
|
||||
use_64bit_bar,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -290,7 +355,7 @@ impl VirtioPciDevice {
|
||||
|
||||
fn are_queues_valid(&self) -> bool {
|
||||
if let Some(mem) = self.memory.as_ref() {
|
||||
self.queues.iter().all(|q| q.is_valid(mem))
|
||||
self.queues.iter().all(|q| q.is_valid(&mem.read().unwrap()))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -376,10 +441,20 @@ impl PciDevice for VirtioPciDevice {
|
||||
) {
|
||||
self.configuration.set_irq(irq_num as u8, irq_pin);
|
||||
|
||||
let cb = Arc::new(Box::new(move |_queue: &Queue| {
|
||||
let param = InterruptParameters { msix: None };
|
||||
(irq_cb)(param)
|
||||
}) as VirtioInterrupt);
|
||||
let interrupt_status = self.interrupt_status.clone();
|
||||
let cb = Arc::new(Box::new(
|
||||
move |int_type: &VirtioInterruptType, _queue: Option<&Queue>| {
|
||||
let param = InterruptParameters { msix: None };
|
||||
|
||||
let status = match int_type {
|
||||
VirtioInterruptType::Config => INTERRUPT_STATUS_CONFIG_CHANGED,
|
||||
VirtioInterruptType::Queue => INTERRUPT_STATUS_USED_RING,
|
||||
};
|
||||
interrupt_status.fetch_or(status as usize, Ordering::SeqCst);
|
||||
|
||||
(irq_cb)(param)
|
||||
},
|
||||
) as VirtioInterrupt);
|
||||
|
||||
self.interrupt_cb = Some(cb);
|
||||
}
|
||||
@@ -393,22 +468,44 @@ impl PciDevice for VirtioPciDevice {
|
||||
|
||||
let msix_config_clone = msix_config.clone();
|
||||
|
||||
let cb = Arc::new(Box::new(move |queue: &Queue| {
|
||||
let config = &mut msix_config_clone.lock().unwrap();
|
||||
let entry = &config.table_entries[queue.vector as usize];
|
||||
let common_config_msi_vector = self.common_config.msix_config.clone();
|
||||
let cb = Arc::new(Box::new(
|
||||
move |int_type: &VirtioInterruptType, queue: Option<&Queue>| {
|
||||
let vector = match int_type {
|
||||
VirtioInterruptType::Config => {
|
||||
common_config_msi_vector.load(Ordering::SeqCst)
|
||||
}
|
||||
VirtioInterruptType::Queue => {
|
||||
if let Some(q) = queue {
|
||||
q.vector
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// In case the vector control register associated with the entry
|
||||
// has its first bit set, this means the vector is masked and the
|
||||
// device should not inject the interrupt.
|
||||
// Instead, the Pending Bit Array table is updated to reflect there
|
||||
// is a pending interrupt for this specific vector.
|
||||
if config.masked() || entry.masked() {
|
||||
config.set_pba_bit(queue.vector, false);
|
||||
return Ok(());
|
||||
}
|
||||
let config = &mut msix_config_clone.lock().unwrap();
|
||||
let entry = &config.table_entries[vector as usize];
|
||||
|
||||
(msi_cb)(InterruptParameters { msix: Some(entry) })
|
||||
}) as VirtioInterrupt);
|
||||
// If MSI-X interrupts are not enabled for this device, then simply
|
||||
// ignore the interrupt.
|
||||
if !config.enabled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// In case the vector control register associated with the entry
|
||||
// has its first bit set, this means the vector is masked and the
|
||||
// device should not inject the interrupt.
|
||||
// Instead, the Pending Bit Array table is updated to reflect there
|
||||
// is a pending interrupt for this specific vector.
|
||||
if config.masked() || entry.masked() {
|
||||
config.set_pba_bit(vector, false);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
(msi_cb)(InterruptParameters { msix: Some(entry) })
|
||||
},
|
||||
) as VirtioInterrupt);
|
||||
|
||||
self.interrupt_cb = Some(cb);
|
||||
}
|
||||
@@ -450,41 +547,58 @@ impl PciDevice for VirtioPciDevice {
|
||||
|
||||
// Allocate the virtio-pci capability BAR.
|
||||
// See http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html#x1-740004
|
||||
let virtio_pci_bar_addr = allocator
|
||||
.allocate_mmio_addresses(None, CAPABILITY_BAR_SIZE, None)
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(CAPABILITY_BAR_SIZE))?;
|
||||
let (virtio_pci_bar_addr, region_type) = if self.use_64bit_bar {
|
||||
let region_type = PciBarRegionType::Memory64BitRegion;
|
||||
let addr = allocator
|
||||
.allocate_mmio_addresses(None, CAPABILITY_BAR_SIZE, None)
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(CAPABILITY_BAR_SIZE))?;
|
||||
ranges.push((addr, CAPABILITY_BAR_SIZE, region_type));
|
||||
(addr, region_type)
|
||||
} else {
|
||||
let region_type = PciBarRegionType::Memory32BitRegion;
|
||||
let addr = allocator
|
||||
.allocate_mmio_hole_addresses(None, CAPABILITY_BAR_SIZE, None)
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(CAPABILITY_BAR_SIZE))?;
|
||||
ranges.push((addr, CAPABILITY_BAR_SIZE, region_type));
|
||||
(addr, region_type)
|
||||
};
|
||||
|
||||
let config = PciBarConfiguration::default()
|
||||
.set_register_index(0)
|
||||
.set_address(virtio_pci_bar_addr.raw_value())
|
||||
.set_size(CAPABILITY_BAR_SIZE);
|
||||
.set_size(CAPABILITY_BAR_SIZE)
|
||||
.set_region_type(region_type);
|
||||
let virtio_pci_bar =
|
||||
self.configuration.add_pci_bar(&config).map_err(|e| {
|
||||
PciDeviceError::IoRegistrationFailed(virtio_pci_bar_addr.raw_value(), e)
|
||||
})? as u8;
|
||||
|
||||
ranges.push((
|
||||
virtio_pci_bar_addr,
|
||||
CAPABILITY_BAR_SIZE,
|
||||
PciBarRegionType::Memory64BitRegion,
|
||||
));
|
||||
|
||||
// Once the BARs are allocated, the capabilities can be added to the PCI configuration.
|
||||
self.add_pci_capabilities(virtio_pci_bar)?;
|
||||
|
||||
// Allocate the device specific BARs.
|
||||
for config in self.device.get_device_bars() {
|
||||
let device_bar_addr = allocator
|
||||
.allocate_mmio_addresses(None, config.get_size(), None)
|
||||
.ok_or_else(|| PciDeviceError::IoAllocationFailed(config.get_size()))?;
|
||||
config.set_address(device_bar_addr.raw_value());
|
||||
let _device_bar = self.configuration.add_pci_bar(&config).map_err(|e| {
|
||||
PciDeviceError::IoRegistrationFailed(device_bar_addr.raw_value(), e)
|
||||
})?;
|
||||
ranges.push((
|
||||
device_bar_addr,
|
||||
config.get_size(),
|
||||
PciBarRegionType::Memory64BitRegion,
|
||||
));
|
||||
// Allocate a dedicated BAR if there are some shared memory regions.
|
||||
if let Some(shm_list) = self.device.get_shm_regions() {
|
||||
let config = PciBarConfiguration::default()
|
||||
.set_register_index(2)
|
||||
.set_address(shm_list.addr.raw_value())
|
||||
.set_size(shm_list.len);
|
||||
let virtio_pci_shm_bar =
|
||||
self.configuration.add_pci_bar(&config).map_err(|e| {
|
||||
PciDeviceError::IoRegistrationFailed(shm_list.addr.raw_value(), e)
|
||||
})? as u8;
|
||||
|
||||
for (idx, shm) in shm_list.region_list.iter().enumerate() {
|
||||
let shm_cap = VirtioPciCap64::new(
|
||||
PciCapabilityType::SharedMemoryConfig,
|
||||
virtio_pci_shm_bar,
|
||||
idx as u8,
|
||||
shm.offset,
|
||||
shm.len,
|
||||
);
|
||||
self.configuration
|
||||
.add_capability(&shm_cap)
|
||||
.map_err(PciDeviceError::CapabilitiesSetup)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ranges)
|
||||
@@ -585,7 +699,6 @@ impl PciDevice for VirtioPciDevice {
|
||||
.activate(
|
||||
mem,
|
||||
interrupt_cb,
|
||||
self.interrupt_status.clone(),
|
||||
self.queues.clone(),
|
||||
self.queue_evts.split_off(0),
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user