mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
510 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea3e2ff625 | ||
|
|
3320015793 | ||
|
|
9113544e21 | ||
|
|
8c526891ac | ||
|
|
78a30012fb | ||
|
|
c5c751c478 | ||
|
|
f92cce888b | ||
|
|
00b4d97826 | ||
|
|
3fe7d6d904 | ||
|
|
02f146fef8 | ||
|
|
36cdd67b9c | ||
|
|
92b12ce2f4 | ||
|
|
cbb588c380 | ||
|
|
1c7997c5c3 | ||
|
|
fee769bed4 | ||
|
|
6b2c2c938a | ||
|
|
1e967697c2 | ||
|
|
959ea7115f | ||
|
|
398e9338a7 | ||
|
|
5c4b5c0e40 | ||
|
|
f9bd2aaf9f | ||
|
|
f126288159 | ||
|
|
9f9a4e657f | ||
|
|
3097d78c6e | ||
|
|
7c2a252c02 | ||
|
|
a87b25a962 | ||
|
|
24ed063dbe | ||
|
|
fbac81843f | ||
|
|
610a2234dc | ||
|
|
5f18ac3bc0 | ||
|
|
4bf2d4f7dd | ||
|
|
954f3dd057 | ||
|
|
026e2c6aa8 | ||
|
|
be9f57dcf9 | ||
|
|
bd180bc3eb | ||
|
|
b9f086bcb3 | ||
|
|
d2f0e8aebb | ||
|
|
513973873c | ||
|
|
4c52fa344e | ||
|
|
e18d32bac2 | ||
|
|
59185050d5 | ||
|
|
dc72ef42dc | ||
|
|
68bb32a3e7 | ||
|
|
0f89387475 | ||
|
|
f5b2eb5c76 | ||
|
|
82ac114b8a | ||
|
|
2ffd5df018 | ||
|
|
006a12d911 | ||
|
|
bb55976454 | ||
|
|
a15e041138 | ||
|
|
0fa96402b8 | ||
|
|
bc0ef1d8ba | ||
|
|
86153225cf | ||
|
|
ddc3f194aa | ||
|
|
c6cc3d3ba9 | ||
|
|
0fbb3e2c2c | ||
|
|
330e1aac36 | ||
|
|
5fddf76a3b | ||
|
|
b50dfb3538 | ||
|
|
502995746a | ||
|
|
8aa2d7ab2a | ||
|
|
bd8c28d341 | ||
|
|
422906a0c7 | ||
|
|
b7512263be | ||
|
|
7e749c0390 | ||
|
|
61a5bae25c | ||
|
|
a2438700e4 | ||
|
|
bb4af57219 | ||
|
|
824e83ab0d | ||
|
|
29675cfe68 | ||
|
|
43632f255b | ||
|
|
a1941ad10b | ||
|
|
70db454b90 | ||
|
|
7b3ffd89a5 | ||
|
|
c1f18fa634 | ||
|
|
efb92d409f | ||
|
|
e4aa3617c7 | ||
|
|
d7e8cd8258 | ||
|
|
8803e4a2e7 | ||
|
|
d2a01f7cec | ||
|
|
dec4a82058 | ||
|
|
5fc143205c | ||
|
|
b73d94f969 | ||
|
|
ead4f767ff | ||
|
|
bc6acb842f | ||
|
|
d89f1f4f21 | ||
|
|
dc90744ec3 | ||
|
|
18340d9761 | ||
|
|
cfaa192eb4 | ||
|
|
c67f799717 | ||
|
|
b47efc3bc2 | ||
|
|
498f35a1ab | ||
|
|
2d61bc36ed | ||
|
|
46447962b8 | ||
|
|
9a6bc025a7 | ||
|
|
de1abe0e30 | ||
|
|
e97cee99ef | ||
|
|
3103526153 | ||
|
|
08ff89ba6e | ||
|
|
514f36fb5a | ||
|
|
fe3506976e | ||
|
|
94929889ac | ||
|
|
8452edfcc7 | ||
|
|
466cc5e043 | ||
|
|
034c674c4c | ||
|
|
b785e00317 | ||
|
|
7f6731cd05 | ||
|
|
457fd9ef96 | ||
|
|
eea45a2c78 | ||
|
|
11c17ca319 | ||
|
|
249e362c70 | ||
|
|
b3e5738b40 | ||
|
|
1dd53c3d24 | ||
|
|
130c988380 | ||
|
|
8c76a3e4b5 | ||
|
|
af5a9677c8 | ||
|
|
1d89f98edf | ||
|
|
287dbd4fc9 | ||
|
|
c50ea2c708 | ||
|
|
14b45e4d2e | ||
|
|
519476e842 | ||
|
|
5fec858130 | ||
|
|
67f22b6aa4 | ||
|
|
1eb4133034 | ||
|
|
9f3bea3e3f | ||
|
|
e7c7a304e8 | ||
|
|
b5cce0d371 | ||
|
|
7c608f6380 | ||
|
|
3ad8d24943 | ||
|
|
56c6c02724 | ||
|
|
19b0ea842b | ||
|
|
16bd88b4b5 | ||
|
|
09136c50b5 | ||
|
|
380b7d398c | ||
|
|
764959c9a8 | ||
|
|
7d84654a79 | ||
|
|
246cb94dc5 | ||
|
|
165c2c476f | ||
|
|
5e8f380698 | ||
|
|
e9b2639757 | ||
|
|
98d0aabb99 | ||
|
|
d206586683 | ||
|
|
19f2800d9e | ||
|
|
08cf983d42 | ||
|
|
243dc5918c | ||
|
|
544de7d000 | ||
|
|
796db588ea | ||
|
|
397c76dd20 | ||
|
|
cdd3ff5e5a | ||
|
|
6cb76abbf1 | ||
|
|
3a5e5364b9 | ||
|
|
f2b2d033f7 | ||
|
|
c41461c9c2 | ||
|
|
9257322330 | ||
|
|
220455caaf | ||
|
|
c206c14318 | ||
|
|
777757254b | ||
|
|
9c42d98e6a | ||
|
|
254db7b96a | ||
|
|
b3a73d6634 | ||
|
|
9cd214b0a1 | ||
|
|
9f969ee18d | ||
|
|
c3fcddf830 | ||
|
|
11d98fccac | ||
|
|
a8fa2af64b | ||
|
|
dc723171a7 | ||
|
|
52eebaf6b2 | ||
|
|
380ba564f4 | ||
|
|
385f9a9aa9 | ||
|
|
d784bf0c75 | ||
|
|
cf6115a73c | ||
|
|
0991b881de | ||
|
|
e048da6f73 | ||
|
|
6a08133efb | ||
|
|
2e50f5769f | ||
|
|
42e9632c53 | ||
|
|
46c5fb5f2c | ||
|
|
81b41b55fd | ||
|
|
112a971c56 | ||
|
|
9b87a7e699 | ||
|
|
1bd8afeffa | ||
|
|
3414586995 | ||
|
|
06e8d1c40c | ||
|
|
b70b8dfa4c | ||
|
|
57e450a7a8 | ||
|
|
d82846c954 | ||
|
|
714b61846c | ||
|
|
6bb3ad1b96 | ||
|
|
dd820d69ba | ||
|
|
77c80f67ed | ||
|
|
eb4dd90532 | ||
|
|
d3cd25566b | ||
|
|
925ab090af | ||
|
|
8caf29ec01 | ||
|
|
091ce85473 | ||
|
|
133ac463c2 | ||
|
|
400837ff99 | ||
|
|
a9e41c417a | ||
|
|
1845e63e7c | ||
|
|
f13d8f1412 | ||
|
|
bfe0106566 | ||
|
|
7c46e610e7 | ||
|
|
b13de200f5 | ||
|
|
c3d69a9bac | ||
|
|
51a9f78625 | ||
|
|
89b429c768 | ||
|
|
f25315d151 | ||
|
|
193c006669 | ||
|
|
5d0d56f50b | ||
|
|
30b6e412af | ||
|
|
6c90623c8e | ||
|
|
94fe22da62 | ||
|
|
036e7e3797 | ||
|
|
5a811af099 | ||
|
|
7ac2e56fa6 | ||
|
|
eb8f959361 | ||
|
|
27fd114cbf | ||
|
|
ba488f5c33 | ||
|
|
af1f94e5b8 | ||
|
|
22621df084 | ||
|
|
972b4bf248 | ||
|
|
f4aa3de335 | ||
|
|
b2d25a582d | ||
|
|
a3262ad570 | ||
|
|
034283fc83 | ||
|
|
860939d677 | ||
|
|
a5a41bf797 | ||
|
|
aabfc9513e | ||
|
|
3fe9b87736 | ||
|
|
9e338d86f5 | ||
|
|
5f9c6bc84f | ||
|
|
de38e4b777 | ||
|
|
6c4947e9ba | ||
|
|
63df8599dc | ||
|
|
b6003cb428 | ||
|
|
340a51d08e | ||
|
|
507a03e7ee | ||
|
|
fe86f90ba6 | ||
|
|
3769b18a70 | ||
|
|
6d74393833 | ||
|
|
26670e4778 | ||
|
|
555c4c41ab | ||
|
|
b82f25572b | ||
|
|
584784a0f8 | ||
|
|
4fd5070f5d | ||
|
|
241d1d5cdb | ||
|
|
c07671edb4 | ||
|
|
8093820965 | ||
|
|
86cf50565e | ||
|
|
95fd684ad7 | ||
|
|
ce8e76cf94 | ||
|
|
3f8cd52ffd | ||
|
|
f9d3c73c15 | ||
|
|
7e25cc2aa0 | ||
|
|
8b86c7724b | ||
|
|
ea23c16c5a | ||
|
|
3def18f502 | ||
|
|
2bf6f9300a | ||
|
|
fd43b79f96 | ||
|
|
a70808bae9 | ||
|
|
97d617f071 | ||
|
|
0194a635f7 | ||
|
|
e5d0acf0a9 | ||
|
|
622a1a3735 | ||
|
|
f5abb168e3 | ||
|
|
1e3ef1a548 | ||
|
|
275a63655b | ||
|
|
16651db712 | ||
|
|
d03e43facc | ||
|
|
c2ed82f3af | ||
|
|
5fc71dec34 | ||
|
|
d10f20eb71 | ||
|
|
6c0dedd560 | ||
|
|
b29edfbee8 | ||
|
|
75e1dc2bce | ||
|
|
6ba4f6de95 | ||
|
|
de1f055481 | ||
|
|
1a94c6c932 | ||
|
|
1aeaee221d | ||
|
|
862a056105 | ||
|
|
fe704bd44a | ||
|
|
f6cd3bd86d | ||
|
|
0816c8292f | ||
|
|
319538fbed | ||
|
|
4f1e74b553 | ||
|
|
24f8d15b14 | ||
|
|
f4b0443489 | ||
|
|
030d84eb08 | ||
|
|
4847f5c4f6 | ||
|
|
bf6c9e6447 | ||
|
|
f6d99d9a9b | ||
|
|
e38a69f0d2 | ||
|
|
4f96fa15a8 | ||
|
|
ed1f906d46 | ||
|
|
3b64b7723b | ||
|
|
6925750622 | ||
|
|
26808bfb71 | ||
|
|
b89657ea22 | ||
|
|
1ef2b488c7 | ||
|
|
7be69edf51 | ||
|
|
c022063ae8 | ||
|
|
e6aa57e3b9 | ||
|
|
512591ba1c | ||
|
|
e7e856d8ac | ||
|
|
a84bc06874 | ||
|
|
a750e6ec15 | ||
|
|
82d275ccaa | ||
|
|
10ab87d6a3 | ||
|
|
dad55fcef2 | ||
|
|
5e9886bba4 | ||
|
|
ce29afd0eb | ||
|
|
493178c972 | ||
|
|
533710f0cd | ||
|
|
2b82384a90 | ||
|
|
e7ee88e8ac | ||
|
|
6c4144e943 | ||
|
|
8a6add9c79 | ||
|
|
76e557814d | ||
|
|
a28ad40671 | ||
|
|
cb9726b4a4 | ||
|
|
b5c5e9b34b | ||
|
|
a5552b87f4 | ||
|
|
79021ebb16 | ||
|
|
00e05c0278 | ||
|
|
691dd926e4 | ||
|
|
b6783d4477 | ||
|
|
077970f99b | ||
|
|
f43df1d415 | ||
|
|
8a80bea4c5 | ||
|
|
108af5a293 | ||
|
|
c8f4fece1a | ||
|
|
549681de59 | ||
|
|
c4ad9b45d0 | ||
|
|
d485896edd | ||
|
|
e1bb5e71bf | ||
|
|
7966925c1c | ||
|
|
f409c4b63b | ||
|
|
d0cfa3a014 | ||
|
|
e99540e5e9 | ||
|
|
101cfb9650 | ||
|
|
11c593e3b9 | ||
|
|
f3b0f59646 | ||
|
|
f09f5af16a | ||
|
|
00684e6d19 | ||
|
|
fa7a000dbe | ||
|
|
85a3623b44 | ||
|
|
66add03d38 | ||
|
|
3fa02b34ca | ||
|
|
5668f02eb6 | ||
|
|
045964deee | ||
|
|
a5e2460d95 | ||
|
|
fd854c7339 | ||
|
|
2d15a2cd45 | ||
|
|
fbe3e4d642 | ||
|
|
28d4957ba5 | ||
|
|
f39d5eeaf0 | ||
|
|
639db35635 | ||
|
|
6482f7e8c3 | ||
|
|
3214dc6431 | ||
|
|
97686ef7a3 | ||
|
|
d3e4f9cc1b | ||
|
|
fcf229a33a | ||
|
|
310382a918 | ||
|
|
6d374d8805 | ||
|
|
11fa24cdcb | ||
|
|
f0be099461 | ||
|
|
3f95ada71e | ||
|
|
c6d5cd78a7 | ||
|
|
80dcb165ba | ||
|
|
6922e25e78 | ||
|
|
5997cfacbf | ||
|
|
55678b23ba | ||
|
|
b15e5923ab | ||
|
|
fd81a23fcc | ||
|
|
acc25def7d | ||
|
|
c3f1c3ee3d | ||
|
|
58e6a289ab | ||
|
|
7c6c45128d | ||
|
|
625b18d2a2 | ||
|
|
fbdc5d4487 | ||
|
|
6bee8ac702 | ||
|
|
749eb423cf | ||
|
|
44fbd60b7a | ||
|
|
98c48d40d2 | ||
|
|
fdcc8539cd | ||
|
|
f7b9a6e577 | ||
|
|
c9e989de6e | ||
|
|
2501426e47 | ||
|
|
dad1ab1227 | ||
|
|
97a1a70275 | ||
|
|
3a60c65369 | ||
|
|
719cae217e | ||
|
|
41bae9fe93 | ||
|
|
f093ffcbef | ||
|
|
2f5e48d295 | ||
|
|
4d52150b87 | ||
|
|
c721c0d88f | ||
|
|
b4b5f16268 | ||
|
|
b8e84f09be | ||
|
|
ab9fc3a8a0 | ||
|
|
1e3d21e504 | ||
|
|
72620295dc | ||
|
|
67054bf78b | ||
|
|
39ab482c47 | ||
|
|
521a0d1ade | ||
|
|
2529ffd593 | ||
|
|
f1856bb27c | ||
|
|
89ff0627e6 | ||
|
|
dd37f33a43 | ||
|
|
87f4399853 | ||
|
|
ec36113751 | ||
|
|
f6b5356dd2 | ||
|
|
0886eb4301 | ||
|
|
0e7c8a1d8a | ||
|
|
cb984e163c | ||
|
|
1363891df6 | ||
|
|
f898e660b6 | ||
|
|
d2c2054047 | ||
|
|
2d6287d159 | ||
|
|
61d9debb5e | ||
|
|
e50e5d8081 | ||
|
|
7ff5ebee1d | ||
|
|
6a446b6158 | ||
|
|
f9be73ae06 | ||
|
|
c85488f22e | ||
|
|
c420dcd41b | ||
|
|
dccc00f208 | ||
|
|
7e487dfa17 | ||
|
|
dcc2294949 | ||
|
|
8f0464b635 | ||
|
|
9c68e86f83 | ||
|
|
cd116cb24f | ||
|
|
da376a4b37 | ||
|
|
fbcf5fb37d | ||
|
|
23632b0bf4 | ||
|
|
d05b05b050 | ||
|
|
1811e24a4b | ||
|
|
d245e62427 | ||
|
|
f1e4a82fd3 | ||
|
|
5466e873c4 | ||
|
|
1708561c74 | ||
|
|
c72bf0b32d | ||
|
|
f40dd4a993 | ||
|
|
f8ac38d113 | ||
|
|
b072671e82 | ||
|
|
6f49d7f192 | ||
|
|
0f71956d6d | ||
|
|
46c9b9693c | ||
|
|
cdafe5344d | ||
|
|
f48942ce3f | ||
|
|
d3fade85a7 | ||
|
|
b65b866895 | ||
|
|
0718067851 | ||
|
|
7d60ab70e6 | ||
|
|
084eb0792d | ||
|
|
b8f5687707 | ||
|
|
1091494320 | ||
|
|
3f2ca5375e | ||
|
|
efc24119f3 | ||
|
|
b750c332aa | ||
|
|
05ec6190da | ||
|
|
1db30405e1 | ||
|
|
3e35529842 | ||
|
|
96cc1ba76c | ||
|
|
022f375ef8 | ||
|
|
81b95023c4 | ||
|
|
f15ca1aec3 | ||
|
|
cb8a728dfb | ||
|
|
80aa91f24c | ||
|
|
d9f48505fe | ||
|
|
1d098949b9 | ||
|
|
18a4d732b9 | ||
|
|
ba6bfee4ff | ||
|
|
eecd879b36 | ||
|
|
57fb97e41f | ||
|
|
97b23f1448 | ||
|
|
0310c5726f | ||
|
|
5627c26405 | ||
|
|
07560c596d | ||
|
|
1a4c890f83 | ||
|
|
e51fb0ee36 | ||
|
|
aa6c486a6b | ||
|
|
b765acd608 | ||
|
|
4fb86e9915 | ||
|
|
7d305c5bbf | ||
|
|
3c4fa14c3e | ||
|
|
b77f779c90 | ||
|
|
1fe2771a0d | ||
|
|
2e4079becb | ||
|
|
d32de07be7 | ||
|
|
6ec83c7d8e | ||
|
|
68dd467104 | ||
|
|
a9aed1a9bc | ||
|
|
84a6da5e93 | ||
|
|
6930370a03 | ||
|
|
89f2a4882e | ||
|
|
307a0166c5 | ||
|
|
845bdfb1b2 | ||
|
|
e136d343ab | ||
|
|
9b722bbcf6 | ||
|
|
927df4e643 | ||
|
|
8e46c03453 | ||
|
|
adb318f4cd | ||
|
|
09f3658999 | ||
|
|
6362b711c6 | ||
|
|
d8cd403c5d | ||
|
|
09cf8c3118 | ||
|
|
da3693f164 | ||
|
|
2b3e10ab88 | ||
|
|
3d5718bd87 |
3
.github/workflows/audit.yaml
vendored
3
.github/workflows/audit.yaml
vendored
@@ -4,12 +4,13 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- '**/Cargo.toml'
|
- '**/Cargo.toml'
|
||||||
- '**/Cargo.lock'
|
- '**/Cargo.lock'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
security_audit:
|
security_audit:
|
||||||
name: Audit
|
name: Audit
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions-rs/audit-check@v1
|
- uses: actions-rust-lang/audit@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
14
.github/workflows/build.yaml
vendored
14
.github/workflows/build.yaml
vendored
@@ -1,9 +1,11 @@
|
|||||||
name: Cloud Hypervisor Build
|
name: Cloud Hypervisor Build
|
||||||
on: [pull_request, create]
|
on: [pull_request, merge_group]
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
@@ -13,7 +15,7 @@ jobs:
|
|||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
- nightly
|
- nightly
|
||||||
- "1.70"
|
- "1.77.0"
|
||||||
target:
|
target:
|
||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
- x86_64-unknown-linux-musl
|
- x86_64-unknown-linux-musl
|
||||||
@@ -27,11 +29,10 @@ jobs:
|
|||||||
run: sudo apt install -y musl-tools
|
run: sudo apt install -y musl-tools
|
||||||
|
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||||
uses: actions-rs/toolchain@v1
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
override: true
|
|
||||||
|
|
||||||
- name: Build (default features)
|
- name: Build (default features)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks
|
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
@@ -48,6 +49,9 @@ jobs:
|
|||||||
- name: Build (default features + guest_debug)
|
- name: Build (default features + guest_debug)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
|
- name: Build (default features + pvmemcontrol)
|
||||||
|
run: cargo rustc --locked --bin cloud-hypervisor --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Build (mshv)
|
- name: Build (mshv)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/dco.yaml
vendored
6
.github/workflows/dco.yaml
vendored
@@ -1,8 +1,9 @@
|
|||||||
name: DCO
|
name: DCO
|
||||||
on:
|
on: [pull_request, merge_group]
|
||||||
pull_request:
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
|
name: DCO Check ("Signed-Off-By")
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -11,6 +12,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
- name: Check DCO
|
- name: Check DCO
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
6
.github/workflows/docker-image.yaml
vendored
6
.github/workflows/docker-image.yaml
vendored
@@ -1,11 +1,13 @@
|
|||||||
name: Cloud Hypervisor's Docker image update
|
name: Cloud Hypervisor's Docker image update
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: main
|
branches: main
|
||||||
paths: resources/Dockerfile
|
paths: resources/Dockerfile
|
||||||
pull_request:
|
pull_request:
|
||||||
paths: resources/Dockerfile
|
paths: resources/Dockerfile
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
@@ -39,7 +41,7 @@ jobs:
|
|||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
# generate Docker tags based on the following events/attributes
|
# generate Docker tags based on the following events/attributes
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value={{date 'YYYYMMDD'}}-0
|
type=raw,value=20240507-0
|
||||||
type=sha
|
type=sha
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
|
|||||||
9
.github/workflows/fuzz-build.yaml
vendored
9
.github/workflows/fuzz-build.yaml
vendored
@@ -1,9 +1,11 @@
|
|||||||
name: Cloud Hypervisor Cargo Fuzz Build
|
name: Cloud Hypervisor Cargo Fuzz Build
|
||||||
on: [pull_request, create]
|
on: [pull_request, merge_group]
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
name: Cargo Fuzz Build
|
name: Cargo Fuzz Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
@@ -18,11 +20,10 @@ jobs:
|
|||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||||
uses: actions-rs/toolchain@v1
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
override: true
|
|
||||||
- name: Install Cargo fuzz
|
- name: Install Cargo fuzz
|
||||||
run: cargo install cargo-fuzz
|
run: cargo install cargo-fuzz
|
||||||
- name: Fuzz Build
|
- name: Fuzz Build
|
||||||
|
|||||||
1
.github/workflows/gitlint.yaml
vendored
1
.github/workflows/gitlint.yaml
vendored
@@ -1,5 +1,4 @@
|
|||||||
name: Commit messages check
|
name: Commit messages check
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
|||||||
1
.github/workflows/hadolint.yaml
vendored
1
.github/workflows/hadolint.yaml
vendored
@@ -1,5 +1,4 @@
|
|||||||
name: Lint Dockerfile
|
name: Lint Dockerfile
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
|
|||||||
54
.github/workflows/integration-arm64.yaml
vendored
Normal file
54
.github/workflows/integration-arm64.yaml
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
name: Cloud Hypervisor Tests (ARM64)
|
||||||
|
on: [pull_request, merge_group]
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
timeout-minutes: 60
|
||||||
|
name: Tests (ARM64)
|
||||||
|
runs-on: focal-arm64
|
||||||
|
steps:
|
||||||
|
- name: Fix workspace permissions
|
||||||
|
run: sudo chown -R github-runner:github-runner ${GITHUB_WORKSPACE}
|
||||||
|
- name: Code checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Run unit tests (musl)
|
||||||
|
run: scripts/dev_cli.sh tests --unit --libc musl
|
||||||
|
- name: Load openvswitch module
|
||||||
|
run: sudo modprobe openvswitch
|
||||||
|
- name: Run integration tests (musl)
|
||||||
|
timeout-minutes: 30
|
||||||
|
run: scripts/dev_cli.sh tests --integration --libc musl
|
||||||
|
- name: Install Azure CLI
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
run: |
|
||||||
|
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
|
||||||
|
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
|
||||||
|
echo "deb [arch=arm64] https://packages.microsoft.com/repos/azure-cli/ focal main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y azure-cli
|
||||||
|
- name: Download Windows image
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
IMG_BASENAME=windows-11-iot-enterprise-aarch64.raw
|
||||||
|
IMG_PATH=$HOME/workloads/$IMG_BASENAME
|
||||||
|
IMG_GZ_PATH=$HOME/workloads/$IMG_BASENAME.gz
|
||||||
|
IMG_GZ_BLOB_NAME=windows-11-iot-enterprise-aarch64-9-min.raw.gz
|
||||||
|
cp "scripts/$IMG_BASENAME.sha1" "$HOME/workloads/"
|
||||||
|
pushd "$HOME/workloads"
|
||||||
|
if sha1sum "$IMG_BASENAME.sha1" --check; then
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
popd
|
||||||
|
mkdir -p "$HOME/workloads"
|
||||||
|
az storage blob download --container-name private-images --file "$IMG_GZ_PATH" --name "$IMG_GZ_BLOB_NAME" --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
|
||||||
|
gzip -d $IMG_GZ_PATH
|
||||||
|
- name: Run Windows guest integration tests
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
timeout-minutes: 30
|
||||||
|
run: scripts/dev_cli.sh tests --integration-windows --libc musl
|
||||||
22
.github/workflows/integration-metrics.yaml
vendored
Normal file
22
.github/workflows/integration-metrics.yaml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: Cloud Hypervisor Tests (Metrics)
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Tests (Metrics)
|
||||||
|
runs-on: jammy-metrics
|
||||||
|
env:
|
||||||
|
METRICS_PUBLISH_KEY: ${{ secrets.METRICS_PUBLISH_KEY }}
|
||||||
|
steps:
|
||||||
|
- name: Code checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Run metrics tests
|
||||||
|
timeout-minutes: 60
|
||||||
|
run: scripts/dev_cli.sh tests --metrics -- -- --report-file /root/workloads/metrics.json
|
||||||
|
- name: Upload metrics report
|
||||||
|
run: 'curl -X PUT https://ch-metrics.azurewebsites.net/api/publishmetrics -H "x-functions-key: $METRICS_PUBLISH_KEY" -T ~/workloads/metrics.json'
|
||||||
28
.github/workflows/integration-rate-limiter.yaml
vendored
Normal file
28
.github/workflows/integration-rate-limiter.yaml
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
name: Cloud Hypervisor Tests (Rate-Limiter)
|
||||||
|
on: [merge_group, pull_request]
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Tests (Rate-Limiter)
|
||||||
|
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'jammy-rate-limiter' }}
|
||||||
|
env:
|
||||||
|
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
||||||
|
steps:
|
||||||
|
- name: Fix workspace permissions
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
run: sudo chown -R github-runner:github-runner ${GITHUB_WORKSPACE}
|
||||||
|
- name: Code checkout
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Run rate-limiter integration tests
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
timeout-minutes: 10
|
||||||
|
run: scripts/dev_cli.sh tests --integration-rate-limiter
|
||||||
|
- name: Skipping build for PR
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
run: echo "Skipping build for PR"
|
||||||
32
.github/workflows/integration-sgx.yaml
vendored
Normal file
32
.github/workflows/integration-sgx.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Cloud Hypervisor Tests (SGX)
|
||||||
|
on: [merge_group, pull_request]
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Tests (SGX)
|
||||||
|
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'jammy-sgx' }}
|
||||||
|
env:
|
||||||
|
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
||||||
|
steps:
|
||||||
|
- name: Fix workspace permissions
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
run: sudo chown -R github-runner:github-runner ${GITHUB_WORKSPACE}
|
||||||
|
- name: Code checkout
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Run SGX integration tests
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
timeout-minutes: 10
|
||||||
|
run: scripts/dev_cli.sh tests --integration-sgx
|
||||||
|
- name: Run SGX integration tests for musl
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
timeout-minutes: 10
|
||||||
|
run: scripts/dev_cli.sh tests --integration-sgx --libc musl
|
||||||
|
- name: Skipping build for PR
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
run: echo "Skipping build for PR"
|
||||||
32
.github/workflows/integration-vfio.yaml
vendored
Normal file
32
.github/workflows/integration-vfio.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Cloud Hypervisor Tests (VFIO)
|
||||||
|
on: [merge_group, pull_request]
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Tests (VFIO)
|
||||||
|
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'jammy-vfio' }}
|
||||||
|
env:
|
||||||
|
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
||||||
|
steps:
|
||||||
|
- name: Fix workspace permissions
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
run: sudo chown -R github-runner:github-runner ${GITHUB_WORKSPACE}
|
||||||
|
- name: Code checkout
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Run VFIO integration tests
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
run: scripts/dev_cli.sh tests --integration-vfio
|
||||||
|
- name: Run VFIO integration tests for musl
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
run: scripts/dev_cli.sh tests --integration-vfio --libc musl
|
||||||
|
- name: Skipping build for PR
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
run: echo "Skipping build for PR"
|
||||||
50
.github/workflows/integration-windows.yaml
vendored
Normal file
50
.github/workflows/integration-windows.yaml
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
name: Cloud Hypervisor Tests (Windows Guest)
|
||||||
|
on: [merge_group, pull_request]
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Tests (Windows Guest)
|
||||||
|
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'garm-jammy-16' }}
|
||||||
|
steps:
|
||||||
|
- name: Code checkout
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Install Docker
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get -y install ca-certificates curl gnupg
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||||
|
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
|
||||||
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt install -y docker-ce docker-ce-cli
|
||||||
|
- name: Install Azure CLI
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
run: |
|
||||||
|
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
|
||||||
|
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
|
||||||
|
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ jammy main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y azure-cli
|
||||||
|
- name: Download Windows image
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
run: |
|
||||||
|
mkdir $HOME/workloads
|
||||||
|
az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2022-amd64-2.raw" --name windows-server-2022-amd64-2.raw --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
|
||||||
|
- name: Run Windows guest integration tests
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
run: scripts/dev_cli.sh tests --integration-windows
|
||||||
|
- name: Run Windows guest integration tests for musl
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
run: scripts/dev_cli.sh tests --integration-windows --libc musl
|
||||||
|
- name: Skipping build for PR
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
run: echo "Skipping build for PR"
|
||||||
52
.github/workflows/integration-x86-64.yaml
vendored
Normal file
52
.github/workflows/integration-x86-64.yaml
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
name: Cloud Hypervisor Tests (x86-64)
|
||||||
|
on: [pull_request, merge_group]
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
timeout-minutes: 60
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
runner: ['garm-jammy', "garm-jammy-amd"]
|
||||||
|
libc: ["musl", 'gnu']
|
||||||
|
name: Tests (x86-64)
|
||||||
|
runs-on: ${{ github.event_name == 'pull_request' && !(matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') && 'ubuntu-latest' || format('{0}-16', matrix.runner) }}
|
||||||
|
steps:
|
||||||
|
- name: Code checkout
|
||||||
|
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Install Docker
|
||||||
|
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get -y install ca-certificates curl gnupg
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||||
|
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
|
||||||
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt install -y docker-ce docker-ce-cli
|
||||||
|
- name: Prepare for VDPA
|
||||||
|
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||||
|
run: scripts/prepare_vdpa.sh
|
||||||
|
- name: Run unit tests
|
||||||
|
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||||
|
run: scripts/dev_cli.sh tests --unit --libc ${{ matrix.libc }}
|
||||||
|
- name: Load openvswitch module
|
||||||
|
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||||
|
run: sudo modprobe openvswitch
|
||||||
|
- name: Run integration tests
|
||||||
|
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||||
|
timeout-minutes: 40
|
||||||
|
run: scripts/dev_cli.sh tests --integration --libc ${{ matrix.libc }}
|
||||||
|
- name: Run live-migration integration tests
|
||||||
|
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||||
|
timeout-minutes: 20
|
||||||
|
run: scripts/dev_cli.sh tests --integration-live-migration --libc ${{ matrix.libc }}
|
||||||
|
- name: Skipping build for PR
|
||||||
|
if: ${{ github.event_name == 'pull_request' && matrix.runner != 'garm-jammy' && matrix.libc != 'gnu' }}
|
||||||
|
run: echo "Skipping build for PR"
|
||||||
4
.github/workflows/openapi.yaml
vendored
4
.github/workflows/openapi.yaml
vendored
@@ -1,7 +1,5 @@
|
|||||||
name: Cloud Hypervisor OpenAPI Validation
|
name: Cloud Hypervisor OpenAPI Validation
|
||||||
|
on: [pull_request, merge_group]
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Validate:
|
Validate:
|
||||||
|
|||||||
17
.github/workflows/quality.yaml
vendored
17
.github/workflows/quality.yaml
vendored
@@ -1,9 +1,11 @@
|
|||||||
name: Cloud Hypervisor Quality Checks
|
name: Cloud Hypervisor Quality Checks
|
||||||
on: [pull_request, create]
|
on: [pull_request, merge_group]
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
name: Quality (clippy, rustfmt)
|
name: Quality (clippy, rustfmt)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: ${{ matrix.experimental }}
|
continue-on-error: ${{ matrix.experimental }}
|
||||||
@@ -46,8 +48,8 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
- name: Debug Check (default features)
|
- name: Bisectability Check (default features)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ github.event_name == 'pull_request' && matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
commits=$(git rev-list origin/${{ github.base_ref }}..${{ github.sha }})
|
commits=$(git rev-list origin/${{ github.base_ref }}..${{ github.sha }})
|
||||||
@@ -78,6 +80,13 @@ jobs:
|
|||||||
command: clippy
|
command: clippy
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
|
- name: Clippy (default features + pvmemcontrol)
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
|
command: clippy
|
||||||
|
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Clippy (default features + tracing)
|
- name: Clippy (default features + tracing)
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
190
.github/workflows/release.yaml
vendored
190
.github/workflows/release.yaml
vendored
@@ -1,134 +1,69 @@
|
|||||||
name: Cloud Hypervisor Release
|
name: Cloud Hypervisor Release
|
||||||
on: [pull_request, create]
|
on: [create, merge_group]
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
if: (github.event_name == 'create' && github.event.ref_type == 'tag') || github.event_name == 'pull_request'
|
if: (github.event_name == 'create' && github.event.ref_type == 'tag') || github.event_name == 'merge_group'
|
||||||
name: Release
|
name: Release ${{ matrix.platform.target }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- target: x86_64-unknown-linux-gnu
|
||||||
|
args: --all --release --features mshv
|
||||||
|
name_ch: cloud-hypervisor
|
||||||
|
name_ch_remote: ch-remote
|
||||||
|
- target: x86_64-unknown-linux-musl
|
||||||
|
args: --all --release --features mshv
|
||||||
|
name_ch: cloud-hypervisor-static
|
||||||
|
name_ch_remote: ch-remote-static
|
||||||
|
- target: aarch64-unknown-linux-musl
|
||||||
|
args: --all --release
|
||||||
|
name_ch: cloud-hypervisor-static-aarch64
|
||||||
|
name_ch_remote: ch-remote-static-aarch64
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Install musl-gcc
|
- name: Install musl-gcc
|
||||||
|
if: contains(matrix.platform.target, 'musl')
|
||||||
run: sudo apt install -y musl-tools
|
run: sudo apt install -y musl-tools
|
||||||
- name: Create release directory
|
- name: Create release directory
|
||||||
|
if: |
|
||||||
|
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
||||||
|
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
||||||
run: rsync -rv --exclude=.git . ../cloud-hypervisor-${{ github.event.ref }}
|
run: rsync -rv --exclude=.git . ../cloud-hypervisor-${{ github.event.ref }}
|
||||||
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
|
- name: Build ${{ matrix.platform.target }}
|
||||||
uses: actions-rs/toolchain@v1
|
uses: houseabsolute/actions-rust-cross@v0
|
||||||
with:
|
with:
|
||||||
toolchain: "1.70"
|
|
||||||
target: x86_64-unknown-linux-gnu
|
|
||||||
- name: Install Rust toolchain (x86_64-unknown-linux-musl)
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: "1.70"
|
|
||||||
target: x86_64-unknown-linux-musl
|
|
||||||
- name: Build
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
toolchain: "1.70"
|
|
||||||
command: build
|
command: build
|
||||||
args: --all --release --features mshv --target=x86_64-unknown-linux-gnu
|
target: ${{ matrix.platform.target }}
|
||||||
- name: Static Build
|
args: ${{ matrix.platform.args }}
|
||||||
uses: actions-rs/cargo@v1
|
strip: true
|
||||||
with:
|
toolchain: "1.77.0"
|
||||||
toolchain: "1.70"
|
- name: Copy Release Binaries
|
||||||
command: build
|
|
||||||
args: --all --release --features mshv --target=x86_64-unknown-linux-musl
|
|
||||||
- name: Install Rust toolchain (aarch64-unknown-linux-musl)
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: "1.70"
|
|
||||||
target: aarch64-unknown-linux-musl
|
|
||||||
override: true
|
|
||||||
- name: Create Release
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
id: create_release
|
shell: bash
|
||||||
uses: actions/create-release@v1
|
run: |
|
||||||
env:
|
cp target/${{ matrix.platform.target }}/release/cloud-hypervisor ./${{ matrix.platform.name_ch }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
cp target/${{ matrix.platform.target }}/release/ch-remote ./${{ matrix.platform.name_ch_remote }}
|
||||||
with:
|
- name: Upload Release Artifacts
|
||||||
tag_name: ${{ github.ref }}
|
|
||||||
release_name: ${{ github.ref }}
|
|
||||||
draft: true
|
|
||||||
prerelease: true
|
|
||||||
- name: Upload cloud-hypervisor
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
id: upload-release-cloud-hypervisor
|
uses: actions/upload-artifact@v3
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
name: Artifacts for ${{ matrix.platform.target }}
|
||||||
asset_path: target/x86_64-unknown-linux-gnu/release/cloud-hypervisor
|
path: |
|
||||||
asset_name: cloud-hypervisor
|
./${{ matrix.platform.name_ch }}
|
||||||
asset_content_type: application/octet-stream
|
./${{ matrix.platform.name_ch_remote }}
|
||||||
- name: Upload static cloud-hypervisor
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
|
||||||
id: upload-release-static-cloud-hypervisor
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
asset_path: target/x86_64-unknown-linux-musl/release/cloud-hypervisor
|
|
||||||
asset_name: cloud-hypervisor-static
|
|
||||||
asset_content_type: application/octet-stream
|
|
||||||
- name: Upload ch-remote
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
|
||||||
id: upload-release-ch-remote
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
asset_path: target/x86_64-unknown-linux-gnu/release/ch-remote
|
|
||||||
asset_name: ch-remote
|
|
||||||
asset_content_type: application/octet-stream
|
|
||||||
- name: Upload static-ch-remote
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
|
||||||
id: upload-release-static-ch-remote
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
asset_path: target/x86_64-unknown-linux-musl/release/ch-remote
|
|
||||||
asset_name: ch-remote-static
|
|
||||||
asset_content_type: application/octet-stream
|
|
||||||
- name: Clean build tree ahead of cross build
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: clean
|
|
||||||
- name: Static Build (AArch64)
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
use-cross: true
|
|
||||||
command: build
|
|
||||||
args: --all --release --target=aarch64-unknown-linux-musl
|
|
||||||
- name: Upload static AArch64 cloud-hypervisor
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
|
||||||
id: upload-release-static-aarch64-cloud-hypervisor
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
asset_path: target/aarch64-unknown-linux-musl/release/cloud-hypervisor
|
|
||||||
asset_name: cloud-hypervisor-static-aarch64
|
|
||||||
asset_content_type: application/octet-stream
|
|
||||||
- name: Upload static AArch64 ch-remote
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
|
||||||
id: upload-release-static-aarch64-ch-remote
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
asset_path: target/aarch64-unknown-linux-musl/release/ch-remote
|
|
||||||
asset_name: ch-remote-static-aarch64
|
|
||||||
asset_content_type: application/octet-stream
|
|
||||||
- name: Vendor
|
- name: Vendor
|
||||||
|
if: |
|
||||||
|
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
||||||
|
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
||||||
working-directory: ../cloud-hypervisor-${{ github.event.ref }}
|
working-directory: ../cloud-hypervisor-${{ github.event.ref }}
|
||||||
run: |
|
run: |
|
||||||
mkdir ../vendor-cargo-home
|
mkdir ../vendor-cargo-home
|
||||||
@@ -136,16 +71,25 @@ jobs:
|
|||||||
mkdir .cargo
|
mkdir .cargo
|
||||||
cargo vendor > .cargo/config.toml
|
cargo vendor > .cargo/config.toml
|
||||||
- name: Create vendored source archive
|
- name: Create vendored source archive
|
||||||
working-directory: ../
|
if: |
|
||||||
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz cloud-hypervisor-${{ github.event.ref }}
|
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
||||||
|
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
||||||
|
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz ../cloud-hypervisor-${{ github.event.ref }}
|
||||||
- name: Upload cloud-hypervisor vendored source archive
|
- name: Upload cloud-hypervisor vendored source archive
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
if: |
|
||||||
|
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
||||||
|
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
||||||
id: upload-release-cloud-hypervisor-vendored-sources
|
id: upload-release-cloud-hypervisor-vendored-sources
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-artifact@v3
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
path: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||||
asset_path: ../cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||||
asset_name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
- name: Create GitHub Release
|
||||||
asset_content_type: application/x-xz
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
draft: true
|
||||||
|
files: |
|
||||||
|
./${{ matrix.platform.name_ch }}
|
||||||
|
./${{ matrix.platform.name_ch_remote }}
|
||||||
|
./cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||||
|
|||||||
12
.github/workflows/reuse.yaml
vendored
Normal file
12
.github/workflows/reuse.yaml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
name: REUSE Compliance Check
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
reuse:
|
||||||
|
name: REUSE Compliance Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: REUSE Compliance Check
|
||||||
|
uses: fsfe/reuse-action@v3
|
||||||
2
.github/workflows/shlint.yaml
vendored
2
.github/workflows/shlint.yaml
vendored
@@ -1,7 +1,7 @@
|
|||||||
name: Shell scripts check
|
name: Shell scripts check
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
merge_group:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|||||||
21
.github/workflows/taplo.yaml
vendored
Normal file
21
.github/workflows/taplo.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: Cargo.toml Formatting (taplo)
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- '**/Cargo.toml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cargo_toml_format:
|
||||||
|
name: Cargo.toml Formatting
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Code checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Install build dependencies
|
||||||
|
run: sudo apt-get update && sudo apt-get -yqq install build-essential libssl-dev
|
||||||
|
- name: Install taplo
|
||||||
|
run: cargo install taplo-cli --locked
|
||||||
|
- name: Check formatting
|
||||||
|
run: taplo fmt --check
|
||||||
12
.reuse/dep5
Normal file
12
.reuse/dep5
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: cloud-hypervisor
|
||||||
|
Upstream-Contact: <>
|
||||||
|
Source: https://www.cloudhypervisor.org
|
||||||
|
|
||||||
|
Files: docs/*.md *.md
|
||||||
|
Copyright: 2024
|
||||||
|
License: CC-BY-4.0
|
||||||
|
|
||||||
|
Files: scripts/* test_data/* *.toml .git* fuzz/Cargo.lock fuzz/.gitignore resources/linux-config-* vmm/src/api/openapi/cloud-hypervisor.yaml CODEOWNERS Cargo.lock
|
||||||
|
Copyright: 2024
|
||||||
|
License: Apache-2.0
|
||||||
5
.taplo.toml
Normal file
5
.taplo.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
include = ["**/Cargo.toml"]
|
||||||
|
|
||||||
|
[formatting]
|
||||||
|
reorder_arrays = true
|
||||||
|
reorder_keys = true
|
||||||
@@ -112,5 +112,5 @@ Fixes #88
|
|||||||
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
|
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, after the corresponding PR is merged, Github will automatically close that issue when parsing the
|
Then, after the corresponding PR is merged, GitHub will automatically close that issue when parsing the
|
||||||
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
||||||
|
|||||||
1243
Cargo.lock
generated
1243
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
104
Cargo.toml
104
Cargo.toml
@@ -1,13 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cloud-hypervisor"
|
|
||||||
version = "38.0.0"
|
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
edition = "2021"
|
|
||||||
default-run = "cloud-hypervisor"
|
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
license = "LICENSE-APACHE & LICENSE-BSD-3-Clause"
|
default-run = "cloud-hypervisor"
|
||||||
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM"
|
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV"
|
||||||
|
edition = "2021"
|
||||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||||
|
license = "Apache-2.0 AND BSD-3-Clause"
|
||||||
|
name = "cloud-hypervisor"
|
||||||
|
version = "41.0.0"
|
||||||
# Minimum buildable version:
|
# Minimum buildable version:
|
||||||
# Keep in sync with version in .github/workflows/build.yaml
|
# Keep in sync with version in .github/workflows/build.yaml
|
||||||
# Policy on MSRV (see #4318):
|
# Policy on MSRV (see #4318):
|
||||||
@@ -15,92 +15,88 @@ homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
|||||||
# a.) A dependency requires it,
|
# a.) A dependency requires it,
|
||||||
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
||||||
# c.) There is a security issue that is addressed by the toolchain update.
|
# c.) There is a security issue that is addressed by the toolchain update.
|
||||||
rust-version = "1.70"
|
rust-version = "1.77.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
lto = true
|
||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
strip = true
|
strip = true
|
||||||
|
|
||||||
[profile.profiling]
|
[profile.profiling]
|
||||||
|
debug = true
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
strip = false
|
strip = false
|
||||||
debug = true
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.86"
|
||||||
api_client = { path = "api_client" }
|
api_client = { path = "api_client" }
|
||||||
clap = { version = "4.4.7", features = ["string"] }
|
clap = { version = "4.5.4", features = ["string"] }
|
||||||
dhat = { version = "0.3.3", optional = true }
|
dhat = { version = "0.3.3", optional = true }
|
||||||
epoll = "4.3.3"
|
epoll = "4.3.3"
|
||||||
event_monitor = { path = "event_monitor" }
|
event_monitor = { path = "event_monitor" }
|
||||||
hypervisor = { path = "hypervisor" }
|
hypervisor = { path = "hypervisor" }
|
||||||
libc = "0.2.153"
|
libc = "0.2.155"
|
||||||
log = { version = "0.4.20", features = ["std"] }
|
log = { version = "0.4.22", features = ["std"] }
|
||||||
option_parser = { path = "option_parser" }
|
option_parser = { path = "option_parser" }
|
||||||
seccompiler = "0.4.0"
|
seccompiler = "0.4.0"
|
||||||
serde_json = "1.0.109"
|
serde_json = "1.0.120"
|
||||||
signal-hook = "0.3.17"
|
signal-hook = "0.3.17"
|
||||||
thiserror = "1.0.52"
|
thiserror = "1.0.62"
|
||||||
tpm = { path = "tpm"}
|
tpm = { path = "tpm" }
|
||||||
tracer = { path = "tracer" }
|
tracer = { path = "tracer" }
|
||||||
|
vm-memory = "0.14.1"
|
||||||
vmm = { path = "vmm" }
|
vmm = { path = "vmm" }
|
||||||
vmm-sys-util = "0.12.1"
|
vmm-sys-util = "0.12.1"
|
||||||
vm-memory = "0.14.0"
|
zbus = { version = "4.1.2", optional = true }
|
||||||
zbus = { version = "3.11.1", optional = true }
|
|
||||||
|
|
||||||
# List of patched crates
|
|
||||||
[patch.crates-io]
|
|
||||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.7.0" }
|
|
||||||
versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch-0.1.6" }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dirs = "5.0.0"
|
dirs = "5.0.1"
|
||||||
net_util = { path = "net_util" }
|
net_util = { path = "net_util" }
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
serde_json = "1.0.109"
|
serde_json = "1.0.120"
|
||||||
test_infra = { path = "test_infra" }
|
test_infra = { path = "test_infra" }
|
||||||
wait-timeout = "0.2.0"
|
wait-timeout = "0.2.0"
|
||||||
|
|
||||||
# Please adjust `vmm::feature_list()` accordingly when changing the
|
# Please adjust `vmm::feature_list()` accordingly when changing the
|
||||||
# feature list below
|
# feature list below
|
||||||
[features]
|
[features]
|
||||||
default = ["kvm", "io_uring"]
|
dbus_api = ["vmm/dbus_api", "zbus"]
|
||||||
dbus_api = ["zbus", "vmm/dbus_api"]
|
default = ["io_uring", "kvm"]
|
||||||
dhat-heap = ["dhat"] # For heap profiling
|
dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling
|
||||||
guest_debug = ["vmm/guest_debug"]
|
guest_debug = ["vmm/guest_debug"]
|
||||||
igvm = ["vmm/igvm", "mshv"]
|
igvm = ["mshv", "vmm/igvm"]
|
||||||
io_uring = ["vmm/io_uring"]
|
io_uring = ["vmm/io_uring"]
|
||||||
kvm = ["vmm/kvm"]
|
kvm = ["vmm/kvm"]
|
||||||
mshv = ["vmm/mshv"]
|
mshv = ["vmm/mshv"]
|
||||||
sev_snp = ["igvm", "vmm/sev_snp", "mshv"]
|
pvmemcontrol = ["vmm/pvmemcontrol"]
|
||||||
|
sev_snp = ["igvm", "mshv", "vmm/sev_snp"]
|
||||||
tdx = ["vmm/tdx"]
|
tdx = ["vmm/tdx"]
|
||||||
tracing = ["vmm/tracing", "tracer/tracing"]
|
tracing = ["tracer/tracing", "vmm/tracing"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"api_client",
|
"api_client",
|
||||||
"arch",
|
"arch",
|
||||||
"block",
|
"block",
|
||||||
"devices",
|
"devices",
|
||||||
"event_monitor",
|
"event_monitor",
|
||||||
"hypervisor",
|
"hypervisor",
|
||||||
"net_gen",
|
"net_gen",
|
||||||
"net_util",
|
"net_util",
|
||||||
"option_parser",
|
"option_parser",
|
||||||
"pci",
|
"pci",
|
||||||
"performance-metrics",
|
"performance-metrics",
|
||||||
"rate_limiter",
|
"rate_limiter",
|
||||||
"serial_buffer",
|
"serial_buffer",
|
||||||
"test_infra",
|
"test_infra",
|
||||||
"tracer",
|
"tracer",
|
||||||
"vhost_user_block",
|
"vhost_user_block",
|
||||||
"vhost_user_net",
|
"vhost_user_net",
|
||||||
"virtio-devices",
|
"virtio-devices",
|
||||||
"vmm",
|
"vm-allocator",
|
||||||
"vm-allocator",
|
"vm-device",
|
||||||
"vm-device",
|
"vm-migration",
|
||||||
"vm-migration",
|
"vm-virtio",
|
||||||
"vm-virtio"
|
"vmm",
|
||||||
]
|
]
|
||||||
|
|||||||
530
Jenkinsfile
vendored
530
Jenkinsfile
vendored
@@ -1,530 +0,0 @@
|
|||||||
def runWorkers = true
|
|
||||||
pipeline {
|
|
||||||
agent none
|
|
||||||
options {
|
|
||||||
timeout(time: 4, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
stages {
|
|
||||||
stage('Early checks') {
|
|
||||||
agent { node { label 'built-in' } }
|
|
||||||
stages {
|
|
||||||
stage('Checkout') {
|
|
||||||
steps {
|
|
||||||
checkout scm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Check if worker build can be skipped') {
|
|
||||||
when {
|
|
||||||
expression {
|
|
||||||
return skipWorkerBuild()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
runWorkers = false
|
|
||||||
echo 'No changes requiring a build'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Check for RFC/WIP builds') {
|
|
||||||
when {
|
|
||||||
changeRequest comparator: 'REGEXP', title: '.*(rfc|RFC|wip|WIP).*'
|
|
||||||
beforeAgent true
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
error('Failing as this is marked as a WIP or RFC PR.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Cancel older builds') {
|
|
||||||
when { not { branch 'main' } }
|
|
||||||
steps {
|
|
||||||
cancelPreviousBuilds()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Build') {
|
|
||||||
parallel {
|
|
||||||
stage('Worker build') {
|
|
||||||
agent { node { label 'jammy' } }
|
|
||||||
when {
|
|
||||||
beforeAgent true
|
|
||||||
expression {
|
|
||||||
return runWorkers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
environment {
|
|
||||||
AUTH_DOWNLOAD_TOKEN = credentials('8a26fd74-d40e-414c-9132-ff3f867806ef')
|
|
||||||
}
|
|
||||||
stages {
|
|
||||||
stage('Checkout') {
|
|
||||||
steps {
|
|
||||||
checkout scm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Prepare environment') {
|
|
||||||
steps {
|
|
||||||
sh 'scripts/prepare_vdpa.sh'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run unit tests') {
|
|
||||||
steps {
|
|
||||||
sh 'scripts/dev_cli.sh tests --unit'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run integration tests') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'sudo modprobe openvswitch'
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run live-migration integration tests') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'sudo modprobe openvswitch'
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration-live-migration'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run unit tests for musl') {
|
|
||||||
steps {
|
|
||||||
sh 'scripts/dev_cli.sh tests --unit --libc musl'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run integration tests for musl') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'sudo modprobe openvswitch'
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration --libc musl'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run live-migration integration tests for musl') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'sudo modprobe openvswitch'
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration-live-migration --libc musl'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Worker build - AMD') {
|
|
||||||
agent { node { label 'jammy-amd' } }
|
|
||||||
when {
|
|
||||||
beforeAgent true
|
|
||||||
expression {
|
|
||||||
return runWorkers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
environment {
|
|
||||||
AUTH_DOWNLOAD_TOKEN = credentials('8a26fd74-d40e-414c-9132-ff3f867806ef')
|
|
||||||
}
|
|
||||||
stages {
|
|
||||||
stage('Checkout') {
|
|
||||||
steps {
|
|
||||||
checkout scm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Prepare environment') {
|
|
||||||
steps {
|
|
||||||
sh 'scripts/prepare_vdpa.sh'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run integration tests') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'sudo modprobe openvswitch'
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run live-migration integration tests') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'sudo modprobe openvswitch'
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration-live-migration'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run integration tests for musl') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'sudo modprobe openvswitch'
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration --libc musl'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run live-migration integration tests for musl') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'sudo modprobe openvswitch'
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration-live-migration --libc musl'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('AArch64 worker build') {
|
|
||||||
agent { node { label 'bionic-arm64' } }
|
|
||||||
when {
|
|
||||||
beforeAgent true
|
|
||||||
expression {
|
|
||||||
return runWorkers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
environment {
|
|
||||||
AZURE_CONNECTION_STRING = credentials('46b4e7d6-315f-4cc1-8333-b58780863b9b')
|
|
||||||
}
|
|
||||||
stages {
|
|
||||||
stage('Checkout') {
|
|
||||||
steps {
|
|
||||||
checkout scm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run unit tests') {
|
|
||||||
steps {
|
|
||||||
sh 'scripts/dev_cli.sh tests --unit --libc musl'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run integration tests') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'sudo modprobe openvswitch'
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration --libc musl'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Install azure-cli') {
|
|
||||||
steps {
|
|
||||||
installAzureCli('focal', 'arm64')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Download Windows image') {
|
|
||||||
steps {
|
|
||||||
sh '''#!/bin/bash -x
|
|
||||||
IMG_BASENAME=windows-11-iot-enterprise-aarch64.raw
|
|
||||||
IMG_PATH=$HOME/workloads/$IMG_BASENAME
|
|
||||||
IMG_GZ_PATH=$HOME/workloads/$IMG_BASENAME.gz
|
|
||||||
IMG_GZ_BLOB_NAME=windows-11-iot-enterprise-aarch64-9-min.raw.gz
|
|
||||||
cp "scripts/$IMG_BASENAME.sha1" "$HOME/workloads/"
|
|
||||||
pushd "$HOME/workloads"
|
|
||||||
if sha1sum "$IMG_BASENAME.sha1" --check; then
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
popd
|
|
||||||
mkdir -p "$HOME/workloads"
|
|
||||||
az storage blob download \
|
|
||||||
--container-name private-images \
|
|
||||||
--file "$IMG_GZ_PATH" \
|
|
||||||
--name "$IMG_GZ_BLOB_NAME" \
|
|
||||||
--connection-string "$AZURE_CONNECTION_STRING"
|
|
||||||
gzip -d $IMG_GZ_PATH
|
|
||||||
'''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run Windows guest integration tests') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration-windows --libc musl'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
always {
|
|
||||||
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
|
|
||||||
deleteDir()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Worker build - Windows guest') {
|
|
||||||
agent { node { label 'jammy' } }
|
|
||||||
when {
|
|
||||||
beforeAgent true
|
|
||||||
expression {
|
|
||||||
return runWorkers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
environment {
|
|
||||||
AZURE_CONNECTION_STRING = credentials('46b4e7d6-315f-4cc1-8333-b58780863b9b')
|
|
||||||
}
|
|
||||||
stages {
|
|
||||||
stage('Checkout') {
|
|
||||||
steps {
|
|
||||||
checkout scm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Install azure-cli') {
|
|
||||||
steps {
|
|
||||||
installAzureCli('jammy', 'amd64')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Download assets') {
|
|
||||||
steps {
|
|
||||||
sh "mkdir ${env.HOME}/workloads"
|
|
||||||
sh 'az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2022-amd64-2.raw" --name windows-server-2022-amd64-2.raw --connection-string "$AZURE_CONNECTION_STRING"'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run Windows guest integration tests') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration-windows'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run Windows guest integration tests for musl') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration-windows --libc musl'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Worker build - Metrics') {
|
|
||||||
agent { node { label 'jammy-metrics' } }
|
|
||||||
when {
|
|
||||||
branch 'main'
|
|
||||||
beforeAgent true
|
|
||||||
expression {
|
|
||||||
return runWorkers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
environment {
|
|
||||||
METRICS_PUBLISH_KEY = credentials('52e0945f-ce7a-43d1-87af-67d1d87cc40f')
|
|
||||||
}
|
|
||||||
stages {
|
|
||||||
stage('Checkout') {
|
|
||||||
steps {
|
|
||||||
checkout scm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run metrics tests') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'scripts/dev_cli.sh tests --metrics -- -- --report-file /root/workloads/metrics.json'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Upload metrics report') {
|
|
||||||
steps {
|
|
||||||
sh 'curl -X PUT https://cloud-hypervisor-metrics.azurewebsites.net/api/publishmetrics -H "x-functions-key: $METRICS_PUBLISH_KEY" -T ~/workloads/metrics.json'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Worker build - Rate Limiter') {
|
|
||||||
agent { node { label 'focal-metrics' } }
|
|
||||||
when {
|
|
||||||
branch 'main'
|
|
||||||
beforeAgent true
|
|
||||||
expression {
|
|
||||||
return runWorkers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stages {
|
|
||||||
stage('Checkout') {
|
|
||||||
steps {
|
|
||||||
checkout scm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run rate-limiter integration tests') {
|
|
||||||
options {
|
|
||||||
timeout(time: 10, unit: 'MINUTES')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration-rate-limiter'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Worker build - SGX') {
|
|
||||||
agent { node { label 'jammy-sgx' } }
|
|
||||||
when {
|
|
||||||
beforeAgent true
|
|
||||||
allOf {
|
|
||||||
branch 'main'
|
|
||||||
expression {
|
|
||||||
return runWorkers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
environment {
|
|
||||||
AUTH_DOWNLOAD_TOKEN = credentials('8a26fd74-d40e-414c-9132-ff3f867806ef')
|
|
||||||
}
|
|
||||||
stages {
|
|
||||||
stage('Checkout') {
|
|
||||||
steps {
|
|
||||||
checkout scm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run SGX integration tests') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration-sgx'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run SGX integration tests for musl') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration-sgx --libc musl'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
always {
|
|
||||||
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
|
|
||||||
deleteDir()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Worker build - VFIO') {
|
|
||||||
agent { node { label 'jammy-vfio' } }
|
|
||||||
when {
|
|
||||||
beforeAgent true
|
|
||||||
allOf {
|
|
||||||
branch 'main'
|
|
||||||
expression {
|
|
||||||
return runWorkers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
environment {
|
|
||||||
AUTH_DOWNLOAD_TOKEN = credentials('8a26fd74-d40e-414c-9132-ff3f867806ef')
|
|
||||||
}
|
|
||||||
stages {
|
|
||||||
stage('Checkout') {
|
|
||||||
steps {
|
|
||||||
checkout scm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run VFIO integration tests') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration-vfio'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Run VFIO integration tests for musl') {
|
|
||||||
options {
|
|
||||||
timeout(time: 1, unit: 'HOURS')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'scripts/dev_cli.sh tests --integration-vfio --libc musl'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
always {
|
|
||||||
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
|
|
||||||
deleteDir()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
regression {
|
|
||||||
script {
|
|
||||||
if (env.BRANCH_NAME == 'main') {
|
|
||||||
slackSend(color: '#ff0000', message: '"main" branch build is now failing', channel: '#jenkins-ci')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fixed {
|
|
||||||
script {
|
|
||||||
if (env.BRANCH_NAME == 'main') {
|
|
||||||
slackSend(color: '#00ff00', message: '"main" branch build is now fixed', channel: '#jenkins-ci')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def cancelPreviousBuilds() {
|
|
||||||
// Check for other instances of this particular build, cancel any that are older than the current one
|
|
||||||
def jobName = env.JOB_NAME
|
|
||||||
def currentBuildNumber = env.BUILD_NUMBER.toInteger()
|
|
||||||
def currentJob = Jenkins.instance.getItemByFullName(jobName)
|
|
||||||
|
|
||||||
// Loop through all instances of this particular job/branch
|
|
||||||
for (def build : currentJob.builds) {
|
|
||||||
if (build.isBuilding() && (build.number.toInteger() < currentBuildNumber)) {
|
|
||||||
echo "Older build still queued. Sending kill signal to build number: ${build.number}"
|
|
||||||
build.doStop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installAzureCli(distro, arch) {
|
|
||||||
sh 'sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg'
|
|
||||||
sh 'curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null'
|
|
||||||
sh "echo \"deb [arch=${arch}] https://packages.microsoft.com/repos/azure-cli/ ${distro} main\" | sudo tee /etc/apt/sources.list.d/azure-cli.list"
|
|
||||||
sh 'sudo apt update'
|
|
||||||
sh 'sudo apt install -y azure-cli'
|
|
||||||
}
|
|
||||||
|
|
||||||
def boolean skipWorkerBuild() {
|
|
||||||
if (env.CHANGE_TARGET == null) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sh(
|
|
||||||
returnStatus: true,
|
|
||||||
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v '\\.md'"
|
|
||||||
) != 0) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sh(
|
|
||||||
returnStatus: true,
|
|
||||||
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v -E 'fuzz/'"
|
|
||||||
) != 0) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sh(
|
|
||||||
returnStatus: true,
|
|
||||||
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v -E '.github/'"
|
|
||||||
) != 0) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sh(
|
|
||||||
returnStatus: true,
|
|
||||||
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v '^\\.'"
|
|
||||||
) != 0) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sh(
|
|
||||||
returnStatus: true,
|
|
||||||
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v 'gitlint'"
|
|
||||||
) != 0) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
The documentation in this directory is covered by the following license:
|
All documentations (e.g. files with extension `.md`) in this repository is
|
||||||
|
covered by the following license:
|
||||||
|
|
||||||
Attribution 4.0 International
|
Attribution 4.0 International
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ do not wish to use the pre-built binaries.
|
|||||||
## Booting Linux
|
## Booting Linux
|
||||||
|
|
||||||
Cloud Hypervisor supports direct kernel boot (the x86-64 kernel requires the kernel
|
Cloud Hypervisor supports direct kernel boot (the x86-64 kernel requires the kernel
|
||||||
built with PVH support) or booting via a firmware (either [Rust Hypervisor
|
built with PVH support or a bzImage) or booting via a firmware (either [Rust Hypervisor
|
||||||
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) or an
|
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) or an
|
||||||
edk2 UEFI firmware called `CLOUDHV` / `CLOUDHV_EFI`.)
|
edk2 UEFI firmware called `CLOUDHV` / `CLOUDHV_EFI`.)
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ $ ./cloud-hypervisor \
|
|||||||
|
|
||||||
#### Building your Kernel
|
#### Building your Kernel
|
||||||
|
|
||||||
Cloud Hypervisor also supports direct kernel boot. For x86-64, a `vmlinux` ELF kernel (compiled with PVH support) is needed. In order to support development there is a custom branch; however provided the required options are enabled any recent kernel will suffice.
|
Cloud Hypervisor also supports direct kernel boot. For x86-64, a `vmlinux` ELF kernel (compiled with PVH support) or a regular bzImage are supported. In order to support development there is a custom branch; however provided the required options are enabled any recent kernel will suffice.
|
||||||
|
|
||||||
To build the kernel:
|
To build the kernel:
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "api_client"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
name = "api_client"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
thiserror = "1.0.62"
|
||||||
vmm-sys-util = "0.12.1"
|
vmm-sys-util = "0.12.1"
|
||||||
|
|||||||
@@ -3,41 +3,27 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
|
use thiserror::Error;
|
||||||
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
|
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
#[error("Error writing to or reading from HTTP socket: {0}")]
|
||||||
Socket(std::io::Error),
|
Socket(std::io::Error),
|
||||||
|
#[error("Error sending file descriptors: {0}")]
|
||||||
SocketSendFds(vmm_sys_util::errno::Error),
|
SocketSendFds(vmm_sys_util::errno::Error),
|
||||||
|
#[error("Error parsing HTTP status code: {0}")]
|
||||||
StatusCodeParsing(std::num::ParseIntError),
|
StatusCodeParsing(std::num::ParseIntError),
|
||||||
|
#[error("HTTP output is missing protocol statement")]
|
||||||
MissingProtocol,
|
MissingProtocol,
|
||||||
|
#[error("Error parsing HTTP Content-Length field: {0}")]
|
||||||
ContentLengthParsing(std::num::ParseIntError),
|
ContentLengthParsing(std::num::ParseIntError),
|
||||||
|
#[error("Server responded with an error: {0:?}")]
|
||||||
ServerResponse(StatusCode, Option<String>),
|
ServerResponse(StatusCode, Option<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
use Error::*;
|
|
||||||
match self {
|
|
||||||
Socket(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
|
|
||||||
SocketSendFds(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
|
|
||||||
StatusCodeParsing(e) => write!(f, "Error parsing HTTP status code: {e}"),
|
|
||||||
MissingProtocol => write!(f, "HTTP output is missing protocol statement"),
|
|
||||||
ContentLengthParsing(e) => write!(f, "Error parsing HTTP Content-Length field: {e}"),
|
|
||||||
ServerResponse(s, o) => {
|
|
||||||
if let Some(o) = o {
|
|
||||||
write!(f, "Server responded with an error: {s:?}: {o}")
|
|
||||||
} else {
|
|
||||||
write!(f, "Server responded with an error: {s:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum StatusCode {
|
pub enum StatusCode {
|
||||||
Continue,
|
Continue,
|
||||||
|
|||||||
@@ -1,30 +1,32 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "arch"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["The Chromium OS Authors"]
|
authors = ["The Chromium OS Authors"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
name = "arch"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
kvm = []
|
||||||
sev_snp = []
|
sev_snp = []
|
||||||
tdx = []
|
tdx = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.86"
|
||||||
byteorder = "1.4.3"
|
byteorder = "1.5.0"
|
||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
libc = "0.2.153"
|
libc = "0.2.155"
|
||||||
linux-loader = { version = "0.11.0", features = ["elf", "bzimage", "pe"] }
|
linux-loader = { version = "0.11.0", features = ["bzimage", "elf", "pe"] }
|
||||||
log = "0.4.20"
|
log = "0.4.22"
|
||||||
serde = { version = "1.0.196", features = ["rc", "derive"] }
|
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||||
thiserror = "1.0.52"
|
thiserror = "1.0.62"
|
||||||
uuid = "1.3.4"
|
uuid = "1.8.0"
|
||||||
versionize = "0.2.0"
|
vm-memory = { version = "0.14.1", features = [
|
||||||
versionize_derive = "0.1.6"
|
"backend-bitmap",
|
||||||
vm-memory = { version = "0.14.0", features = ["backend-mmap", "backend-bitmap"] }
|
"backend-mmap",
|
||||||
|
] }
|
||||||
vm-migration = { path = "../vm-migration" }
|
vm-migration = { path = "../vm-migration" }
|
||||||
vmm-sys-util = { version = "0.12.1", features = ["with-serde"] }
|
vmm-sys-util = { version = "0.12.1", features = ["with-serde"] }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||||
fdt_parser = { version = "0.1.4", package = "fdt" }
|
fdt_parser = { version = "0.1.5", package = "fdt" }
|
||||||
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ use super::layout::{
|
|||||||
};
|
};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use thiserror::Error;
|
||||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
||||||
|
|
||||||
@@ -80,9 +81,10 @@ pub trait DeviceInfoForFdt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
|
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failure in writing FDT in memory.
|
/// Failure in writing FDT in memory.
|
||||||
|
#[error("Failure in writing FDT in memory: {0}")]
|
||||||
WriteFdtToMemory(GuestMemoryError),
|
WriteFdtToMemory(GuestMemoryError),
|
||||||
}
|
}
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
@@ -523,7 +525,7 @@ fn create_memory_node(
|
|||||||
let memory_region_size: u64 = memory_region.size() as u64;
|
let memory_region_size: u64 = memory_region.size() as u64;
|
||||||
mem_reg_prop.push(memory_region_start_addr);
|
mem_reg_prop.push(memory_region_start_addr);
|
||||||
mem_reg_prop.push(memory_region_size);
|
mem_reg_prop.push(memory_region_size);
|
||||||
// Set the node address the first non-zero regison address
|
// Set the node address the first non-zero region address
|
||||||
if node_memory_addr == 0 {
|
if node_memory_addr == 0 {
|
||||||
node_memory_addr = memory_region_start_addr;
|
node_memory_addr = memory_region_start_addr;
|
||||||
}
|
}
|
||||||
@@ -569,7 +571,7 @@ fn create_memory_node(
|
|||||||
|
|
||||||
if ram_regions.len() > 2 {
|
if ram_regions.len() > 2 {
|
||||||
panic!(
|
panic!(
|
||||||
"There should be up to two non-continuous regions, devidided by the
|
"There should be up to two non-continuous regions, divided by the
|
||||||
gap at the end of 32bit address space."
|
gap at the end of 32bit address space."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -894,7 +896,7 @@ fn create_pci_nodes(
|
|||||||
for pci_device_info_elem in pci_device_info.iter() {
|
for pci_device_info_elem in pci_device_info.iter() {
|
||||||
// EDK2 requires the PCIe high space above 4G address.
|
// EDK2 requires the PCIe high space above 4G address.
|
||||||
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
||||||
// could fall bellow 4G.
|
// could fall below 4G.
|
||||||
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
||||||
// But the address written in ACPI is not impacted.
|
// But the address written in ACPI is not impacted.
|
||||||
let (pci_device_base_64bit, pci_device_size_64bit) =
|
let (pci_device_base_64bit, pci_device_size_64bit) =
|
||||||
|
|||||||
@@ -16,35 +16,42 @@ use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
|
|||||||
use hypervisor::arch::aarch64::gic::Vgic;
|
use hypervisor::arch::aarch64::gic::Vgic;
|
||||||
use log::{log_enabled, Level};
|
use log::{log_enabled, Level};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use thiserror::Error;
|
||||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
||||||
|
|
||||||
pub const _NSIG: i32 = 65;
|
pub const _NSIG: i32 = 65;
|
||||||
|
|
||||||
/// Errors thrown while configuring aarch64 system.
|
/// Errors thrown while configuring aarch64 system.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failed to create a FDT.
|
/// Failed to create a FDT.
|
||||||
|
#[error("Failed to create a FDT")]
|
||||||
SetupFdt,
|
SetupFdt,
|
||||||
|
|
||||||
/// Failed to write FDT to memory.
|
/// Failed to write FDT to memory.
|
||||||
|
#[error("Failed to write FDT to memory: {0}")]
|
||||||
WriteFdtToMemory(fdt::Error),
|
WriteFdtToMemory(fdt::Error),
|
||||||
|
|
||||||
/// Failed to create a GIC.
|
/// Failed to create a GIC.
|
||||||
|
#[error("Failed to create a GIC")]
|
||||||
SetupGic,
|
SetupGic,
|
||||||
|
|
||||||
/// Failed to compute the initramfs address.
|
/// Failed to compute the initramfs address.
|
||||||
|
#[error("Failed to compute the initramfs address")]
|
||||||
InitramfsAddress,
|
InitramfsAddress,
|
||||||
|
|
||||||
/// Error configuring the general purpose registers
|
/// Error configuring the general purpose registers
|
||||||
|
#[error("Error configuring the general purpose registers: {0}")]
|
||||||
RegsConfiguration(hypervisor::HypervisorCpuError),
|
RegsConfiguration(hypervisor::HypervisorCpuError),
|
||||||
|
|
||||||
/// Error configuring the MPIDR register
|
/// Error configuring the MPIDR register
|
||||||
|
#[error("Error configuring the MPIDR register: {0}")]
|
||||||
VcpuRegMpidr(hypervisor::HypervisorCpuError),
|
VcpuRegMpidr(hypervisor::HypervisorCpuError),
|
||||||
|
|
||||||
/// Error initializing PMU for vcpu
|
/// Error initializing PMU for vcpu
|
||||||
|
#[error("Error initializing PMU for vcpu")]
|
||||||
VcpuInitPmu,
|
VcpuInitPmu,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,27 @@
|
|||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
use std::os::fd::AsFd;
|
use std::os::fd::AsFd;
|
||||||
use std::result;
|
use std::result;
|
||||||
|
use thiserror::Error;
|
||||||
use vm_memory::{GuestAddress, GuestMemory};
|
use vm_memory::{GuestAddress, GuestMemory};
|
||||||
|
|
||||||
/// Errors thrown while loading UEFI binary
|
/// Errors thrown while loading UEFI binary
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Unable to seek to UEFI image start.
|
/// Unable to seek to UEFI image start.
|
||||||
|
#[error("Unable to seek to UEFI image start")]
|
||||||
SeekUefiStart,
|
SeekUefiStart,
|
||||||
/// Unable to seek to UEFI image end.
|
/// Unable to seek to UEFI image end.
|
||||||
|
#[error("Unable to seek to UEFI image end")]
|
||||||
SeekUefiEnd,
|
SeekUefiEnd,
|
||||||
/// UEFI image too big.
|
/// UEFI image too big.
|
||||||
|
#[error("UEFI image too big")]
|
||||||
UefiTooBig,
|
UefiTooBig,
|
||||||
/// Unable to read UEFI image
|
/// Unable to read UEFI image
|
||||||
|
#[error("Unable to read UEFI image")]
|
||||||
ReadUefiImage,
|
ReadUefiImage,
|
||||||
}
|
}
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|||||||
@@ -18,9 +18,6 @@ use std::fmt;
|
|||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult};
|
|
||||||
use versionize_derive::Versionize;
|
|
||||||
use vm_migration::VersionMapped;
|
|
||||||
|
|
||||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
|
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||||
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
|
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||||
@@ -48,13 +45,17 @@ pub enum Error {
|
|||||||
ModlistSetup(#[source] vm_memory::GuestMemoryError),
|
ModlistSetup(#[source] vm_memory::GuestMemoryError),
|
||||||
#[error("RSDP extends past the end of guest memory")]
|
#[error("RSDP extends past the end of guest memory")]
|
||||||
RsdpPastRamEnd,
|
RsdpPastRamEnd,
|
||||||
|
#[error("Failed to setup Zero Page for bzImage")]
|
||||||
|
ZeroPageSetup(#[source] vm_memory::GuestMemoryError),
|
||||||
|
#[error("Zero Page for bzImage past RAM end")]
|
||||||
|
ZeroPagePastRamEnd,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type for returning public functions outcome.
|
/// Type for returning public functions outcome.
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
/// Type for memory region types.
|
/// Type for memory region types.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Versionize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
pub enum RegionType {
|
pub enum RegionType {
|
||||||
/// RAM type
|
/// RAM type
|
||||||
Ram,
|
Ram,
|
||||||
@@ -72,8 +73,6 @@ pub enum RegionType {
|
|||||||
Reserved,
|
Reserved,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VersionMapped for RegionType {}
|
|
||||||
|
|
||||||
/// Module for aarch64 related functionality.
|
/// Module for aarch64 related functionality.
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub mod aarch64;
|
pub mod aarch64;
|
||||||
|
|||||||
@@ -17,11 +17,13 @@ use crate::InitramfsConfig;
|
|||||||
use crate::RegionType;
|
use crate::RegionType;
|
||||||
use hypervisor::arch::x86::{CpuIdEntry, CPUID_FLAG_VALID_INDEX};
|
use hypervisor::arch::x86::{CpuIdEntry, CPUID_FLAG_VALID_INDEX};
|
||||||
use hypervisor::{CpuVendor, HypervisorCpuError, HypervisorError};
|
use hypervisor::{CpuVendor, HypervisorCpuError, HypervisorError};
|
||||||
|
use linux_loader::loader::bootparam::{boot_params, setup_header};
|
||||||
use linux_loader::loader::elf::start_info::{
|
use linux_loader::loader::elf::start_info::{
|
||||||
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
|
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
|
||||||
};
|
};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use thiserror::Error;
|
||||||
use vm_memory::{
|
use vm_memory::{
|
||||||
Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
||||||
GuestMemoryRegion, GuestUsize,
|
GuestMemoryRegion, GuestUsize,
|
||||||
@@ -32,6 +34,7 @@ use std::arch::x86_64;
|
|||||||
pub mod tdx;
|
pub mod tdx;
|
||||||
|
|
||||||
// CPUID feature bits
|
// CPUID feature bits
|
||||||
|
#[cfg(feature = "kvm")]
|
||||||
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
||||||
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
|
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
|
||||||
const MTRR_EDX_BIT: u8 = 12; // Hypervisor ecx bit.
|
const MTRR_EDX_BIT: u8 = 12; // Hypervisor ecx bit.
|
||||||
@@ -63,6 +66,8 @@ pub const _NSIG: i32 = 65;
|
|||||||
pub struct EntryPoint {
|
pub struct EntryPoint {
|
||||||
/// Address in guest memory where the guest must start execution
|
/// Address in guest memory where the guest must start execution
|
||||||
pub entry_addr: GuestAddress,
|
pub entry_addr: GuestAddress,
|
||||||
|
/// This field is used for bzImage to fill the zero page
|
||||||
|
pub setup_header: Option<setup_header>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const E820_RAM: u32 = 1;
|
const E820_RAM: u32 = 1;
|
||||||
@@ -124,62 +129,84 @@ pub struct CpuidConfig {
|
|||||||
pub amx: bool,
|
pub amx: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Error writing MP table to memory.
|
/// Error writing MP table to memory.
|
||||||
|
#[error("Error writing MP table to memory: {0}")]
|
||||||
MpTableSetup(mptable::Error),
|
MpTableSetup(mptable::Error),
|
||||||
|
|
||||||
/// Error configuring the general purpose registers
|
/// Error configuring the general purpose registers
|
||||||
|
#[error("Error configuring the general purpose registers: {0}")]
|
||||||
RegsConfiguration(regs::Error),
|
RegsConfiguration(regs::Error),
|
||||||
|
|
||||||
/// Error configuring the special registers
|
/// Error configuring the special registers
|
||||||
|
#[error("Error configuring the special registers: {0}")]
|
||||||
SregsConfiguration(regs::Error),
|
SregsConfiguration(regs::Error),
|
||||||
|
|
||||||
/// Error configuring the floating point related registers
|
/// Error configuring the floating point related registers
|
||||||
|
#[error("Error configuring the floating point related registers: {0}")]
|
||||||
FpuConfiguration(regs::Error),
|
FpuConfiguration(regs::Error),
|
||||||
|
|
||||||
/// Error configuring the MSR registers
|
/// Error configuring the MSR registers
|
||||||
|
#[error("Error configuring the MSR registers: {0}")]
|
||||||
MsrsConfiguration(regs::Error),
|
MsrsConfiguration(regs::Error),
|
||||||
|
|
||||||
/// Failed to set supported CPUs.
|
/// Failed to set supported CPUs.
|
||||||
|
#[error("Failed to set supported CPUs: {0}")]
|
||||||
SetSupportedCpusFailed(anyhow::Error),
|
SetSupportedCpusFailed(anyhow::Error),
|
||||||
|
|
||||||
/// Cannot set the local interruption due to bad configuration.
|
/// Cannot set the local interruption due to bad configuration.
|
||||||
|
#[error("Cannot set the local interruption due to bad configuration: {0}")]
|
||||||
LocalIntConfiguration(anyhow::Error),
|
LocalIntConfiguration(anyhow::Error),
|
||||||
|
|
||||||
/// Error setting up SMBIOS table
|
/// Error setting up SMBIOS table
|
||||||
|
#[error("Error setting up SMBIOS table: {0}")]
|
||||||
SmbiosSetup(smbios::Error),
|
SmbiosSetup(smbios::Error),
|
||||||
|
|
||||||
/// Could not find any SGX EPC section
|
/// Could not find any SGX EPC section
|
||||||
|
#[error("Could not find any SGX EPC section")]
|
||||||
NoSgxEpcSection,
|
NoSgxEpcSection,
|
||||||
|
|
||||||
/// Missing SGX CPU feature
|
/// Missing SGX CPU feature
|
||||||
|
#[error("Missing SGX CPU feature")]
|
||||||
MissingSgxFeature,
|
MissingSgxFeature,
|
||||||
|
|
||||||
/// Missing SGX_LC CPU feature
|
/// Missing SGX_LC CPU feature
|
||||||
|
#[error("Missing SGX_LC CPU feature")]
|
||||||
MissingSgxLaunchControlFeature,
|
MissingSgxLaunchControlFeature,
|
||||||
|
|
||||||
/// Error getting supported CPUID through the hypervisor (kvm/mshv) API
|
/// Error getting supported CPUID through the hypervisor (kvm/mshv) API
|
||||||
|
#[error("Error getting supported CPUID through the hypervisor API: {0}")]
|
||||||
CpuidGetSupported(HypervisorError),
|
CpuidGetSupported(HypervisorError),
|
||||||
|
|
||||||
/// Error populating CPUID with KVM HyperV emulation details
|
/// Error populating CPUID with KVM HyperV emulation details
|
||||||
|
#[error("Error populating CPUID with KVM HyperV emulation details: {0}")]
|
||||||
CpuidKvmHyperV(vmm_sys_util::fam::Error),
|
CpuidKvmHyperV(vmm_sys_util::fam::Error),
|
||||||
|
|
||||||
/// Error populating CPUID with CPU identification
|
/// Error populating CPUID with CPU identification
|
||||||
|
#[error("Error populating CPUID with CPU identification: {0}")]
|
||||||
CpuidIdentification(vmm_sys_util::fam::Error),
|
CpuidIdentification(vmm_sys_util::fam::Error),
|
||||||
|
|
||||||
/// Error checking CPUID compatibility
|
/// Error checking CPUID compatibility
|
||||||
|
#[error("Error checking CPUID compatibility")]
|
||||||
CpuidCheckCompatibility,
|
CpuidCheckCompatibility,
|
||||||
|
|
||||||
// Error writing EBDA address
|
// Error writing EBDA address
|
||||||
|
#[error("Error writing EBDA address: {0}")]
|
||||||
EbdaSetup(vm_memory::GuestMemoryError),
|
EbdaSetup(vm_memory::GuestMemoryError),
|
||||||
|
|
||||||
// Error getting CPU TSC frequency
|
// Error getting CPU TSC frequency
|
||||||
|
#[error("Error getting CPU TSC frequency: {0}")]
|
||||||
GetTscFrequency(HypervisorCpuError),
|
GetTscFrequency(HypervisorCpuError),
|
||||||
|
|
||||||
/// Error retrieving TDX capabilities through the hypervisor (kvm/mshv) API
|
/// Error retrieving TDX capabilities through the hypervisor (kvm/mshv) API
|
||||||
#[cfg(feature = "tdx")]
|
#[cfg(feature = "tdx")]
|
||||||
|
#[error("Error retrieving TDX capabilities through the hypervisor API: {0}")]
|
||||||
TdxCapabilities(HypervisorError),
|
TdxCapabilities(HypervisorError),
|
||||||
|
|
||||||
|
/// Failed to configure E820 map for bzImage
|
||||||
|
#[error("Failed to configure E820 map for bzImage")]
|
||||||
|
E820Configuration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for super::Error {
|
impl From<Error> for super::Error {
|
||||||
@@ -551,7 +578,7 @@ impl CpuidFeatureEntry {
|
|||||||
if !entry_compatible {
|
if !entry_compatible {
|
||||||
error!(
|
error!(
|
||||||
"Detected incompatible CPUID entry: leaf={:#02x} (subleaf={:#02x}), register='{:?}', \
|
"Detected incompatible CPUID entry: leaf={:#02x} (subleaf={:#02x}), register='{:?}', \
|
||||||
compatilbe_check='{:?}', source VM feature='{:#04x}', destination VM feature'{:#04x}'.",
|
compatible_check='{:?}', source VM feature='{:#04x}', destination VM feature'{:#04x}'.",
|
||||||
entry.function, entry.index, entry.feature_reg,
|
entry.function, entry.index, entry.feature_reg,
|
||||||
entry.compatible_check, src_vm_feature, dest_vm_feature
|
entry.compatible_check, src_vm_feature, dest_vm_feature
|
||||||
);
|
);
|
||||||
@@ -593,17 +620,8 @@ pub fn generate_common_cpuid(
|
|||||||
"Generating guest CPUID for with physical address size: {}",
|
"Generating guest CPUID for with physical address size: {}",
|
||||||
config.phys_bits
|
config.phys_bits
|
||||||
);
|
);
|
||||||
let cpuid_patches = vec![
|
#[allow(unused_mut)]
|
||||||
// Patch tsc deadline timer bit
|
let mut cpuid_patches = vec![
|
||||||
CpuidPatch {
|
|
||||||
function: 1,
|
|
||||||
index: 0,
|
|
||||||
flags_bit: None,
|
|
||||||
eax_bit: None,
|
|
||||||
ebx_bit: None,
|
|
||||||
ecx_bit: Some(TSC_DEADLINE_TIMER_ECX_BIT),
|
|
||||||
edx_bit: None,
|
|
||||||
},
|
|
||||||
// Patch hypervisor bit
|
// Patch hypervisor bit
|
||||||
CpuidPatch {
|
CpuidPatch {
|
||||||
function: 1,
|
function: 1,
|
||||||
@@ -626,6 +644,23 @@ pub fn generate_common_cpuid(
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[cfg(feature = "kvm")]
|
||||||
|
if matches!(
|
||||||
|
hypervisor.hypervisor_type(),
|
||||||
|
hypervisor::HypervisorType::Kvm
|
||||||
|
) {
|
||||||
|
// Patch tsc deadline timer bit
|
||||||
|
cpuid_patches.push(CpuidPatch {
|
||||||
|
function: 1,
|
||||||
|
index: 0,
|
||||||
|
flags_bit: None,
|
||||||
|
eax_bit: None,
|
||||||
|
ebx_bit: None,
|
||||||
|
ecx_bit: Some(TSC_DEADLINE_TIMER_ECX_BIT),
|
||||||
|
edx_bit: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Supported CPUID
|
// Supported CPUID
|
||||||
let mut cpuid = hypervisor
|
let mut cpuid = hypervisor
|
||||||
.get_supported_cpuid()
|
.get_supported_cpuid()
|
||||||
@@ -676,6 +711,20 @@ pub fn generate_common_cpuid(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Copy host L1 cache details if not populated by KVM
|
||||||
|
0x8000_0005 => {
|
||||||
|
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
||||||
|
// SAFETY: cpuid called with valid leaves
|
||||||
|
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0005 {
|
||||||
|
// SAFETY: cpuid called with valid leaves
|
||||||
|
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0005) };
|
||||||
|
entry.eax = leaf.eax;
|
||||||
|
entry.ebx = leaf.ebx;
|
||||||
|
entry.ecx = leaf.ecx;
|
||||||
|
entry.edx = leaf.edx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Copy host L2 cache details if not populated by KVM
|
// Copy host L2 cache details if not populated by KVM
|
||||||
0x8000_0006 => {
|
0x8000_0006 => {
|
||||||
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
||||||
@@ -794,12 +843,17 @@ pub fn configure_vcpu(
|
|||||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x8000_001e, Some(0), CpuidReg::EAX, x2apic_id);
|
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x8000_001e, Some(0), CpuidReg::EAX, x2apic_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set ApicId in cpuid for each vcpu
|
// Set ApicId in cpuid for each vcpu - found in cpuid ebx when eax = 1
|
||||||
// SAFETY: get host cpuid when eax=1
|
let mut apic_id_patched = false;
|
||||||
let mut cpu_ebx = unsafe { core::arch::x86_64::__cpuid(1) }.ebx;
|
for entry in &mut cpuid {
|
||||||
cpu_ebx &= 0xffffff;
|
if entry.function == 1 {
|
||||||
cpu_ebx |= x2apic_id << 24;
|
entry.ebx &= 0xffffff;
|
||||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x1, None, CpuidReg::EBX, cpu_ebx);
|
entry.ebx |= x2apic_id << 24;
|
||||||
|
apic_id_patched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(apic_id_patched);
|
||||||
|
|
||||||
if let Some(t) = topology {
|
if let Some(t) = topology {
|
||||||
update_cpuid_topology(&mut cpuid, t.0, t.1, t.2, cpu_vendor, id);
|
update_cpuid_topology(&mut cpuid, t.0, t.1, t.2, cpu_vendor, id);
|
||||||
@@ -842,8 +896,7 @@ pub fn configure_vcpu(
|
|||||||
|
|
||||||
regs::setup_msrs(vcpu).map_err(Error::MsrsConfiguration)?;
|
regs::setup_msrs(vcpu).map_err(Error::MsrsConfiguration)?;
|
||||||
if let Some((kernel_entry_point, guest_memory)) = boot_setup {
|
if let Some((kernel_entry_point, guest_memory)) = boot_setup {
|
||||||
regs::setup_regs(vcpu, kernel_entry_point.entry_addr.raw_value())
|
regs::setup_regs(vcpu, kernel_entry_point).map_err(Error::RegsConfiguration)?;
|
||||||
.map_err(Error::RegsConfiguration)?;
|
|
||||||
regs::setup_fpu(vcpu).map_err(Error::FpuConfiguration)?;
|
regs::setup_fpu(vcpu).map_err(Error::FpuConfiguration)?;
|
||||||
regs::setup_sregs(&guest_memory.memory(), vcpu).map_err(Error::SregsConfiguration)?;
|
regs::setup_sregs(&guest_memory.memory(), vcpu).map_err(Error::SregsConfiguration)?;
|
||||||
}
|
}
|
||||||
@@ -892,8 +945,10 @@ pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
|||||||
pub fn configure_system(
|
pub fn configure_system(
|
||||||
guest_mem: &GuestMemoryMmap,
|
guest_mem: &GuestMemoryMmap,
|
||||||
cmdline_addr: GuestAddress,
|
cmdline_addr: GuestAddress,
|
||||||
|
cmdline_size: usize,
|
||||||
initramfs: &Option<InitramfsConfig>,
|
initramfs: &Option<InitramfsConfig>,
|
||||||
_num_cpus: u8,
|
_num_cpus: u8,
|
||||||
|
setup_header: Option<setup_header>,
|
||||||
rsdp_addr: Option<GuestAddress>,
|
rsdp_addr: Option<GuestAddress>,
|
||||||
sgx_epc_region: Option<SgxEpcRegion>,
|
sgx_epc_region: Option<SgxEpcRegion>,
|
||||||
serial_number: Option<&str>,
|
serial_number: Option<&str>,
|
||||||
@@ -921,13 +976,24 @@ pub fn configure_system(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configure_pvh(
|
match setup_header {
|
||||||
guest_mem,
|
Some(hdr) => configure_32bit_entry(
|
||||||
cmdline_addr,
|
guest_mem,
|
||||||
initramfs,
|
cmdline_addr,
|
||||||
rsdp_addr,
|
cmdline_size,
|
||||||
sgx_epc_region,
|
initramfs,
|
||||||
)
|
hdr,
|
||||||
|
rsdp_addr,
|
||||||
|
sgx_epc_region,
|
||||||
|
),
|
||||||
|
None => configure_pvh(
|
||||||
|
guest_mem,
|
||||||
|
cmdline_addr,
|
||||||
|
initramfs,
|
||||||
|
rsdp_addr,
|
||||||
|
sgx_epc_region,
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RamRange = (u64, u64);
|
type RamRange = (u64, u64);
|
||||||
@@ -1132,6 +1198,113 @@ fn configure_pvh(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn configure_32bit_entry(
|
||||||
|
guest_mem: &GuestMemoryMmap,
|
||||||
|
cmdline_addr: GuestAddress,
|
||||||
|
cmdline_size: usize,
|
||||||
|
initramfs: &Option<InitramfsConfig>,
|
||||||
|
setup_hdr: setup_header,
|
||||||
|
rsdp_addr: Option<GuestAddress>,
|
||||||
|
sgx_epc_region: Option<SgxEpcRegion>,
|
||||||
|
) -> super::Result<()> {
|
||||||
|
const KERNEL_LOADER_OTHER: u8 = 0xff;
|
||||||
|
|
||||||
|
// Use the provided setup header
|
||||||
|
let mut params = boot_params {
|
||||||
|
hdr: setup_hdr,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Common bootparams settings
|
||||||
|
if params.hdr.type_of_loader == 0 {
|
||||||
|
params.hdr.type_of_loader = KERNEL_LOADER_OTHER;
|
||||||
|
}
|
||||||
|
params.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||||
|
params.hdr.cmdline_size = cmdline_size as u32;
|
||||||
|
|
||||||
|
if let Some(initramfs_config) = initramfs {
|
||||||
|
params.hdr.ramdisk_image = initramfs_config.address.raw_value() as u32;
|
||||||
|
params.hdr.ramdisk_size = initramfs_config.size as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_e820_entry(&mut params, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
|
||||||
|
|
||||||
|
let mem_end = guest_mem.last_addr();
|
||||||
|
if mem_end < layout::MEM_32BIT_RESERVED_START {
|
||||||
|
add_e820_entry(
|
||||||
|
&mut params,
|
||||||
|
layout::HIGH_RAM_START.raw_value(),
|
||||||
|
mem_end.unchecked_offset_from(layout::HIGH_RAM_START) + 1,
|
||||||
|
E820_RAM,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
add_e820_entry(
|
||||||
|
&mut params,
|
||||||
|
layout::HIGH_RAM_START.raw_value(),
|
||||||
|
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::HIGH_RAM_START),
|
||||||
|
E820_RAM,
|
||||||
|
)?;
|
||||||
|
if mem_end > layout::RAM_64BIT_START {
|
||||||
|
add_e820_entry(
|
||||||
|
&mut params,
|
||||||
|
layout::RAM_64BIT_START.raw_value(),
|
||||||
|
mem_end.unchecked_offset_from(layout::RAM_64BIT_START) + 1,
|
||||||
|
E820_RAM,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_e820_entry(
|
||||||
|
&mut params,
|
||||||
|
layout::PCI_MMCONFIG_START.0,
|
||||||
|
layout::PCI_MMCONFIG_SIZE,
|
||||||
|
E820_RESERVED,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(sgx_epc_region) = sgx_epc_region {
|
||||||
|
add_e820_entry(
|
||||||
|
&mut params,
|
||||||
|
sgx_epc_region.start().raw_value(),
|
||||||
|
sgx_epc_region.size(),
|
||||||
|
E820_RESERVED,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rsdp_addr) = rsdp_addr {
|
||||||
|
params.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>())
|
||||||
|
.ok_or(super::Error::ZeroPagePastRamEnd)?;
|
||||||
|
guest_mem
|
||||||
|
.write_obj(params, zero_page_addr)
|
||||||
|
.map_err(super::Error::ZeroPageSetup)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an e820 region to the e820 map.
|
||||||
|
/// Returns Ok(()) if successful, or an error if there is no space left in the map.
|
||||||
|
fn add_e820_entry(
|
||||||
|
params: &mut boot_params,
|
||||||
|
addr: u64,
|
||||||
|
size: u64,
|
||||||
|
mem_type: u32,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if params.e820_entries >= params.e820_table.len() as u8 {
|
||||||
|
return Err(Error::E820Configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
fn add_memmap_entry(memmap: &mut Vec<hvm_memmap_table_entry>, addr: u64, size: u64, mem_type: u32) {
|
fn add_memmap_entry(memmap: &mut Vec<hvm_memmap_table_entry>, addr: u64, size: u64, mem_type: u32) {
|
||||||
// Add the table entry to the vector
|
// Add the table entry to the vector
|
||||||
memmap.push(hvm_memmap_table_entry {
|
memmap.push(hvm_memmap_table_entry {
|
||||||
@@ -1378,6 +1551,7 @@ fn update_cpuid_sgx(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use linux_loader::loader::bootparam::boot_e820_entry;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn regions_base_addr() {
|
fn regions_base_addr() {
|
||||||
@@ -1394,8 +1568,10 @@ mod tests {
|
|||||||
let config_err = configure_system(
|
let config_err = configure_system(
|
||||||
&gm,
|
&gm,
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
|
0,
|
||||||
&None,
|
&None,
|
||||||
1,
|
1,
|
||||||
|
None,
|
||||||
Some(layout::RSDP_POINTER),
|
Some(layout::RSDP_POINTER),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
@@ -1417,6 +1593,7 @@ mod tests {
|
|||||||
configure_system(
|
configure_system(
|
||||||
&gm,
|
&gm,
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
|
0,
|
||||||
&None,
|
&None,
|
||||||
no_vcpus,
|
no_vcpus,
|
||||||
None,
|
None,
|
||||||
@@ -1425,6 +1602,7 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1445,6 +1623,7 @@ mod tests {
|
|||||||
configure_system(
|
configure_system(
|
||||||
&gm,
|
&gm,
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
|
0,
|
||||||
&None,
|
&None,
|
||||||
no_vcpus,
|
no_vcpus,
|
||||||
None,
|
None,
|
||||||
@@ -1453,12 +1632,14 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
configure_system(
|
configure_system(
|
||||||
&gm,
|
&gm,
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
|
0,
|
||||||
&None,
|
&None,
|
||||||
no_vcpus,
|
no_vcpus,
|
||||||
None,
|
None,
|
||||||
@@ -1467,10 +1648,51 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_e820_entry() {
|
||||||
|
let e820_table = [(boot_e820_entry {
|
||||||
|
addr: 0x1,
|
||||||
|
size: 4,
|
||||||
|
type_: 1,
|
||||||
|
}); 128];
|
||||||
|
|
||||||
|
let expected_params = boot_params {
|
||||||
|
e820_table,
|
||||||
|
e820_entries: 1,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut params: boot_params = Default::default();
|
||||||
|
add_e820_entry(
|
||||||
|
&mut params,
|
||||||
|
e820_table[0].addr,
|
||||||
|
e820_table[0].size,
|
||||||
|
e820_table[0].type_,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
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_table.len() as u8 + 1;
|
||||||
|
assert!(add_e820_entry(
|
||||||
|
&mut params,
|
||||||
|
e820_table[0].addr,
|
||||||
|
e820_table[0].size,
|
||||||
|
e820_table[0].type_
|
||||||
|
)
|
||||||
|
.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_memmap_entry() {
|
fn test_add_memmap_entry() {
|
||||||
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
@@ -15,7 +17,7 @@ pub const MP_IRQDIR_DEFAULT: ::std::os::raw::c_uint = 0;
|
|||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpf_intel {
|
pub struct mpf_intel {
|
||||||
pub signature: [::std::os::raw::c_char; 4usize],
|
pub signature: [::std::os::raw::c_uchar; 4usize],
|
||||||
pub physptr: ::std::os::raw::c_uint,
|
pub physptr: ::std::os::raw::c_uint,
|
||||||
pub length: ::std::os::raw::c_uchar,
|
pub length: ::std::os::raw::c_uchar,
|
||||||
pub specification: ::std::os::raw::c_uchar,
|
pub specification: ::std::os::raw::c_uchar,
|
||||||
@@ -30,12 +32,12 @@ pub struct mpf_intel {
|
|||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_table {
|
pub struct mpc_table {
|
||||||
pub signature: [::std::os::raw::c_char; 4usize],
|
pub signature: [::std::os::raw::c_uchar; 4usize],
|
||||||
pub length: ::std::os::raw::c_ushort,
|
pub length: ::std::os::raw::c_ushort,
|
||||||
pub spec: ::std::os::raw::c_char,
|
pub spec: ::std::os::raw::c_uchar,
|
||||||
pub checksum: ::std::os::raw::c_char,
|
pub checksum: ::std::os::raw::c_uchar,
|
||||||
pub oem: [::std::os::raw::c_char; 8usize],
|
pub oem: [::std::os::raw::c_uchar; 8usize],
|
||||||
pub productid: [::std::os::raw::c_char; 12usize],
|
pub productid: [::std::os::raw::c_uchar; 12usize],
|
||||||
pub oemptr: ::std::os::raw::c_uint,
|
pub oemptr: ::std::os::raw::c_uint,
|
||||||
pub oemsize: ::std::os::raw::c_ushort,
|
pub oemsize: ::std::os::raw::c_ushort,
|
||||||
pub oemcount: ::std::os::raw::c_ushort,
|
pub oemcount: ::std::os::raw::c_ushort,
|
||||||
@@ -104,9 +106,9 @@ pub struct mpc_lintsrc {
|
|||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_oemtable {
|
pub struct mpc_oemtable {
|
||||||
pub signature: [::std::os::raw::c_char; 4usize],
|
pub signature: [::std::os::raw::c_uchar; 4usize],
|
||||||
pub length: ::std::os::raw::c_ushort,
|
pub length: ::std::os::raw::c_ushort,
|
||||||
pub rev: ::std::os::raw::c_char,
|
pub rev: ::std::os::raw::c_uchar,
|
||||||
pub checksum: ::std::os::raw::c_char,
|
pub checksum: ::std::os::raw::c_uchar,
|
||||||
pub mpc: [::std::os::raw::c_char; 8usize],
|
pub mpc: [::std::os::raw::c_uchar; 8usize],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,11 @@
|
|||||||
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
||||||
use crate::x86_64::{get_x2apic_id, mpspec};
|
use crate::x86_64::{get_x2apic_id, mpspec};
|
||||||
use crate::GuestMemoryMmap;
|
use crate::GuestMemoryMmap;
|
||||||
use libc::c_char;
|
use libc::c_uchar;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
use thiserror::Error;
|
||||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
||||||
|
|
||||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||||
@@ -49,29 +50,40 @@ unsafe impl ByteValued for MpcLintsrcWrapper {}
|
|||||||
// SAFETY: see above
|
// SAFETY: see above
|
||||||
unsafe impl ByteValued for MpfIntelWrapper {}
|
unsafe impl ByteValued for MpfIntelWrapper {}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// There was too little guest memory to store the entire MP table.
|
/// There was too little guest memory to store the entire MP table.
|
||||||
|
#[error("There was too little guest memory to store the entire MP table")]
|
||||||
NotEnoughMemory,
|
NotEnoughMemory,
|
||||||
/// The MP table has too little address space to be stored.
|
/// The MP table has too little address space to be stored.
|
||||||
|
#[error("The MP table has too little address space to be stored")]
|
||||||
AddressOverflow,
|
AddressOverflow,
|
||||||
/// Failure while zeroing out the memory for the MP table.
|
/// Failure while zeroing out the memory for the MP table.
|
||||||
|
#[error("Failure while zeroing out the memory for the MP table: {0}")]
|
||||||
Clear(GuestMemoryError),
|
Clear(GuestMemoryError),
|
||||||
/// Number of CPUs exceeds the maximum supported CPUs
|
/// Number of CPUs exceeds the maximum supported CPUs
|
||||||
|
#[error("Number of CPUs exceeds the maximum supported CPUs")]
|
||||||
TooManyCpus,
|
TooManyCpus,
|
||||||
/// Failure to write the MP floating pointer.
|
/// Failure to write the MP floating pointer.
|
||||||
|
#[error("Failure to write the MP floating pointer: {0}")]
|
||||||
WriteMpfIntel(GuestMemoryError),
|
WriteMpfIntel(GuestMemoryError),
|
||||||
/// Failure to write MP CPU entry.
|
/// Failure to write MP CPU entry.
|
||||||
|
#[error("Failure to write MP CPU entry: {0}")]
|
||||||
WriteMpcCpu(GuestMemoryError),
|
WriteMpcCpu(GuestMemoryError),
|
||||||
/// Failure to write MP ioapic entry.
|
/// Failure to write MP ioapic entry.
|
||||||
|
#[error("Failure to write MP ioapic entry: {0}")]
|
||||||
WriteMpcIoapic(GuestMemoryError),
|
WriteMpcIoapic(GuestMemoryError),
|
||||||
/// Failure to write MP bus entry.
|
/// Failure to write MP bus entry.
|
||||||
|
#[error("Failure to write MP bus entry: {0}")]
|
||||||
WriteMpcBus(GuestMemoryError),
|
WriteMpcBus(GuestMemoryError),
|
||||||
/// Failure to write MP interrupt source entry.
|
/// Failure to write MP interrupt source entry.
|
||||||
|
#[error("Failure to write MP interrupt source entry: {0}")]
|
||||||
WriteMpcIntsrc(GuestMemoryError),
|
WriteMpcIntsrc(GuestMemoryError),
|
||||||
/// Failure to write MP local interrupt source entry.
|
/// Failure to write MP local interrupt source entry.
|
||||||
|
#[error("Failure to write MP local interrupt source entry: {0}")]
|
||||||
WriteMpcLintsrc(GuestMemoryError),
|
WriteMpcLintsrc(GuestMemoryError),
|
||||||
/// Failure to write MP table header.
|
/// Failure to write MP table header.
|
||||||
|
#[error("Failure to write MP table header: {0}")]
|
||||||
WriteMpcTable(GuestMemoryError),
|
WriteMpcTable(GuestMemoryError),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,18 +94,13 @@ pub type Result<T> = result::Result<T, Error>;
|
|||||||
// a large number for FC usecases.
|
// a large number for FC usecases.
|
||||||
pub const MAX_SUPPORTED_CPUS: u32 = 254;
|
pub const MAX_SUPPORTED_CPUS: u32 = 254;
|
||||||
|
|
||||||
// Convenience macro for making arrays of diverse character types.
|
|
||||||
macro_rules! char_array {
|
|
||||||
($t:ty; $( $c:expr ),*) => ( [ $( $c as $t ),* ] )
|
|
||||||
}
|
|
||||||
|
|
||||||
// Most of these variables are sourced from the Intel MP Spec 1.4.
|
// Most of these variables are sourced from the Intel MP Spec 1.4.
|
||||||
const SMP_MAGIC_IDENT: [c_char; 4] = char_array!(c_char; '_', 'M', 'P', '_');
|
const SMP_MAGIC_IDENT: &[c_uchar; 4] = b"_MP_";
|
||||||
const MPC_SIGNATURE: [c_char; 4] = char_array!(c_char; 'P', 'C', 'M', 'P');
|
const MPC_SIGNATURE: &[c_uchar; 4] = b"PCMP";
|
||||||
const MPC_SPEC: i8 = 4;
|
const MPC_SPEC: u8 = 4;
|
||||||
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'F', 'C', ' ', ' ', ' ', ' ', ' ', ' ');
|
const MPC_OEM: &[c_uchar; 8] = b"FC ";
|
||||||
const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
|
const MPC_PRODUCT_ID: &[c_uchar; 12] = &[b'0'; 12];
|
||||||
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
|
const BUS_TYPE_ISA: &[c_uchar; 6] = b"ISA ";
|
||||||
const APIC_VERSION: u8 = 0x14;
|
const APIC_VERSION: u8 = 0x14;
|
||||||
const CPU_STEPPING: u32 = 0x600;
|
const CPU_STEPPING: u32 = 0x600;
|
||||||
const CPU_FEATURE_APIC: u32 = 0x200;
|
const CPU_FEATURE_APIC: u32 = 0x200;
|
||||||
@@ -168,7 +175,7 @@ pub fn setup_mptable(
|
|||||||
{
|
{
|
||||||
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
||||||
let size = mem::size_of::<MpfIntelWrapper>() as u64;
|
let size = mem::size_of::<MpfIntelWrapper>() as u64;
|
||||||
mpf_intel.0.signature = SMP_MAGIC_IDENT;
|
mpf_intel.0.signature = *SMP_MAGIC_IDENT;
|
||||||
mpf_intel.0.length = 1;
|
mpf_intel.0.length = 1;
|
||||||
mpf_intel.0.specification = 4;
|
mpf_intel.0.specification = 4;
|
||||||
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
|
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
|
||||||
@@ -209,7 +216,7 @@ pub fn setup_mptable(
|
|||||||
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
|
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
|
||||||
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
|
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
|
||||||
mpc_bus.0.busid = 0;
|
mpc_bus.0.busid = 0;
|
||||||
mpc_bus.0.bustype = BUS_TYPE_ISA;
|
mpc_bus.0.bustype = *BUS_TYPE_ISA;
|
||||||
mem.write_obj(mpc_bus, base_mp)
|
mem.write_obj(mpc_bus, base_mp)
|
||||||
.map_err(Error::WriteMpcBus)?;
|
.map_err(Error::WriteMpcBus)?;
|
||||||
base_mp = base_mp.unchecked_add(size as u64);
|
base_mp = base_mp.unchecked_add(size as u64);
|
||||||
@@ -280,14 +287,14 @@ pub fn setup_mptable(
|
|||||||
|
|
||||||
{
|
{
|
||||||
let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default());
|
let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default());
|
||||||
mpc_table.0.signature = MPC_SIGNATURE;
|
mpc_table.0.signature = *MPC_SIGNATURE;
|
||||||
mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16;
|
mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16;
|
||||||
mpc_table.0.spec = MPC_SPEC;
|
mpc_table.0.spec = MPC_SPEC;
|
||||||
mpc_table.0.oem = MPC_OEM;
|
mpc_table.0.oem = *MPC_OEM;
|
||||||
mpc_table.0.productid = MPC_PRODUCT_ID;
|
mpc_table.0.productid = *MPC_PRODUCT_ID;
|
||||||
mpc_table.0.lapic = APIC_START.0 as u32;
|
mpc_table.0.lapic = APIC_START.0 as u32;
|
||||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
||||||
mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8;
|
mpc_table.0.checksum = (!checksum).wrapping_add(1);
|
||||||
mem.write_obj(mpc_table, table_base)
|
mem.write_obj(mpc_table, table_base)
|
||||||
.map_err(Error::WriteMpcTable)?;
|
.map_err(Error::WriteMpcTable)?;
|
||||||
}
|
}
|
||||||
@@ -300,8 +307,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::layout::MPTABLE_START;
|
use crate::layout::MPTABLE_START;
|
||||||
use vm_memory::{
|
use vm_memory::{
|
||||||
bitmap::BitmapSlice, GuestAddress, GuestUsize, VolatileMemoryError, VolatileSlice,
|
bitmap::BitmapSlice, GuestUsize, VolatileMemoryError, VolatileSlice, WriteVolatile,
|
||||||
WriteVolatile,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn table_entry_size(type_: u8) -> usize {
|
fn table_entry_size(type_: u8) -> usize {
|
||||||
|
|||||||
@@ -6,40 +6,55 @@
|
|||||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// 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
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
use crate::layout::{BOOT_GDT_START, BOOT_IDT_START, PVH_INFO_START};
|
use crate::layout::{
|
||||||
use crate::GuestMemoryMmap;
|
BOOT_GDT_START, BOOT_IDT_START, BOOT_STACK_POINTER, PVH_INFO_START, ZERO_PAGE_START,
|
||||||
|
};
|
||||||
|
use crate::{EntryPoint, GuestMemoryMmap};
|
||||||
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
||||||
use hypervisor::arch::x86::regs::CR0_PE;
|
use hypervisor::arch::x86::regs::CR0_PE;
|
||||||
use hypervisor::arch::x86::{FpuState, SpecialRegisters, StandardRegisters};
|
use hypervisor::arch::x86::{FpuState, SpecialRegisters, StandardRegisters};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{mem, result};
|
use std::{mem, result};
|
||||||
|
use thiserror::Error;
|
||||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError};
|
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failed to get SREGs for this CPU.
|
/// Failed to get SREGs for this CPU.
|
||||||
|
#[error("Failed to get SREGs for this CPU: {0}")]
|
||||||
GetStatusRegisters(hypervisor::HypervisorCpuError),
|
GetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||||
/// Failed to set base registers for this CPU.
|
/// Failed to set base registers for this CPU.
|
||||||
|
#[error("Failed to set base registers for this CPU: {0}")]
|
||||||
SetBaseRegisters(hypervisor::HypervisorCpuError),
|
SetBaseRegisters(hypervisor::HypervisorCpuError),
|
||||||
/// Failed to configure the FPU.
|
/// Failed to configure the FPU.
|
||||||
|
#[error("Failed to configure the FPU: {0}")]
|
||||||
SetFpuRegisters(hypervisor::HypervisorCpuError),
|
SetFpuRegisters(hypervisor::HypervisorCpuError),
|
||||||
/// Setting up MSRs failed.
|
/// Setting up MSRs failed.
|
||||||
|
#[error("Setting up MSRs failed: {0}")]
|
||||||
SetModelSpecificRegisters(hypervisor::HypervisorCpuError),
|
SetModelSpecificRegisters(hypervisor::HypervisorCpuError),
|
||||||
/// Failed to set SREGs for this CPU.
|
/// Failed to set SREGs for this CPU.
|
||||||
|
#[error("Failed to set SREGs for this CPU: {0}")]
|
||||||
SetStatusRegisters(hypervisor::HypervisorCpuError),
|
SetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||||
/// Checking the GDT address failed.
|
/// Checking the GDT address failed.
|
||||||
|
#[error("Checking the GDT address failed")]
|
||||||
CheckGdtAddr,
|
CheckGdtAddr,
|
||||||
/// Writing the GDT to RAM failed.
|
/// Writing the GDT to RAM failed.
|
||||||
|
#[error("Writing the GDT to RAM failed: {0}")]
|
||||||
WriteGdt(GuestMemoryError),
|
WriteGdt(GuestMemoryError),
|
||||||
/// Writing the IDT to RAM failed.
|
/// Writing the IDT to RAM failed.
|
||||||
|
#[error("Writing the IDT to RAM failed: {0}")]
|
||||||
WriteIdt(GuestMemoryError),
|
WriteIdt(GuestMemoryError),
|
||||||
/// Writing PDPTE to RAM failed.
|
/// Writing PDPTE to RAM failed.
|
||||||
|
#[error("Writing PDPTE to RAM failed: {0}")]
|
||||||
WritePdpteAddress(GuestMemoryError),
|
WritePdpteAddress(GuestMemoryError),
|
||||||
/// Writing PDE to RAM failed.
|
/// Writing PDE to RAM failed.
|
||||||
|
#[error("Writing PDE to RAM failed: {0}")]
|
||||||
WritePdeAddress(GuestMemoryError),
|
WritePdeAddress(GuestMemoryError),
|
||||||
/// Writing PML4 to RAM failed.
|
/// Writing PML4 to RAM failed.
|
||||||
|
#[error("Writing PML4 to RAM failed: {0}")]
|
||||||
WritePml4Address(GuestMemoryError),
|
WritePml4Address(GuestMemoryError),
|
||||||
/// Writing PML5 to RAM failed.
|
/// Writing PML5 to RAM failed.
|
||||||
|
#[error("Writing PML5 to RAM failed: {0}")]
|
||||||
WritePml5Address(GuestMemoryError),
|
WritePml5Address(GuestMemoryError),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,13 +92,22 @@ pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
/// * `boot_ip` - Starting instruction pointer.
|
/// * `entry_point` - Description of the boot entry to set up.
|
||||||
pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, boot_ip: u64) -> Result<()> {
|
pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, entry_point: EntryPoint) -> Result<()> {
|
||||||
let regs = StandardRegisters {
|
let regs = match entry_point.setup_header {
|
||||||
rflags: 0x0000000000000002u64,
|
None => StandardRegisters {
|
||||||
rbx: PVH_INFO_START.raw_value(),
|
rflags: 0x0000000000000002u64,
|
||||||
rip: boot_ip,
|
rip: entry_point.entry_addr.raw_value(),
|
||||||
..Default::default()
|
rbx: PVH_INFO_START.raw_value(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Some(_) => StandardRegisters {
|
||||||
|
rflags: 0x0000000000000002u64,
|
||||||
|
rip: entry_point.entry_addr.raw_value(),
|
||||||
|
rsp: BOOT_STACK_POINTER.raw_value(),
|
||||||
|
rsi: ZERO_PAGE_START.raw_value(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
};
|
};
|
||||||
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
||||||
}
|
}
|
||||||
@@ -164,7 +188,6 @@ pub fn configure_segments_and_sregs(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::GuestMemoryMmap;
|
|
||||||
use vm_memory::GuestAddress;
|
use vm_memory::GuestAddress;
|
||||||
|
|
||||||
fn create_guest_mem() -> GuestMemoryMmap {
|
fn create_guest_mem() -> GuestMemoryMmap {
|
||||||
|
|||||||
@@ -8,53 +8,36 @@
|
|||||||
|
|
||||||
use crate::layout::SMBIOS_START;
|
use crate::layout::SMBIOS_START;
|
||||||
use crate::GuestMemoryMmap;
|
use crate::GuestMemoryMmap;
|
||||||
use std::fmt::{self, Display};
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use vm_memory::ByteValued;
|
use vm_memory::ByteValued;
|
||||||
use vm_memory::{Address, Bytes, GuestAddress};
|
use vm_memory::{Address, Bytes, GuestAddress};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// There was too little guest memory to store the entire SMBIOS table.
|
/// There was too little guest memory to store the entire SMBIOS table.
|
||||||
|
#[error("There was too little guest memory to store the SMBIOS table")]
|
||||||
NotEnoughMemory,
|
NotEnoughMemory,
|
||||||
/// The SMBIOS table has too little address space to be stored.
|
/// The SMBIOS table has too little address space to be stored.
|
||||||
|
#[error("The SMBIOS table has too little address space to be stored")]
|
||||||
AddressOverflow,
|
AddressOverflow,
|
||||||
/// Failure while zeroing out the memory for the SMBIOS table.
|
/// Failure while zeroing out the memory for the SMBIOS table.
|
||||||
|
#[error("Failure while zeroing out the memory for the SMBIOS table")]
|
||||||
Clear,
|
Clear,
|
||||||
/// Failure to write SMBIOS entrypoint structure
|
/// Failure to write SMBIOS entrypoint structure
|
||||||
|
#[error("Failure to write SMBIOS entrypoint structure")]
|
||||||
WriteSmbiosEp,
|
WriteSmbiosEp,
|
||||||
/// Failure to write additional data to memory
|
/// Failure to write additional data to memory
|
||||||
|
#[error("Failure to write additional data to memory")]
|
||||||
WriteData,
|
WriteData,
|
||||||
/// Failure to parse uuid, uuid format may be error
|
/// Failure to parse uuid, uuid format may be error
|
||||||
|
#[error("Failure to parse uuid: {0}")]
|
||||||
ParseUuid(uuid::Error),
|
ParseUuid(uuid::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
|
|
||||||
impl Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
use self::Error::*;
|
|
||||||
|
|
||||||
let description = match self {
|
|
||||||
NotEnoughMemory => {
|
|
||||||
"There was too little guest memory to store the SMBIOS table".to_string()
|
|
||||||
}
|
|
||||||
AddressOverflow => {
|
|
||||||
"The SMBIOS table has too little address space to be stored".to_string()
|
|
||||||
}
|
|
||||||
Clear => "Failure while zeroing out the memory for the SMBIOS table".to_string(),
|
|
||||||
WriteSmbiosEp => "Failure to write SMBIOS entrypoint structure".to_string(),
|
|
||||||
WriteData => "Failure to write additional data to memory".to_string(),
|
|
||||||
ParseUuid(e) => format!("Failure to parse uuid: {e}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
write!(f, "SMBIOS error: {description}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
// Constants sourced from SMBIOS Spec 3.2.0.
|
// Constants sourced from SMBIOS Spec 3.2.0.
|
||||||
|
|||||||
@@ -1,27 +1,30 @@
|
|||||||
[package]
|
[package]
|
||||||
|
authors = ["The Chromium OS Authors", "The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2021"
|
||||||
name = "block"
|
name = "block"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
|
||||||
authors = ["The Cloud Hypervisor Authors", "The Chromium OS Authors"]
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
io_uring = ["dep:io-uring"]
|
io_uring = ["dep:io-uring"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "1.4.3"
|
byteorder = "1.5.0"
|
||||||
crc-any = "2.4.4"
|
crc-any = "2.4.4"
|
||||||
io-uring = { version = "0.6.2", optional = true }
|
io-uring = { version = "0.6.3", optional = true }
|
||||||
libc = "0.2.153"
|
libc = "0.2.155"
|
||||||
log = "0.4.20"
|
log = "0.4.22"
|
||||||
remain = "0.2.11"
|
remain = "0.2.14"
|
||||||
smallvec = "1.13.1"
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
thiserror = "1.0.52"
|
smallvec = "1.13.2"
|
||||||
uuid = { version = "1.3.4", features = ["v4"] }
|
thiserror = "1.0.62"
|
||||||
versionize = "0.2.0"
|
uuid = { version = "1.8.0", features = ["v4"] }
|
||||||
versionize_derive = "0.1.6"
|
virtio-bindings = { version = "0.2.2", features = ["virtio-v5_0_0"] }
|
||||||
virtio-bindings = { version = "0.2.0", features = ["virtio-v5_0_0"] }
|
virtio-queue = "0.12.0"
|
||||||
virtio-queue = "0.11.0"
|
vm-memory = { version = "0.14.1", features = [
|
||||||
vm-memory = { version = "0.14.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
|
"backend-atomic",
|
||||||
|
"backend-bitmap",
|
||||||
|
"backend-mmap",
|
||||||
|
] }
|
||||||
vm-virtio = { path = "../vm-virtio" }
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
vmm-sys-util = "0.12.1"
|
vmm-sys-util = "0.12.1"
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ use crate::vhdx::{Vhdx, VhdxError};
|
|||||||
#[cfg(feature = "io_uring")]
|
#[cfg(feature = "io_uring")]
|
||||||
use io_uring::{opcode, IoUring, Probe};
|
use io_uring::{opcode, IoUring, Probe};
|
||||||
use libc::{ioctl, S_IFBLK, S_IFMT};
|
use libc::{ioctl, S_IFBLK, S_IFMT};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
||||||
@@ -53,21 +53,17 @@ use std::sync::Arc;
|
|||||||
use std::sync::MutexGuard;
|
use std::sync::MutexGuard;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
|
||||||
use versionize_derive::Versionize;
|
|
||||||
use virtio_bindings::virtio_blk::*;
|
use virtio_bindings::virtio_blk::*;
|
||||||
use virtio_queue::DescriptorChain;
|
use virtio_queue::DescriptorChain;
|
||||||
use vm_memory::{
|
use vm_memory::{
|
||||||
bitmap::AtomicBitmap, bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory,
|
bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError,
|
||||||
GuestMemoryError, GuestMemoryLoadGuard,
|
GuestMemoryLoadGuard,
|
||||||
};
|
};
|
||||||
use vm_virtio::{AccessPlatform, Translatable};
|
use vm_virtio::{AccessPlatform, Translatable};
|
||||||
use vmm_sys_util::aio;
|
use vmm_sys_util::aio;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
use vmm_sys_util::{ioctl_io_nr, ioctl_ioc_nr};
|
use vmm_sys_util::{ioctl_io_nr, ioctl_ioc_nr};
|
||||||
|
|
||||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
|
||||||
|
|
||||||
const SECTOR_SHIFT: u8 = 9;
|
const SECTOR_SHIFT: u8 = 9;
|
||||||
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
||||||
|
|
||||||
@@ -168,8 +164,8 @@ pub enum ExecuteError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ExecuteError {
|
impl ExecuteError {
|
||||||
pub fn status(&self) -> u32 {
|
pub fn status(&self) -> u8 {
|
||||||
match *self {
|
let status = match *self {
|
||||||
ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
|
||||||
@@ -184,7 +180,8 @@ impl ExecuteError {
|
|||||||
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::TemporaryBufferAllocation(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::TemporaryBufferAllocation(_) => VIRTIO_BLK_S_IOERR,
|
||||||
}
|
};
|
||||||
|
status as u8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,8 +194,8 @@ pub enum RequestType {
|
|||||||
Unsupported(u32),
|
Unsupported(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_type(
|
pub fn request_type<B: Bitmap + 'static>(
|
||||||
mem: &GuestMemoryMmap,
|
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||||
desc_addr: GuestAddress,
|
desc_addr: GuestAddress,
|
||||||
) -> result::Result<RequestType, Error> {
|
) -> result::Result<RequestType, Error> {
|
||||||
let type_ = mem.read_obj(desc_addr).map_err(Error::GuestMemory)?;
|
let type_ = mem.read_obj(desc_addr).map_err(Error::GuestMemory)?;
|
||||||
@@ -211,7 +208,10 @@ pub fn request_type(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sector(mem: &GuestMemoryMmap, desc_addr: GuestAddress) -> result::Result<u64, Error> {
|
fn sector<B: Bitmap + 'static>(
|
||||||
|
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||||
|
desc_addr: GuestAddress,
|
||||||
|
) -> result::Result<u64, Error> {
|
||||||
const SECTOR_OFFSET: usize = 8;
|
const SECTOR_OFFSET: usize = 8;
|
||||||
let addr = match mem.checked_offset(desc_addr, SECTOR_OFFSET) {
|
let addr = match mem.checked_offset(desc_addr, SECTOR_OFFSET) {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
@@ -241,16 +241,15 @@ pub struct Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
pub fn parse(
|
pub fn parse<B: Bitmap + 'static>(
|
||||||
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap>>,
|
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<vm_memory::GuestMemoryMmap<B>>>,
|
||||||
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
||||||
) -> result::Result<Request, Error> {
|
) -> result::Result<Request, Error> {
|
||||||
let hdr_desc = desc_chain
|
let hdr_desc = desc_chain
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::DescriptorChainTooShort)
|
.ok_or(Error::DescriptorChainTooShort)
|
||||||
.map_err(|e| {
|
.inspect_err(|_| {
|
||||||
error!("Missing head descriptor");
|
error!("Missing head descriptor");
|
||||||
e
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// The head contains the request type which MUST be readable.
|
// The head contains the request type which MUST be readable.
|
||||||
@@ -276,9 +275,8 @@ impl Request {
|
|||||||
let mut desc = desc_chain
|
let mut desc = desc_chain
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::DescriptorChainTooShort)
|
.ok_or(Error::DescriptorChainTooShort)
|
||||||
.map_err(|e| {
|
.inspect_err(|_| {
|
||||||
error!("Only head descriptor present: request = {:?}", req);
|
error!("Only head descriptor present: request = {:?}", req);
|
||||||
e
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !desc.has_next() {
|
if !desc.has_next() {
|
||||||
@@ -309,9 +307,8 @@ impl Request {
|
|||||||
desc = desc_chain
|
desc = desc_chain
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::DescriptorChainTooShort)
|
.ok_or(Error::DescriptorChainTooShort)
|
||||||
.map_err(|e| {
|
.inspect_err(|_| {
|
||||||
error!("DescriptorChain corrupted: request = {:?}", req);
|
error!("DescriptorChain corrupted: request = {:?}", req);
|
||||||
e
|
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
status_desc = desc;
|
status_desc = desc;
|
||||||
@@ -333,11 +330,11 @@ impl Request {
|
|||||||
Ok(req)
|
Ok(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute<T: Seek + Read + Write>(
|
pub fn execute<T: Seek + Read + Write, B: Bitmap + 'static>(
|
||||||
&self,
|
&self,
|
||||||
disk: &mut T,
|
disk: &mut T,
|
||||||
disk_nsectors: u64,
|
disk_nsectors: u64,
|
||||||
mem: &GuestMemoryMmap,
|
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||||
serial: &[u8],
|
serial: &[u8],
|
||||||
) -> result::Result<u32, ExecuteError> {
|
) -> result::Result<u32, ExecuteError> {
|
||||||
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
|
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
|
||||||
@@ -390,9 +387,9 @@ impl Request {
|
|||||||
Ok(len)
|
Ok(len)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute_async(
|
pub fn execute_async<B: Bitmap + 'static>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mem: &GuestMemoryMmap,
|
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||||
disk_nsectors: u64,
|
disk_nsectors: u64,
|
||||||
disk_image: &mut dyn AsyncIo,
|
disk_image: &mut dyn AsyncIo,
|
||||||
serial: &[u8],
|
serial: &[u8],
|
||||||
@@ -545,7 +542,7 @@ impl Request {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Versionize)]
|
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
pub struct VirtioBlockConfig {
|
pub struct VirtioBlockConfig {
|
||||||
pub capacity: u64,
|
pub capacity: u64,
|
||||||
@@ -568,7 +565,7 @@ pub struct VirtioBlockConfig {
|
|||||||
pub write_zeroes_may_unmap: u8,
|
pub write_zeroes_may_unmap: u8,
|
||||||
pub unused1: [u8; 3],
|
pub unused1: [u8; 3],
|
||||||
}
|
}
|
||||||
#[derive(Copy, Clone, Debug, Default, Versionize)]
|
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
pub struct VirtioBlockGeometry {
|
pub struct VirtioBlockGeometry {
|
||||||
pub cylinders: u16,
|
pub cylinders: u16,
|
||||||
@@ -660,7 +657,9 @@ where
|
|||||||
let mut slices: SmallVec<[IoSliceMut; 1]> = SmallVec::with_capacity(iovecs.len());
|
let mut slices: SmallVec<[IoSliceMut; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||||
for iovec in iovecs.iter() {
|
for iovec in iovecs.iter() {
|
||||||
// SAFETY: on Linux IoSliceMut wraps around libc::iovec
|
// SAFETY: on Linux IoSliceMut wraps around libc::iovec
|
||||||
slices.push(IoSliceMut::new(unsafe { std::mem::transmute(*iovec) }));
|
slices.push(IoSliceMut::new(unsafe {
|
||||||
|
std::mem::transmute::<libc::iovec, &mut [u8]>(*iovec)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
@@ -693,7 +692,9 @@ where
|
|||||||
let mut slices: SmallVec<[IoSlice; 1]> = SmallVec::with_capacity(iovecs.len());
|
let mut slices: SmallVec<[IoSlice; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||||
for iovec in iovecs.iter() {
|
for iovec in iovecs.iter() {
|
||||||
// SAFETY: on Linux IoSlice wraps around libc::iovec
|
// SAFETY: on Linux IoSlice wraps around libc::iovec
|
||||||
slices.push(IoSlice::new(unsafe { std::mem::transmute(*iovec) }));
|
slices.push(IoSlice::new(unsafe {
|
||||||
|
std::mem::transmute::<libc::iovec, &mut [u8]>(*iovec)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
mod qcow_raw_file;
|
mod qcow_raw_file;
|
||||||
mod raw_file;
|
mod raw_file;
|
||||||
@@ -17,11 +19,11 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
|||||||
use libc::{EINVAL, ENOSPC, ENOTSUP};
|
use libc::{EINVAL, ENOSPC, ENOTSUP};
|
||||||
use remain::sorted;
|
use remain::sorted;
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
use std::fmt::{self, Display};
|
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
use thiserror::Error;
|
||||||
use vmm_sys_util::{
|
use vmm_sys_util::{
|
||||||
file_traits::FileSetLen, file_traits::FileSync, seek_hole::SeekHole, write_zeroes::PunchHole,
|
file_traits::FileSetLen, file_traits::FileSync, seek_hole::SeekHole, write_zeroes::PunchHole,
|
||||||
write_zeroes::WriteZeroesAt,
|
write_zeroes::WriteZeroesAt,
|
||||||
@@ -29,106 +31,98 @@ use vmm_sys_util::{
|
|||||||
|
|
||||||
pub use crate::qcow::raw_file::RawFile;
|
pub use crate::qcow::raw_file::RawFile;
|
||||||
|
|
||||||
|
/// Nesting depth limit for disk formats that can open other disk files.
|
||||||
|
const MAX_NESTING_DEPTH: u32 = 10;
|
||||||
|
|
||||||
#[sorted]
|
#[sorted]
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
#[error("Backing file io error: {0}")]
|
||||||
BackingFileIo(io::Error),
|
BackingFileIo(io::Error),
|
||||||
BackingFileOpen(Box<crate::Error>),
|
#[error("Backing file open error: {0}")]
|
||||||
|
BackingFileOpen(Box<Error>),
|
||||||
|
#[error("Backing file name is too long: {0} bytes over")]
|
||||||
BackingFileTooLong(usize),
|
BackingFileTooLong(usize),
|
||||||
|
#[error("Compressed blocks not supported")]
|
||||||
CompressedBlocksNotSupported,
|
CompressedBlocksNotSupported,
|
||||||
|
#[error("Failed to evict cache: {0}")]
|
||||||
EvictingCache(io::Error),
|
EvictingCache(io::Error),
|
||||||
|
#[error("File larger than max of {}: {0}", MAX_QCOW_FILE_SIZE)]
|
||||||
FileTooBig(u64),
|
FileTooBig(u64),
|
||||||
|
#[error("Failed to get file size: {0}")]
|
||||||
GettingFileSize(io::Error),
|
GettingFileSize(io::Error),
|
||||||
|
#[error("Failed to get refcount: {0}")]
|
||||||
GettingRefcount(refcount::Error),
|
GettingRefcount(refcount::Error),
|
||||||
|
#[error("Failed to parse filename: {0}")]
|
||||||
InvalidBackingFileName(str::Utf8Error),
|
InvalidBackingFileName(str::Utf8Error),
|
||||||
|
#[error("Invalid cluster index")]
|
||||||
InvalidClusterIndex,
|
InvalidClusterIndex,
|
||||||
|
#[error("Invalid cluster size")]
|
||||||
InvalidClusterSize,
|
InvalidClusterSize,
|
||||||
|
#[error("Invalid index")]
|
||||||
InvalidIndex,
|
InvalidIndex,
|
||||||
|
#[error("Invalid L1 table offset")]
|
||||||
InvalidL1TableOffset,
|
InvalidL1TableOffset,
|
||||||
|
#[error("Invalid L1 table size: {0}")]
|
||||||
InvalidL1TableSize(u32),
|
InvalidL1TableSize(u32),
|
||||||
|
#[error("Invalid magic")]
|
||||||
InvalidMagic,
|
InvalidMagic,
|
||||||
|
#[error("Invalid offset: {0}")]
|
||||||
InvalidOffset(u64),
|
InvalidOffset(u64),
|
||||||
|
#[error("Invalid refcount table offset")]
|
||||||
InvalidRefcountTableOffset,
|
InvalidRefcountTableOffset,
|
||||||
|
#[error("Invalid refcount table size: {0}")]
|
||||||
InvalidRefcountTableSize(u64),
|
InvalidRefcountTableSize(u64),
|
||||||
|
#[error("Maximum disk nesting depth exceeded")]
|
||||||
|
MaxNestingDepthExceeded,
|
||||||
|
#[error("No free clusters")]
|
||||||
NoFreeClusters,
|
NoFreeClusters,
|
||||||
|
#[error("No refcount clusters")]
|
||||||
NoRefcountClusters,
|
NoRefcountClusters,
|
||||||
|
#[error("Not enough space for refcounts")]
|
||||||
NotEnoughSpaceForRefcounts,
|
NotEnoughSpaceForRefcounts,
|
||||||
|
#[error("Failed to open file {0}")]
|
||||||
OpeningFile(io::Error),
|
OpeningFile(io::Error),
|
||||||
|
#[error("Failed to read data: {0}")]
|
||||||
ReadingData(io::Error),
|
ReadingData(io::Error),
|
||||||
|
#[error("Failed to read header: {0}")]
|
||||||
ReadingHeader(io::Error),
|
ReadingHeader(io::Error),
|
||||||
|
#[error("Failed to read pointers: {0}")]
|
||||||
ReadingPointers(io::Error),
|
ReadingPointers(io::Error),
|
||||||
|
#[error("Failed to read ref count block: {0}")]
|
||||||
ReadingRefCountBlock(refcount::Error),
|
ReadingRefCountBlock(refcount::Error),
|
||||||
|
#[error("Failed to read ref counts: {0}")]
|
||||||
ReadingRefCounts(io::Error),
|
ReadingRefCounts(io::Error),
|
||||||
|
#[error("Failed to rebuild ref counts: {0}")]
|
||||||
RebuildingRefCounts(io::Error),
|
RebuildingRefCounts(io::Error),
|
||||||
|
#[error("Refcount table offset past file end")]
|
||||||
RefcountTableOffEnd,
|
RefcountTableOffEnd,
|
||||||
|
#[error("Too many clusters specified for refcount")]
|
||||||
RefcountTableTooLarge,
|
RefcountTableTooLarge,
|
||||||
|
#[error("Failed to seek file: {0}")]
|
||||||
SeekingFile(io::Error),
|
SeekingFile(io::Error),
|
||||||
|
#[error("Failed to set file size: {0}")]
|
||||||
SettingFileSize(io::Error),
|
SettingFileSize(io::Error),
|
||||||
|
#[error("Failed to set refcount refcount: {0}")]
|
||||||
SettingRefcountRefcount(io::Error),
|
SettingRefcountRefcount(io::Error),
|
||||||
|
#[error("Size too small for number of clusters")]
|
||||||
SizeTooSmallForNumberOfClusters,
|
SizeTooSmallForNumberOfClusters,
|
||||||
|
#[error("L1 entry table too large: {0}")]
|
||||||
TooManyL1Entries(u64),
|
TooManyL1Entries(u64),
|
||||||
|
#[error("Ref count table too large: {0}")]
|
||||||
TooManyRefcounts(u64),
|
TooManyRefcounts(u64),
|
||||||
|
#[error("Unsupported refcount order")]
|
||||||
UnsupportedRefcountOrder,
|
UnsupportedRefcountOrder,
|
||||||
|
#[error("Unsupported version: {0}")]
|
||||||
UnsupportedVersion(u32),
|
UnsupportedVersion(u32),
|
||||||
|
#[error("Failed to write data: {0}")]
|
||||||
WritingData(io::Error),
|
WritingData(io::Error),
|
||||||
|
#[error("Failed to write header: {0}")]
|
||||||
WritingHeader(io::Error),
|
WritingHeader(io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
impl Display for Error {
|
|
||||||
#[remain::check]
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
use self::Error::*;
|
|
||||||
|
|
||||||
#[sorted]
|
|
||||||
match self {
|
|
||||||
BackingFileIo(e) => write!(f, "backing file io error: {}", e),
|
|
||||||
BackingFileOpen(e) => write!(f, "backing file open error: {}", *e),
|
|
||||||
BackingFileTooLong(len) => {
|
|
||||||
write!(f, "backing file name is too long: {} bytes over", len)
|
|
||||||
}
|
|
||||||
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}"),
|
|
||||||
InvalidBackingFileName(e) => write!(f, "failed to parse filename: {}", 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(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}"),
|
|
||||||
ReadingPointers(e) => write!(f, "failed to read pointers: {e}"),
|
|
||||||
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}"),
|
|
||||||
WritingHeader(e) => write!(f, "failed to write header: {e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ImageType {
|
pub enum ImageType {
|
||||||
Raw,
|
Raw,
|
||||||
Qcow2,
|
Qcow2,
|
||||||
@@ -445,12 +439,20 @@ pub struct QcowFile {
|
|||||||
// List of unreferenced clusters available to be used. unref clusters become available once the
|
// List of unreferenced clusters available to be used. unref clusters become available once the
|
||||||
// removal of references to them have been synced to disk.
|
// removal of references to them have been synced to disk.
|
||||||
avail_clusters: Vec<u64>,
|
avail_clusters: Vec<u64>,
|
||||||
backing_file: Option<Box<dyn BlockBackend>>,
|
backing_file: Option<Box<Self>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QcowFile {
|
impl QcowFile {
|
||||||
/// Creates a QcowFile from `file`. File must be a valid qcow2 image.
|
/// Creates a QcowFile from `file`. File must be a valid qcow2 image.
|
||||||
pub fn from(mut file: RawFile) -> Result<QcowFile> {
|
///
|
||||||
|
/// Additionally, max nesting depth of this qcow2 image will be set to default value 10.
|
||||||
|
pub fn from(file: RawFile) -> Result<QcowFile> {
|
||||||
|
Self::from_with_nesting_depth(file, MAX_NESTING_DEPTH)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a QcowFile from `file` and with a max nesting depth. File must be a valid qcow2
|
||||||
|
/// image.
|
||||||
|
pub fn from_with_nesting_depth(mut file: RawFile, max_nesting_depth: u32) -> Result<QcowFile> {
|
||||||
let header = QcowHeader::new(&mut file)?;
|
let header = QcowHeader::new(&mut file)?;
|
||||||
|
|
||||||
// Only v2 and v3 files are supported.
|
// Only v2 and v3 files are supported.
|
||||||
@@ -477,14 +479,20 @@ impl QcowFile {
|
|||||||
let direct_io = file.is_direct();
|
let direct_io = file.is_direct();
|
||||||
|
|
||||||
let backing_file = if let Some(backing_file_path) = header.backing_file_path.as_ref() {
|
let backing_file = if let Some(backing_file_path) = header.backing_file_path.as_ref() {
|
||||||
|
if max_nesting_depth == 0 {
|
||||||
|
return Err(Error::MaxNestingDepthExceeded);
|
||||||
|
}
|
||||||
let path = backing_file_path.clone();
|
let path = backing_file_path.clone();
|
||||||
let backing_raw_file = OpenOptions::new()
|
let backing_raw_file = OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
.open(path)
|
.open(path)
|
||||||
.map_err(Error::BackingFileIo)?;
|
.map_err(Error::BackingFileIo)?;
|
||||||
let backing_file = crate::create_disk_file(backing_raw_file, direct_io)
|
let backing_file = Self::from_with_nesting_depth(
|
||||||
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
RawFile::new(backing_raw_file, direct_io),
|
||||||
Some(backing_file)
|
max_nesting_depth - 1,
|
||||||
|
)
|
||||||
|
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||||
|
Some(Box::new(backing_file))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -632,20 +640,22 @@ impl QcowFile {
|
|||||||
file: RawFile,
|
file: RawFile,
|
||||||
version: u32,
|
version: u32,
|
||||||
backing_file_name: &str,
|
backing_file_name: &str,
|
||||||
|
backing_file_max_nesting_depth: u32,
|
||||||
) -> Result<QcowFile> {
|
) -> Result<QcowFile> {
|
||||||
let direct_io = file.is_direct();
|
let direct_io = file.is_direct();
|
||||||
let backing_raw_file = OpenOptions::new()
|
let backing_raw_file = OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
.open(backing_file_name)
|
.open(backing_file_name)
|
||||||
.map_err(Error::BackingFileIo)?;
|
.map_err(Error::BackingFileIo)?;
|
||||||
let backing_file = crate::create_disk_file(backing_raw_file, direct_io)
|
let backing_file = Self::from_with_nesting_depth(
|
||||||
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
RawFile::new(backing_raw_file, direct_io),
|
||||||
let size = backing_file
|
backing_file_max_nesting_depth,
|
||||||
.size()
|
)
|
||||||
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||||
|
let size = backing_file.virtual_size();
|
||||||
let header = QcowHeader::create_for_size_and_path(version, size, Some(backing_file_name))?;
|
let header = QcowHeader::create_for_size_and_path(version, size, Some(backing_file_name))?;
|
||||||
let mut result = QcowFile::new_from_header(file, header)?;
|
let mut result = QcowFile::new_from_header(file, header)?;
|
||||||
result.backing_file = Some(backing_file);
|
result.backing_file = Some(Box::new(backing_file));
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,7 +683,7 @@ impl QcowFile {
|
|||||||
Ok(qcow)
|
Ok(qcow)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_backing_file(&mut self, backing: Option<Box<dyn BlockBackend>>) {
|
pub fn set_backing_file(&mut self, backing: Option<Box<Self>>) {
|
||||||
self.backing_file = backing;
|
self.backing_file = backing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1211,7 +1221,7 @@ impl QcowFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Allocate and initialize a new data cluster. Returns the offset of the
|
// Allocate and initialize a new data cluster. Returns the offset of the
|
||||||
// cluster in to the file on success.
|
// cluster into the file on success.
|
||||||
fn append_data_cluster(&mut self, initial_data: Option<Vec<u8>>) -> std::io::Result<u64> {
|
fn append_data_cluster(&mut self, initial_data: Option<Vec<u8>>) -> std::io::Result<u64> {
|
||||||
let new_addr: u64 = self.get_new_cluster(initial_data)?;
|
let new_addr: u64 = self.get_new_cluster(initial_data)?;
|
||||||
// The cluster refcount starts at one indicating it is used but doesn't need COW.
|
// The cluster refcount starts at one indicating it is used but doesn't need COW.
|
||||||
@@ -1629,7 +1639,7 @@ impl PunchHole for QcowFile {
|
|||||||
let mut remaining = length;
|
let mut remaining = length;
|
||||||
let mut offset = offset;
|
let mut offset = offset;
|
||||||
while remaining > 0 {
|
while remaining > 0 {
|
||||||
let chunk_length = min(remaining, std::usize::MAX as u64) as usize;
|
let chunk_length = min(remaining, usize::MAX as u64) as usize;
|
||||||
self.deallocate_bytes(offset, chunk_length)?;
|
self.deallocate_bytes(offset, chunk_length)?;
|
||||||
remaining -= chunk_length as u64;
|
remaining -= chunk_length as u64;
|
||||||
offset += chunk_length as u64;
|
offset += chunk_length as u64;
|
||||||
@@ -1789,11 +1799,17 @@ where
|
|||||||
/// Copy the contents of a disk image in `src_file` into `dst_file`.
|
/// Copy the contents of a disk image in `src_file` into `dst_file`.
|
||||||
/// The type of `src_file` is automatically detected, and the output file type is
|
/// The type of `src_file` is automatically detected, and the output file type is
|
||||||
/// determined by `dst_type`.
|
/// determined by `dst_type`.
|
||||||
pub fn convert(mut src_file: RawFile, dst_file: RawFile, dst_type: ImageType) -> Result<()> {
|
pub fn convert(
|
||||||
|
mut src_file: RawFile,
|
||||||
|
dst_file: RawFile,
|
||||||
|
dst_type: ImageType,
|
||||||
|
src_max_nesting_depth: u32,
|
||||||
|
) -> Result<()> {
|
||||||
let src_type = detect_image_type(&mut src_file)?;
|
let src_type = detect_image_type(&mut src_file)?;
|
||||||
match src_type {
|
match src_type {
|
||||||
ImageType::Qcow2 => {
|
ImageType::Qcow2 => {
|
||||||
let mut src_reader = QcowFile::from(src_file)?;
|
let mut src_reader =
|
||||||
|
QcowFile::from_with_nesting_depth(src_file, src_max_nesting_depth)?;
|
||||||
convert_reader(&mut src_reader, dst_file, dst_type)
|
convert_reader(&mut src_reader, dst_file, dst_type)
|
||||||
}
|
}
|
||||||
ImageType::Raw => {
|
ImageType::Raw => {
|
||||||
@@ -1822,7 +1838,9 @@ pub fn detect_image_type(file: &mut RawFile) -> Result<ImageType> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
use std::fs::File;
|
||||||
|
use std::path::Path;
|
||||||
|
use vmm_sys_util::tempdir::TempDir;
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
use vmm_sys_util::tempfile::TempFile;
|
||||||
use vmm_sys_util::write_zeroes::WriteZeroes;
|
use vmm_sys_util::write_zeroes::WriteZeroes;
|
||||||
|
|
||||||
@@ -2022,6 +2040,82 @@ mod tests {
|
|||||||
assert_eq!(read_header.backing_file_path, header.backing_file_path);
|
assert_eq!(read_header.backing_file_path, header.backing_file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_backing_file() {
|
||||||
|
// `backing_file` is `None`
|
||||||
|
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, None)
|
||||||
|
.expect("Failed to create header.");
|
||||||
|
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
||||||
|
header
|
||||||
|
.write_to(&mut disk_file)
|
||||||
|
.expect("Failed to write header to shm.");
|
||||||
|
disk_file.rewind().unwrap();
|
||||||
|
// The maximum nesting depth is 0, which means backing file is not allowed.
|
||||||
|
let res = QcowFile::from_with_nesting_depth(disk_file, 0);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn disable_backing_file() {
|
||||||
|
// `backing_file` is `Some`
|
||||||
|
let header =
|
||||||
|
QcowHeader::create_for_size_and_path(3, 0x10_0000, Some("/path/to/backing/file"))
|
||||||
|
.expect("Failed to create header.");
|
||||||
|
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
||||||
|
header
|
||||||
|
.write_to(&mut disk_file)
|
||||||
|
.expect("Failed to write header to shm.");
|
||||||
|
disk_file.rewind().unwrap();
|
||||||
|
// The maximum nesting depth is 0, which means backing file is not allowed.
|
||||||
|
let res = QcowFile::from_with_nesting_depth(disk_file, 0);
|
||||||
|
assert!(res.is_err());
|
||||||
|
assert!(matches!(res.unwrap_err(), Error::MaxNestingDepthExceeded));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a qcow2 file with itself as its backing file.
|
||||||
|
///
|
||||||
|
/// Without configuration `max_nesting_depth`, this will cause infinite recursion when loading
|
||||||
|
/// the file until stack overflow.
|
||||||
|
fn new_self_referential_qcow(path: &Path) -> Result<()> {
|
||||||
|
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, path.to_str())?;
|
||||||
|
let mut disk_file = RawFile::new(
|
||||||
|
File::create(path).expect("Failed to create image file."),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
header.write_to(&mut disk_file)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn max_nesting_backing() {
|
||||||
|
let test_dir = TempDir::new_with_prefix("/tmp/ch").unwrap();
|
||||||
|
let img_path = test_dir.as_path().join("test.img");
|
||||||
|
|
||||||
|
new_self_referential_qcow(img_path.as_path()).unwrap();
|
||||||
|
|
||||||
|
let err = QcowFile::from_with_nesting_depth(
|
||||||
|
RawFile::new(
|
||||||
|
File::open(img_path.as_path()).expect("Failed to open qcow image file"),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
MAX_NESTING_DEPTH,
|
||||||
|
)
|
||||||
|
.expect_err("Opening qcow file with itself as backing file should fail.");
|
||||||
|
|
||||||
|
// This type of error is complex. For comparing easily, we can check if it contains the
|
||||||
|
// type name after formatting.
|
||||||
|
assert!(format!("{err:?}").contains(&format!("{:?}", Error::MaxNestingDepthExceeded)));
|
||||||
|
// This should recursively call the function ten times before throwing an error, and the
|
||||||
|
// error `BackingFileOpen` should also be repeated ten times.
|
||||||
|
assert_eq!(
|
||||||
|
format!("{err:?}")
|
||||||
|
.matches("BackingFileOpen")
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.len() as u32,
|
||||||
|
MAX_NESTING_DEPTH,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_magic() {
|
fn invalid_magic() {
|
||||||
let invalid_header = vec![0x51u8, 0x46, 0x4a, 0xfb];
|
let invalid_header = vec![0x51u8, 0x46, 0x4a, 0xfb];
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use super::RawFile;
|
use super::RawFile;
|
||||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
use crate::BlockBackend;
|
use crate::BlockBackend;
|
||||||
use libc::c_void;
|
use libc::c_void;
|
||||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::fs::{File, Metadata};
|
use std::fs::{File, Metadata};
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|||||||
@@ -1,51 +1,40 @@
|
|||||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::fmt::{self, Display};
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use libc::EINVAL;
|
use libc::EINVAL;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::qcow::{
|
use crate::qcow::{
|
||||||
qcow_raw_file::QcowRawFile,
|
qcow_raw_file::QcowRawFile,
|
||||||
vec_cache::{CacheMap, Cacheable, VecCache},
|
vec_cache::{CacheMap, Cacheable, VecCache},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// `EvictingCache` - Error writing a refblock from the cache to disk.
|
/// `EvictingCache` - Error writing a refblock from the cache to disk.
|
||||||
|
#[error("Failed to write a refblock from the cache to disk: {0}")]
|
||||||
EvictingRefCounts(io::Error),
|
EvictingRefCounts(io::Error),
|
||||||
/// `InvalidIndex` - Address requested isn't within the range of the disk.
|
/// `InvalidIndex` - Address requested isn't within the range of the disk.
|
||||||
|
#[error("Address requested is not within the range of the disk")]
|
||||||
InvalidIndex,
|
InvalidIndex,
|
||||||
/// `NeedCluster` - Handle this error by reading the cluster and calling the function again.
|
/// `NeedCluster` - Handle this error by reading the cluster and calling the function again.
|
||||||
|
#[error("Cluster with addr={0} needs to be read")]
|
||||||
NeedCluster(u64),
|
NeedCluster(u64),
|
||||||
/// `NeedNewCluster` - Handle this error by allocating a cluster and calling the function again.
|
/// `NeedNewCluster` - Handle this error by allocating a cluster and calling the function again.
|
||||||
|
#[error("New cluster needs to be allocated for refcounts")]
|
||||||
NeedNewCluster,
|
NeedNewCluster,
|
||||||
/// `ReadingRefCounts` - Error reading the file in to the refcount cache.
|
/// `ReadingRefCounts` - Error reading the file into the refcount cache.
|
||||||
|
#[error("Failed to read the file into the refcount cache: {0}")]
|
||||||
ReadingRefCounts(io::Error),
|
ReadingRefCounts(io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
impl Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
use self::Error::*;
|
|
||||||
|
|
||||||
match self {
|
|
||||||
EvictingRefCounts(e) => {
|
|
||||||
write!(f, "failed to write a refblock from the cache to disk: {e}")
|
|
||||||
}
|
|
||||||
InvalidIndex => write!(f, "address requested is not within the range of the disk"),
|
|
||||||
NeedCluster(addr) => write!(f, "cluster with addr={addr} needs to be read"),
|
|
||||||
NeedNewCluster => write!(f, "new cluster needs to be allocated for refcounts"),
|
|
||||||
ReadingRefCounts(e) => {
|
|
||||||
write!(f, "failed to read the file into the refcount cache: {e}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the refcount entries for an open qcow file.
|
/// Represents the refcount entries for an open qcow file.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RefCount {
|
pub struct RefCount {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::collections::hash_map::IterMut;
|
use std::collections::hash_map::IterMut;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::{read_aligned_block_size, DiskTopology};
|
use crate::{read_aligned_block_size, DiskTopology};
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ extern crate log;
|
|||||||
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
|
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
|
||||||
use remain::sorted;
|
use remain::sorted;
|
||||||
use std::collections::btree_map::BTreeMap;
|
use std::collections::btree_map::BTreeMap;
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
@@ -91,7 +90,7 @@ pub type Result<T> = std::result::Result<T, VhdxHeaderError>;
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct FileTypeIdentifier {
|
pub struct FileTypeIdentifier {
|
||||||
pub signature: u64,
|
pub _signature: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileTypeIdentifier {
|
impl FileTypeIdentifier {
|
||||||
@@ -99,14 +98,14 @@ impl FileTypeIdentifier {
|
|||||||
pub fn new(f: &mut File) -> Result<FileTypeIdentifier> {
|
pub fn new(f: &mut File) -> Result<FileTypeIdentifier> {
|
||||||
f.seek(SeekFrom::Start(FILE_START))
|
f.seek(SeekFrom::Start(FILE_START))
|
||||||
.map_err(VhdxHeaderError::SeekFileTypeIdentifier)?;
|
.map_err(VhdxHeaderError::SeekFileTypeIdentifier)?;
|
||||||
let signature = f
|
let _signature = f
|
||||||
.read_u64::<LittleEndian>()
|
.read_u64::<LittleEndian>()
|
||||||
.map_err(VhdxHeaderError::ReadFileTypeIdentifier)?;
|
.map_err(VhdxHeaderError::ReadFileTypeIdentifier)?;
|
||||||
if signature != VHDX_SIGN {
|
if _signature != VHDX_SIGN {
|
||||||
return Err(VhdxHeaderError::InvalidVHDXSign);
|
return Err(VhdxHeaderError::InvalidVHDXSign);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(FileTypeIdentifier { signature })
|
Ok(FileTypeIdentifier { _signature })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +127,7 @@ pub struct Header {
|
|||||||
impl Header {
|
impl Header {
|
||||||
/// Reads the Header structure from a reference VHDx file
|
/// Reads the Header structure from a reference VHDx file
|
||||||
pub fn new(f: &mut File, start: u64) -> Result<Header> {
|
pub fn new(f: &mut File, start: u64) -> Result<Header> {
|
||||||
// Read the whole header in to a buffer. We will need it for
|
// Read the whole header into a buffer. We will need it for
|
||||||
// calculating checksum.
|
// calculating checksum.
|
||||||
let mut buffer = [0; HEADER_SIZE as usize];
|
let mut buffer = [0; HEADER_SIZE as usize];
|
||||||
f.seek(SeekFrom::Start(start))
|
f.seek(SeekFrom::Start(start))
|
||||||
@@ -471,7 +470,7 @@ impl VhdxHeader {
|
|||||||
pub fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> Result<u32> {
|
pub fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> Result<u32> {
|
||||||
// Read the checksum into a mutable slice
|
// Read the checksum into a mutable slice
|
||||||
let csum_buf = &mut buffer[csum_offset..csum_offset + 4];
|
let csum_buf = &mut buffer[csum_offset..csum_offset + 4];
|
||||||
// Convert the checksum chunk in to a u32 integer
|
// Convert the checksum chunk into a u32 integer
|
||||||
let orig_csum = LittleEndian::read_u32(csum_buf);
|
let orig_csum = LittleEndian::read_u32(csum_buf);
|
||||||
// Zero the checksum in the buffer
|
// Zero the checksum in the buffer
|
||||||
LittleEndian::write_u32(csum_buf, 0);
|
LittleEndian::write_u32(csum_buf, 0);
|
||||||
|
|||||||
@@ -1,27 +1,31 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "devices"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["The Chromium OS Authors"]
|
authors = ["The Chromium OS Authors"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
name = "devices"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.86"
|
||||||
arch = { path = "../arch" }
|
arch = { path = "../arch" }
|
||||||
bitflags = "2.4.1"
|
bitflags = "2.6.0"
|
||||||
byteorder = "1.4.3"
|
byteorder = "1.5.0"
|
||||||
event_monitor = { path = "../event_monitor" }
|
event_monitor = { path = "../event_monitor" }
|
||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
libc = "0.2.153"
|
libc = "0.2.155"
|
||||||
log = "0.4.20"
|
log = "0.4.22"
|
||||||
|
num_enum = "0.7.2"
|
||||||
pci = { path = "../pci" }
|
pci = { path = "../pci" }
|
||||||
thiserror = "1.0.52"
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
thiserror = "1.0.62"
|
||||||
tpm = { path = "../tpm" }
|
tpm = { path = "../tpm" }
|
||||||
versionize = "0.2.0"
|
|
||||||
versionize_derive = "0.1.6"
|
|
||||||
vm-allocator = { path = "../vm-allocator" }
|
vm-allocator = { path = "../vm-allocator" }
|
||||||
vm-device = { path = "../vm-device" }
|
vm-device = { path = "../vm-device" }
|
||||||
vm-memory = "0.14.0"
|
vm-memory = { version = "0.14.1", features = [
|
||||||
|
"backend-atomic",
|
||||||
|
"backend-bitmap",
|
||||||
|
"backend-mmap",
|
||||||
|
] }
|
||||||
vm-migration = { path = "../vm-migration" }
|
vm-migration = { path = "../vm-migration" }
|
||||||
vmm-sys-util = "0.12.1"
|
vmm-sys-util = "0.12.1"
|
||||||
|
|
||||||
@@ -30,3 +34,4 @@ arch = { path = "../arch" }
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
pvmemcontrol = []
|
||||||
|
|||||||
@@ -4,31 +4,42 @@
|
|||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::result;
|
use std::result;
|
||||||
|
use thiserror::Error;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Invalid trigger mode.
|
/// Invalid trigger mode.
|
||||||
|
#[error("Invalid trigger mode")]
|
||||||
InvalidTriggerMode,
|
InvalidTriggerMode,
|
||||||
/// Invalid delivery mode.
|
/// Invalid delivery mode.
|
||||||
|
#[error("Invalid delivery mode")]
|
||||||
InvalidDeliveryMode,
|
InvalidDeliveryMode,
|
||||||
/// Failed creating the interrupt source group.
|
/// Failed creating the interrupt source group.
|
||||||
|
#[error("Failed creating the interrupt source group: {0}")]
|
||||||
CreateInterruptSourceGroup(io::Error),
|
CreateInterruptSourceGroup(io::Error),
|
||||||
/// Failed triggering the interrupt.
|
/// Failed triggering the interrupt.
|
||||||
|
#[error("Failed triggering the interrupt: {0}")]
|
||||||
TriggerInterrupt(io::Error),
|
TriggerInterrupt(io::Error),
|
||||||
/// Failed masking the interrupt.
|
/// Failed masking the interrupt.
|
||||||
|
#[error("Failed masking the interrupt: {0}")]
|
||||||
MaskInterrupt(io::Error),
|
MaskInterrupt(io::Error),
|
||||||
/// Failed unmasking the interrupt.
|
/// Failed unmasking the interrupt.
|
||||||
|
#[error("Failed unmasking the interrupt: {0}")]
|
||||||
UnmaskInterrupt(io::Error),
|
UnmaskInterrupt(io::Error),
|
||||||
/// Failed updating the interrupt.
|
/// Failed updating the interrupt.
|
||||||
|
#[error("Failed updating the interrupt: {0}")]
|
||||||
UpdateInterrupt(io::Error),
|
UpdateInterrupt(io::Error),
|
||||||
/// Failed enabling the interrupt.
|
/// Failed enabling the interrupt.
|
||||||
|
#[error("Failed enabling the interrupt: {0}")]
|
||||||
EnableInterrupt(io::Error),
|
EnableInterrupt(io::Error),
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
/// Failed creating GIC device.
|
/// Failed creating GIC device.
|
||||||
|
#[error("Failed creating GIC device: {0}")]
|
||||||
CreateGic(hypervisor::HypervisorVmError),
|
CreateGic(hypervisor::HypervisorVmError),
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
/// Failed restoring GIC device.
|
/// Failed restoring GIC device.
|
||||||
|
#[error("Failed restoring GIC device: {0}")]
|
||||||
RestoreGic(hypervisor::arch::aarch64::gic::Error),
|
RestoreGic(hypervisor::arch::aarch64::gic::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,19 +11,16 @@
|
|||||||
|
|
||||||
use super::interrupt_controller::{Error, InterruptController};
|
use super::interrupt_controller::{Error, InterruptController};
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
|
||||||
use versionize_derive::Versionize;
|
|
||||||
use vm_device::interrupt::{
|
use vm_device::interrupt::{
|
||||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||||
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||||
};
|
};
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_memory::GuestAddress;
|
use vm_memory::GuestAddress;
|
||||||
use vm_migration::{
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
|
||||||
};
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
@@ -136,7 +133,7 @@ pub struct Ioapic {
|
|||||||
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Versionize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct IoapicState {
|
pub struct IoapicState {
|
||||||
id_reg: u32,
|
id_reg: u32,
|
||||||
reg_sel: u32,
|
reg_sel: u32,
|
||||||
@@ -144,7 +141,6 @@ pub struct IoapicState {
|
|||||||
used_entries: [bool; NUM_IOAPIC_PINS],
|
used_entries: [bool; NUM_IOAPIC_PINS],
|
||||||
apic_address: u64,
|
apic_address: u64,
|
||||||
}
|
}
|
||||||
impl VersionMapped for IoapicState {}
|
|
||||||
|
|
||||||
impl BusDevice for Ioapic {
|
impl BusDevice for Ioapic {
|
||||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||||
@@ -420,7 +416,7 @@ impl InterruptController for Ioapic {
|
|||||||
self.interrupt_source_group
|
self.interrupt_source_group
|
||||||
.trigger(irq as InterruptIndex)
|
.trigger(irq as InterruptIndex)
|
||||||
.map_err(Error::TriggerInterrupt)?;
|
.map_err(Error::TriggerInterrupt)?;
|
||||||
debug!("Interrupt successfully delivered");
|
debug!("Interrupt {irq} successfully delivered");
|
||||||
|
|
||||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
// If trigger mode is level sensitive, set the Remote IRR bit.
|
||||||
// It will be cleared when the EOI is received.
|
// It will be cleared when the EOI is received.
|
||||||
@@ -444,7 +440,7 @@ impl Snapshottable for Ioapic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_versioned_state(&self.state())
|
Snapshot::new_from_state(&self.state())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
|
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
|
|||||||
@@ -8,16 +8,14 @@
|
|||||||
//!
|
//!
|
||||||
|
|
||||||
use crate::{read_le_u32, write_le_u32};
|
use crate::{read_le_u32, write_le_u32};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::io;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::{fmt, io};
|
use thiserror::Error;
|
||||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
|
||||||
use versionize_derive::Versionize;
|
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_migration::{
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
|
||||||
};
|
|
||||||
|
|
||||||
const OFS_DATA: u64 = 0x400; // Data Register
|
const OFS_DATA: u64 = 0x400; // Data Register
|
||||||
const GPIODIR: u64 = 0x400; // Direction Register
|
const GPIODIR: u64 = 0x400; // Direction Register
|
||||||
@@ -40,29 +38,18 @@ const GPIO_ID_HIGH: u64 = 0x1000;
|
|||||||
|
|
||||||
const N_GPIOS: u32 = 8;
|
const N_GPIOS: u32 = 8;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
#[error("Bad Write Offset: {0}")]
|
||||||
BadWriteOffset(u64),
|
BadWriteOffset(u64),
|
||||||
|
#[error("GPIO interrupt disabled by guest driver.")]
|
||||||
GpioInterruptDisabled,
|
GpioInterruptDisabled,
|
||||||
|
#[error("Could not trigger GPIO interrupt: {0}.")]
|
||||||
GpioInterruptFailure(io::Error),
|
GpioInterruptFailure(io::Error),
|
||||||
|
#[error("Invalid GPIO Input key triggered: {0}.")]
|
||||||
GpioTriggerKeyFailure(u32),
|
GpioTriggerKeyFailure(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {offset}"),
|
|
||||||
Error::GpioInterruptDisabled => write!(f, "GPIO interrupt disabled by guest driver.",),
|
|
||||||
Error::GpioInterruptFailure(ref e) => {
|
|
||||||
write!(f, "Could not trigger GPIO interrupt: {e}.")
|
|
||||||
}
|
|
||||||
Error::GpioTriggerKeyFailure(key) => {
|
|
||||||
write!(f, "Invalid GPIO Input key triggered: {key}.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
/// A GPIO device following the PL061 specification.
|
/// A GPIO device following the PL061 specification.
|
||||||
@@ -89,7 +76,7 @@ pub struct Gpio {
|
|||||||
interrupt: Arc<dyn InterruptSourceGroup>,
|
interrupt: Arc<dyn InterruptSourceGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Versionize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GpioState {
|
pub struct GpioState {
|
||||||
data: u32,
|
data: u32,
|
||||||
old_in_data: u32,
|
old_in_data: u32,
|
||||||
@@ -102,8 +89,6 @@ pub struct GpioState {
|
|||||||
afsel: u32,
|
afsel: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VersionMapped for GpioState {}
|
|
||||||
|
|
||||||
impl Gpio {
|
impl Gpio {
|
||||||
/// Constructs an PL061 GPIO device.
|
/// Constructs an PL061 GPIO device.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -328,7 +313,7 @@ impl Snapshottable for Gpio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_versioned_state(&self.state())
|
Snapshot::new_from_state(&self.state())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,8 +324,6 @@ impl Migratable for Gpio {}
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{read_le_u32, write_le_u32};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
//! a real-time clock input.
|
//! a real-time clock input.
|
||||||
//!
|
//!
|
||||||
use crate::{read_le_u32, write_le_u32};
|
use crate::{read_le_u32, write_le_u32};
|
||||||
use std::fmt;
|
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{io, result};
|
use std::{io, result};
|
||||||
|
use thiserror::Error;
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
|
||||||
@@ -39,21 +39,14 @@ const AMBA_ID_HIGH: u64 = 0x1000;
|
|||||||
/// Constant to convert seconds to nanoseconds.
|
/// Constant to convert seconds to nanoseconds.
|
||||||
pub const NANOS_PER_SECOND: u64 = 1_000_000_000;
|
pub const NANOS_PER_SECOND: u64 = 1_000_000_000;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
#[error("Bad Write Offset: {0}")]
|
||||||
BadWriteOffset(u64),
|
BadWriteOffset(u64),
|
||||||
|
#[error("Failed to trigger interrupt: {0}")]
|
||||||
InterruptFailure(io::Error),
|
InterruptFailure(io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {offset}"),
|
|
||||||
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
/// Wrapper over `libc::clockid_t` to specify Linux Kernel clock source.
|
/// Wrapper over `libc::clockid_t` to specify Linux Kernel clock source.
|
||||||
@@ -79,98 +72,6 @@ impl From<ClockType> for libc::clockid_t {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Structure representing the date in local time with nanosecond precision.
|
|
||||||
pub struct LocalTime {
|
|
||||||
/// Seconds in current minute.
|
|
||||||
sec: i32,
|
|
||||||
/// Minutes in current hour.
|
|
||||||
min: i32,
|
|
||||||
/// Hours in current day, 24H format.
|
|
||||||
hour: i32,
|
|
||||||
/// Days in current month.
|
|
||||||
mday: i32,
|
|
||||||
/// Months in current year.
|
|
||||||
mon: i32,
|
|
||||||
/// Years passed since 1900 BC.
|
|
||||||
year: i32,
|
|
||||||
/// Nanoseconds in current second.
|
|
||||||
nsec: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LocalTime {
|
|
||||||
/// Returns the [LocalTime](struct.LocalTime.html) structure for the calling moment.
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn now() -> LocalTime {
|
|
||||||
let mut timespec = libc::timespec {
|
|
||||||
tv_sec: 0,
|
|
||||||
tv_nsec: 0,
|
|
||||||
};
|
|
||||||
let mut tm: libc::tm = libc::tm {
|
|
||||||
tm_sec: 0,
|
|
||||||
tm_min: 0,
|
|
||||||
tm_hour: 0,
|
|
||||||
tm_mday: 0,
|
|
||||||
tm_mon: 0,
|
|
||||||
tm_year: 0,
|
|
||||||
tm_wday: 0,
|
|
||||||
tm_yday: 0,
|
|
||||||
tm_isdst: 0,
|
|
||||||
tm_gmtoff: 0,
|
|
||||||
tm_zone: std::ptr::null(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// SAFETY: the parameters are valid.
|
|
||||||
unsafe {
|
|
||||||
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
|
|
||||||
libc::localtime_r(×pec.tv_sec, &mut tm);
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalTime {
|
|
||||||
sec: tm.tm_sec,
|
|
||||||
min: tm.tm_min,
|
|
||||||
hour: tm.tm_hour,
|
|
||||||
mday: tm.tm_mday,
|
|
||||||
mon: tm.tm_mon,
|
|
||||||
year: tm.tm_year,
|
|
||||||
nsec: timespec.tv_nsec,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for LocalTime {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
|
|
||||||
self.year + 1900,
|
|
||||||
self.mon + 1,
|
|
||||||
self.mday,
|
|
||||||
self.hour,
|
|
||||||
self.min,
|
|
||||||
self.sec,
|
|
||||||
self.nsec
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Holds a micro-second resolution timestamp with both the real time and cpu time.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct TimestampUs {
|
|
||||||
/// Real time in microseconds.
|
|
||||||
pub time_us: u64,
|
|
||||||
/// Cpu time in microseconds.
|
|
||||||
pub cputime_us: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for TimestampUs {
|
|
||||||
fn default() -> TimestampUs {
|
|
||||||
TimestampUs {
|
|
||||||
time_us: get_time(ClockType::Monotonic) / 1000,
|
|
||||||
cputime_us: get_time(ClockType::ProcessCpu) / 1000,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a timestamp in nanoseconds based on the provided clock type.
|
/// Returns a timestamp in nanoseconds based on the provided clock type.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -329,15 +230,78 @@ impl BusDevice for Rtc {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u32, read_le_u64, write_be_u16,
|
read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u64, write_be_u16,
|
||||||
write_be_u32, write_le_i32, write_le_u16, write_le_u32, write_le_u64,
|
write_be_u32, write_le_i32, write_le_u16, write_le_u64,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
|
||||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
|
const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
|
||||||
|
|
||||||
|
struct LocalTime {
|
||||||
|
sec: i32,
|
||||||
|
min: i32,
|
||||||
|
hour: i32,
|
||||||
|
mday: i32,
|
||||||
|
mon: i32,
|
||||||
|
year: i32,
|
||||||
|
nsec: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalTime {
|
||||||
|
fn now() -> LocalTime {
|
||||||
|
let mut timespec = libc::timespec {
|
||||||
|
tv_sec: 0,
|
||||||
|
tv_nsec: 0,
|
||||||
|
};
|
||||||
|
let mut tm: libc::tm = libc::tm {
|
||||||
|
tm_sec: 0,
|
||||||
|
tm_min: 0,
|
||||||
|
tm_hour: 0,
|
||||||
|
tm_mday: 0,
|
||||||
|
tm_mon: 0,
|
||||||
|
tm_year: 0,
|
||||||
|
tm_wday: 0,
|
||||||
|
tm_yday: 0,
|
||||||
|
tm_isdst: 0,
|
||||||
|
tm_gmtoff: 0,
|
||||||
|
tm_zone: std::ptr::null(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY: the parameters are valid.
|
||||||
|
unsafe {
|
||||||
|
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
|
||||||
|
libc::localtime_r(×pec.tv_sec, &mut tm);
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalTime {
|
||||||
|
sec: tm.tm_sec,
|
||||||
|
min: tm.tm_min,
|
||||||
|
hour: tm.tm_hour,
|
||||||
|
mday: tm.tm_mday,
|
||||||
|
mon: tm.tm_mon,
|
||||||
|
year: tm.tm_year,
|
||||||
|
nsec: timespec.tv_nsec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for LocalTime {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
|
||||||
|
self.year + 1900,
|
||||||
|
self.mon + 1,
|
||||||
|
self.mday,
|
||||||
|
self.hour,
|
||||||
|
self.min,
|
||||||
|
self.sec,
|
||||||
|
self.nsec
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_time() {
|
fn test_get_time() {
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
|
|||||||
@@ -5,16 +5,13 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::{io, result};
|
use std::{io, result};
|
||||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
|
||||||
use versionize_derive::Versionize;
|
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_migration::{
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
|
||||||
};
|
|
||||||
use vmm_sys_util::errno::Result;
|
use vmm_sys_util::errno::Result;
|
||||||
|
|
||||||
const LOOP_SIZE: usize = 0x40;
|
const LOOP_SIZE: usize = 0x40;
|
||||||
@@ -74,7 +71,7 @@ pub struct Serial {
|
|||||||
out: Option<Box<dyn io::Write + Send>>,
|
out: Option<Box<dyn io::Write + Send>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Versionize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct SerialState {
|
pub struct SerialState {
|
||||||
interrupt_enable: u8,
|
interrupt_enable: u8,
|
||||||
interrupt_identification: u8,
|
interrupt_identification: u8,
|
||||||
@@ -86,7 +83,6 @@ pub struct SerialState {
|
|||||||
baud_divisor: u16,
|
baud_divisor: u16,
|
||||||
in_buffer: Vec<u8>,
|
in_buffer: Vec<u8>,
|
||||||
}
|
}
|
||||||
impl VersionMapped for SerialState {}
|
|
||||||
|
|
||||||
impl Serial {
|
impl Serial {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -334,7 +330,7 @@ impl Snapshottable for Serial {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_versioned_state(&self.state())
|
Snapshot::new_from_state(&self.state())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,8 +341,7 @@ impl Migratable for Serial {}
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::io;
|
use std::sync::Mutex;
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
@@ -416,14 +411,11 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
serial.write(0, DATA as u64, &[b'x', b'y']);
|
serial.write(0, DATA as u64, b"xy");
|
||||||
serial.write(0, DATA as u64, &[b'a']);
|
serial.write(0, DATA as u64, b"a");
|
||||||
serial.write(0, DATA as u64, &[b'b']);
|
serial.write(0, DATA as u64, b"b");
|
||||||
serial.write(0, DATA as u64, &[b'c']);
|
serial.write(0, DATA as u64, b"c");
|
||||||
assert_eq!(
|
assert_eq!(serial_out.buf.lock().unwrap().as_slice(), b"abc");
|
||||||
serial_out.buf.lock().unwrap().as_slice(),
|
|
||||||
&[b'a', b'b', b'c']
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -441,7 +433,7 @@ mod tests {
|
|||||||
// counter doesn't change (for 0 it blocks)
|
// counter doesn't change (for 0 it blocks)
|
||||||
assert!(intr_evt.write(1).is_ok());
|
assert!(intr_evt.write(1).is_ok());
|
||||||
serial.write(0, IER as u64, &[IER_RECV_BIT]);
|
serial.write(0, IER as u64, &[IER_RECV_BIT]);
|
||||||
serial.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
|
serial.queue_input_bytes(b"abc").unwrap();
|
||||||
|
|
||||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||||
|
|
||||||
@@ -478,7 +470,7 @@ mod tests {
|
|||||||
// counter doesn't change (for 0 it blocks)
|
// counter doesn't change (for 0 it blocks)
|
||||||
assert!(intr_evt.write(1).is_ok());
|
assert!(intr_evt.write(1).is_ok());
|
||||||
serial.write(0, IER as u64, &[IER_THR_BIT]);
|
serial.write(0, IER as u64, &[IER_THR_BIT]);
|
||||||
serial.write(0, DATA as u64, &[b'a']);
|
serial.write(0, DATA as u64, b"a");
|
||||||
|
|
||||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||||
let mut data = [0u8];
|
let mut data = [0u8];
|
||||||
@@ -520,9 +512,9 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT]);
|
serial.write(0, MCR as u64, &[MCR_LOOP_BIT]);
|
||||||
serial.write(0, DATA as u64, &[b'a']);
|
serial.write(0, DATA as u64, b"a");
|
||||||
serial.write(0, DATA as u64, &[b'b']);
|
serial.write(0, DATA as u64, b"b");
|
||||||
serial.write(0, DATA as u64, &[b'c']);
|
serial.write(0, DATA as u64, b"c");
|
||||||
|
|
||||||
let mut data = [0u8];
|
let mut data = [0u8];
|
||||||
serial.read(0, MSR as u64, &mut data[..]);
|
serial.read(0, MSR as u64, &mut data[..]);
|
||||||
|
|||||||
@@ -7,18 +7,15 @@
|
|||||||
//!
|
//!
|
||||||
|
|
||||||
use crate::{read_le_u32, write_le_u32};
|
use crate::{read_le_u32, write_le_u32};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fmt;
|
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{io, result};
|
use std::{io, result};
|
||||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
use thiserror::Error;
|
||||||
use versionize_derive::Versionize;
|
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_migration::{
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Registers */
|
/* Registers */
|
||||||
const UARTDR: u64 = 0;
|
const UARTDR: u64 = 0;
|
||||||
@@ -48,27 +45,20 @@ const PL011_ID: [u8; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
|||||||
const AMBA_ID_LOW: u64 = 0x3f8;
|
const AMBA_ID_LOW: u64 = 0x3f8;
|
||||||
const AMBA_ID_HIGH: u64 = 0x401;
|
const AMBA_ID_HIGH: u64 = 0x401;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
#[error("pl011_write: Bad Write Offset: {0}")]
|
||||||
BadWriteOffset(u64),
|
BadWriteOffset(u64),
|
||||||
|
#[error("pl011: DMA not implemented.")]
|
||||||
DmaNotImplemented,
|
DmaNotImplemented,
|
||||||
|
#[error("Failed to trigger interrupt: {0}")]
|
||||||
InterruptFailure(io::Error),
|
InterruptFailure(io::Error),
|
||||||
|
#[error("Failed to write: {0}")]
|
||||||
WriteAllFailure(io::Error),
|
WriteAllFailure(io::Error),
|
||||||
|
#[error("Failed to flush: {0}")]
|
||||||
FlushFailure(io::Error),
|
FlushFailure(io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Error::BadWriteOffset(offset) => write!(f, "pl011_write: Bad Write Offset: {offset}"),
|
|
||||||
Error::DmaNotImplemented => write!(f, "pl011: DMA not implemented."),
|
|
||||||
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {e}"),
|
|
||||||
Error::WriteAllFailure(e) => write!(f, "Failed to write: {e}"),
|
|
||||||
Error::FlushFailure(e) => write!(f, "Failed to flush: {e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
/// A PL011 device following the PL011 specification.
|
/// A PL011 device following the PL011 specification.
|
||||||
@@ -94,7 +84,7 @@ pub struct Pl011 {
|
|||||||
timestamp: std::time::Instant,
|
timestamp: std::time::Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Versionize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Pl011State {
|
pub struct Pl011State {
|
||||||
flags: u32,
|
flags: u32,
|
||||||
lcr: u32,
|
lcr: u32,
|
||||||
@@ -113,8 +103,6 @@ pub struct Pl011State {
|
|||||||
read_trigger: u32,
|
read_trigger: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VersionMapped for Pl011State {}
|
|
||||||
|
|
||||||
impl Pl011 {
|
impl Pl011 {
|
||||||
/// Constructs an AMBA PL011 UART device.
|
/// Constructs an AMBA PL011 UART device.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -454,7 +442,7 @@ impl Snapshottable for Pl011 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_versioned_state(&self.state())
|
Snapshot::new_from_state(&self.state())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,8 +453,7 @@ impl Migratable for Pl011 {}
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::io;
|
use std::sync::Mutex;
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
@@ -537,14 +524,11 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
pl011.write(0, UARTDR, &[b'x', b'y']);
|
pl011.write(0, UARTDR, b"xy");
|
||||||
pl011.write(0, UARTDR, &[b'a']);
|
pl011.write(0, UARTDR, b"a");
|
||||||
pl011.write(0, UARTDR, &[b'b']);
|
pl011.write(0, UARTDR, b"b");
|
||||||
pl011.write(0, UARTDR, &[b'c']);
|
pl011.write(0, UARTDR, b"c");
|
||||||
assert_eq!(
|
assert_eq!(pl011_out.buf.lock().unwrap().as_slice(), b"xabc");
|
||||||
pl011_out.buf.lock().unwrap().as_slice(),
|
|
||||||
&[b'x', b'a', b'b', b'c']
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -562,7 +546,7 @@ mod tests {
|
|||||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||||
// counter doesn't change (for 0 it blocks)
|
// counter doesn't change (for 0 it blocks)
|
||||||
assert!(intr_evt.write(1).is_ok());
|
assert!(intr_evt.write(1).is_ok());
|
||||||
pl011.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
|
pl011.queue_input_bytes(b"abc").unwrap();
|
||||||
|
|
||||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ pub mod interrupt_controller;
|
|||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub mod ioapic;
|
pub mod ioapic;
|
||||||
pub mod legacy;
|
pub mod legacy;
|
||||||
|
#[cfg(feature = "pvmemcontrol")]
|
||||||
|
pub mod pvmemcontrol;
|
||||||
pub mod pvpanic;
|
pub mod pvpanic;
|
||||||
pub mod tpm;
|
pub mod tpm;
|
||||||
|
|
||||||
|
|||||||
819
devices/src/pvmemcontrol.rs
Normal file
819
devices/src/pvmemcontrol.rs
Normal file
@@ -0,0 +1,819 @@
|
|||||||
|
// Copyright © 2024 Google LLC
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use num_enum::TryFromPrimitive;
|
||||||
|
use pci::{
|
||||||
|
BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType,
|
||||||
|
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
ffi::CString,
|
||||||
|
io, result,
|
||||||
|
sync::{Arc, Barrier, Mutex, RwLock},
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
use vm_allocator::{page_size::get_page_size, AddressAllocator, SystemAllocator};
|
||||||
|
use vm_device::{BusDeviceSync, Resource};
|
||||||
|
use vm_memory::{
|
||||||
|
bitmap::AtomicBitmap, Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory,
|
||||||
|
GuestMemoryAtomic, GuestMemoryError, GuestMemoryMmap, Le32, Le64,
|
||||||
|
};
|
||||||
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
|
|
||||||
|
const PVMEMCONTROL_VENDOR_ID: u16 = 0x1ae0;
|
||||||
|
const PVMEMCONTROL_DEVICE_ID: u16 = 0x0087;
|
||||||
|
|
||||||
|
const PVMEMCONTROL_SUBSYSTEM_VENDOR_ID: u16 = 0x1ae0;
|
||||||
|
const PVMEMCONTROL_SUBSYSTEM_ID: u16 = 0x011F;
|
||||||
|
|
||||||
|
const MAJOR_VERSION: u64 = 1;
|
||||||
|
const MINOR_VERSION: u64 = 0;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
// device errors
|
||||||
|
#[error("Guest gave us bad memory addresses: {0}")]
|
||||||
|
GuestMemory(#[source] GuestMemoryError),
|
||||||
|
#[error("Guest sent us invalid request")]
|
||||||
|
InvalidRequest,
|
||||||
|
|
||||||
|
#[error("Guest sent us invalid command: {0}")]
|
||||||
|
InvalidCommand(u32),
|
||||||
|
#[error("Guest sent us invalid connection: {0}")]
|
||||||
|
InvalidConnection(u32),
|
||||||
|
|
||||||
|
// pvmemcontrol errors
|
||||||
|
#[error("Request contains invalid arguments: {0}")]
|
||||||
|
InvalidArgument(u64),
|
||||||
|
#[error("Unknown function code: {0}")]
|
||||||
|
UnknownFunctionCode(u64),
|
||||||
|
#[error("Libc call fail: {0}")]
|
||||||
|
LibcFail(#[source] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
enum PvmemcontrolSubclass {
|
||||||
|
Other = 0x80,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PciSubclass for PvmemcontrolSubclass {
|
||||||
|
fn get_register_value(&self) -> u8 {
|
||||||
|
*self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// commands have 0 as the most significant byte
|
||||||
|
#[repr(u32)]
|
||||||
|
#[derive(PartialEq, Eq, Copy, Clone, TryFromPrimitive)]
|
||||||
|
enum PvmemcontrolTransportCommand {
|
||||||
|
Reset = 0x060f_e6d2,
|
||||||
|
Register = 0x0e35_9539,
|
||||||
|
Ready = 0x0ca8_d227,
|
||||||
|
Disconnect = 0x030f_5da0,
|
||||||
|
Ack = 0x03cf_5196,
|
||||||
|
Error = 0x01fb_a249,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
struct PvmemcontrolTransportRegister {
|
||||||
|
buf_phys_addr: Le64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
struct PvmemcontrolTransportRegisterResponse {
|
||||||
|
command: Le32,
|
||||||
|
_padding: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
union PvmemcontrolTransportUnion {
|
||||||
|
register: PvmemcontrolTransportRegister,
|
||||||
|
register_response: PvmemcontrolTransportRegisterResponse,
|
||||||
|
unit: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
struct PvmemcontrolTransport {
|
||||||
|
payload: PvmemcontrolTransportUnion,
|
||||||
|
command: PvmemcontrolTransportCommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
const PVMEMCONTROL_DEVICE_MMIO_SIZE: u64 = std::mem::size_of::<PvmemcontrolTransport>() as u64;
|
||||||
|
const PVMEMCONTROL_DEVICE_MMIO_ALIGN: u64 = std::mem::align_of::<PvmemcontrolTransport>() as u64;
|
||||||
|
|
||||||
|
impl PvmemcontrolTransport {
|
||||||
|
fn ack() -> Self {
|
||||||
|
PvmemcontrolTransport {
|
||||||
|
payload: PvmemcontrolTransportUnion { unit: () },
|
||||||
|
command: PvmemcontrolTransportCommand::Ack,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error() -> Self {
|
||||||
|
PvmemcontrolTransport {
|
||||||
|
payload: PvmemcontrolTransportUnion { unit: () },
|
||||||
|
command: PvmemcontrolTransportCommand::Error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_response(command: u32) -> Self {
|
||||||
|
PvmemcontrolTransport {
|
||||||
|
payload: PvmemcontrolTransportUnion {
|
||||||
|
register_response: PvmemcontrolTransportRegisterResponse {
|
||||||
|
command: command.into(),
|
||||||
|
_padding: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
command: PvmemcontrolTransportCommand::Ack,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn as_register(self) -> PvmemcontrolTransportRegister {
|
||||||
|
self.payload.register
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: Contains no references and does not have compiler-inserted padding
|
||||||
|
unsafe impl ByteValued for PvmemcontrolTransportUnion {}
|
||||||
|
// SAFETY: Contains no references and does not have compiler-inserted padding
|
||||||
|
unsafe impl ByteValued for PvmemcontrolTransport {}
|
||||||
|
|
||||||
|
#[repr(u64)]
|
||||||
|
#[derive(Copy, Clone, TryFromPrimitive, Debug)]
|
||||||
|
enum FunctionCode {
|
||||||
|
Info = 0,
|
||||||
|
Dontneed = 1,
|
||||||
|
Remove = 2,
|
||||||
|
Free = 3,
|
||||||
|
Pageout = 4,
|
||||||
|
Dontdump = 5,
|
||||||
|
SetVMAAnonName = 6,
|
||||||
|
Mlock = 7,
|
||||||
|
Munlock = 8,
|
||||||
|
MprotectNone = 9,
|
||||||
|
MprotectR = 10,
|
||||||
|
MprotectW = 11,
|
||||||
|
MprotectRW = 12,
|
||||||
|
Mergeable = 13,
|
||||||
|
Unmergeable = 14,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
struct PvmemcontrolReq {
|
||||||
|
func_code: Le64,
|
||||||
|
addr: Le64,
|
||||||
|
length: Le64,
|
||||||
|
arg: Le64,
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: it only has data and has no implicit padding.
|
||||||
|
unsafe impl ByteValued for PvmemcontrolReq {}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Default)]
|
||||||
|
struct PvmemcontrolResp {
|
||||||
|
ret_errno: Le32,
|
||||||
|
ret_code: Le32,
|
||||||
|
ret_value: Le64,
|
||||||
|
arg0: Le64,
|
||||||
|
arg1: Le64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for PvmemcontrolResp {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let PvmemcontrolResp {
|
||||||
|
ret_errno,
|
||||||
|
ret_code,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"PvmemcontrolResp {{ ret_errno: {}, ret_code: {}, .. }}",
|
||||||
|
ret_errno.to_native(),
|
||||||
|
ret_code.to_native()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: it only has data and has no implicit padding.
|
||||||
|
unsafe impl ByteValued for PvmemcontrolResp {}
|
||||||
|
|
||||||
|
/// The guest connections start at 0x8000_0000, which has a leading 1 in
|
||||||
|
/// the most significant byte, this ensures it does not conflict with
|
||||||
|
/// any of the transport commands
|
||||||
|
#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub struct GuestConnection {
|
||||||
|
command: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GuestConnection {
|
||||||
|
fn default() -> Self {
|
||||||
|
GuestConnection::new(0x8000_0000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GuestConnection {
|
||||||
|
fn new(command: u32) -> Self {
|
||||||
|
Self { command }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&self) -> Self {
|
||||||
|
let GuestConnection { command } = *self;
|
||||||
|
|
||||||
|
if command == u32::MAX {
|
||||||
|
GuestConnection::default()
|
||||||
|
} else {
|
||||||
|
GuestConnection::new(command + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for GuestConnection {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||||
|
if (value & 0x8000_0000) != 0 {
|
||||||
|
Ok(GuestConnection::new(value))
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidConnection(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PercpuInitState {
|
||||||
|
port_buf_map: HashMap<GuestConnection, GuestAddress>,
|
||||||
|
next_conn: GuestConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PercpuInitState {
|
||||||
|
fn new() -> Self {
|
||||||
|
PercpuInitState {
|
||||||
|
port_buf_map: HashMap::new(),
|
||||||
|
next_conn: GuestConnection::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PvmemcontrolState {
|
||||||
|
PercpuInit(PercpuInitState),
|
||||||
|
Ready(HashMap<GuestConnection, GuestAddress>),
|
||||||
|
Broken,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PvmemcontrolDevice {
|
||||||
|
transport: PvmemcontrolTransport,
|
||||||
|
state: PvmemcontrolState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PvmemcontrolDevice {
|
||||||
|
fn new(transport: PvmemcontrolTransport, state: PvmemcontrolState) -> Self {
|
||||||
|
PvmemcontrolDevice { transport, state }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PvmemcontrolDevice {
|
||||||
|
fn register_percpu_buf(
|
||||||
|
guest_memory: &GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||||
|
mut state: PercpuInitState,
|
||||||
|
PvmemcontrolTransportRegister { buf_phys_addr }: PvmemcontrolTransportRegister,
|
||||||
|
) -> Self {
|
||||||
|
// access to this address is checked
|
||||||
|
let buf_phys_addr = GuestAddress(buf_phys_addr.into());
|
||||||
|
if !guest_memory.memory().check_range(
|
||||||
|
buf_phys_addr,
|
||||||
|
std::mem::size_of::<PvmemcontrolResp>().max(std::mem::size_of::<PvmemcontrolReq>()),
|
||||||
|
) {
|
||||||
|
warn!("guest sent invalid phys addr {:#x}", buf_phys_addr.0);
|
||||||
|
return PvmemcontrolDevice::new(
|
||||||
|
PvmemcontrolTransport::error(),
|
||||||
|
PvmemcontrolState::Broken,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let conn = {
|
||||||
|
// find an available port+byte combination, and fail if full
|
||||||
|
let mut next_conn = state.next_conn;
|
||||||
|
while state.port_buf_map.contains_key(&next_conn) {
|
||||||
|
next_conn = next_conn.next();
|
||||||
|
if next_conn == state.next_conn {
|
||||||
|
warn!("connections exhausted");
|
||||||
|
return PvmemcontrolDevice::new(
|
||||||
|
PvmemcontrolTransport::error(),
|
||||||
|
PvmemcontrolState::Broken,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next_conn
|
||||||
|
};
|
||||||
|
state.next_conn = conn.next();
|
||||||
|
state.port_buf_map.insert(conn, buf_phys_addr);
|
||||||
|
|
||||||
|
// inform guest of the connection
|
||||||
|
let response = PvmemcontrolTransport::register_response(conn.command);
|
||||||
|
|
||||||
|
PvmemcontrolDevice::new(response, PvmemcontrolState::PercpuInit(state))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset() -> Self {
|
||||||
|
PvmemcontrolDevice::new(
|
||||||
|
PvmemcontrolTransport::ack(),
|
||||||
|
PvmemcontrolState::PercpuInit(PercpuInitState::new()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error() -> Self {
|
||||||
|
PvmemcontrolDevice::new(PvmemcontrolTransport::error(), PvmemcontrolState::Broken)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ready(PercpuInitState { port_buf_map, .. }: PercpuInitState) -> Self {
|
||||||
|
PvmemcontrolDevice::new(
|
||||||
|
PvmemcontrolTransport::ack(),
|
||||||
|
PvmemcontrolState::Ready(port_buf_map),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_command(
|
||||||
|
&mut self,
|
||||||
|
guest_memory: &GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||||
|
command: PvmemcontrolTransportCommand,
|
||||||
|
) {
|
||||||
|
let state = std::mem::replace(&mut self.state, PvmemcontrolState::Broken);
|
||||||
|
|
||||||
|
*self = match command {
|
||||||
|
PvmemcontrolTransportCommand::Reset => Self::reset(),
|
||||||
|
PvmemcontrolTransportCommand::Register => {
|
||||||
|
if let PvmemcontrolState::PercpuInit(state) = state {
|
||||||
|
// SAFETY: By device protocol. If driver is wrong the device
|
||||||
|
// can enter a Broken state, but the behavior is still sound.
|
||||||
|
Self::register_percpu_buf(guest_memory, state, unsafe {
|
||||||
|
self.transport.as_register()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
debug!("received register without reset");
|
||||||
|
Self::error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PvmemcontrolTransportCommand::Ready => {
|
||||||
|
if let PvmemcontrolState::PercpuInit(state) = state {
|
||||||
|
Self::ready(state)
|
||||||
|
} else {
|
||||||
|
debug!("received ready without reset");
|
||||||
|
Self::error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PvmemcontrolTransportCommand::Disconnect => Self::error(),
|
||||||
|
PvmemcontrolTransportCommand::Ack => {
|
||||||
|
debug!("received ack as command");
|
||||||
|
Self::error()
|
||||||
|
}
|
||||||
|
PvmemcontrolTransportCommand::Error => {
|
||||||
|
debug!("received error as command");
|
||||||
|
Self::error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// read from the transport
|
||||||
|
fn read_transport(&self, offset: u64, data: &mut [u8]) {
|
||||||
|
self.transport
|
||||||
|
.as_slice()
|
||||||
|
.iter()
|
||||||
|
.skip(offset as usize)
|
||||||
|
.zip(data.iter_mut())
|
||||||
|
.for_each(|(src, dest)| *dest = *src)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// can only write to transport payload
|
||||||
|
/// command is a special register that needs separate dispatching
|
||||||
|
fn write_transport(&mut self, offset: u64, data: &[u8]) {
|
||||||
|
self.transport
|
||||||
|
.payload
|
||||||
|
.as_mut_slice()
|
||||||
|
.iter_mut()
|
||||||
|
.skip(offset as usize)
|
||||||
|
.zip(data.iter())
|
||||||
|
.for_each(|(dest, src)| *dest = *src)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_connection(&self, conn: GuestConnection) -> Option<GuestAddress> {
|
||||||
|
match &self.state {
|
||||||
|
PvmemcontrolState::Ready(map) => map.get(&conn).copied(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PvmemcontrolBusDevice {
|
||||||
|
mem: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||||
|
dev: RwLock<PvmemcontrolDevice>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PvmemcontrolPciDevice {
|
||||||
|
id: String,
|
||||||
|
configuration: PciConfiguration,
|
||||||
|
bar_regions: Vec<PciBarConfiguration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PvmemcontrolBusDevice {
|
||||||
|
/// f is called with the host address of `range_base` and only when
|
||||||
|
/// [`range_base`, `range_base` + `range_len`) is present in the guest
|
||||||
|
fn operate_on_memory_range<F>(&self, addr: u64, length: u64, f: F) -> result::Result<(), Error>
|
||||||
|
where
|
||||||
|
F: FnOnce(*mut libc::c_void, libc::size_t) -> libc::c_int,
|
||||||
|
{
|
||||||
|
let memory = self.mem.memory();
|
||||||
|
let range_base = GuestAddress(addr);
|
||||||
|
let range_len = usize::try_from(length).map_err(|_| Error::InvalidRequest)?;
|
||||||
|
|
||||||
|
// assume guest memory is not interleaved with vmm memory on the host.
|
||||||
|
if !memory.check_range(range_base, range_len) {
|
||||||
|
return Err(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(
|
||||||
|
range_base,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let hva = memory
|
||||||
|
.get_host_address(range_base)
|
||||||
|
.map_err(Error::GuestMemory)?;
|
||||||
|
let res = f(hva as *mut libc::c_void, range_len as libc::size_t);
|
||||||
|
if res != 0 {
|
||||||
|
return Err(Error::LibcFail(io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn madvise(&self, addr: u64, length: u64, advice: libc::c_int) -> result::Result<(), Error> {
|
||||||
|
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||||
|
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||||
|
libc::madvise(base, len, advice)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mlock(&self, addr: u64, length: u64, on_default: bool) -> result::Result<(), Error> {
|
||||||
|
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||||
|
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||||
|
libc::mlock2(base, len, if on_default { libc::MLOCK_ONFAULT } else { 0 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn munlock(&self, addr: u64, length: u64) -> result::Result<(), Error> {
|
||||||
|
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||||
|
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||||
|
libc::munlock(base, len)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mprotect(
|
||||||
|
&self,
|
||||||
|
addr: u64,
|
||||||
|
length: u64,
|
||||||
|
protection: libc::c_int,
|
||||||
|
) -> result::Result<(), Error> {
|
||||||
|
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||||
|
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||||
|
libc::mprotect(base, len, protection)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_vma_anon_name(&self, addr: u64, length: u64, name: u64) -> result::Result<(), Error> {
|
||||||
|
let name = (name != 0).then(|| CString::new(format!("pvmemcontrol-{}", name)).unwrap());
|
||||||
|
let name_ptr = if let Some(name) = &name {
|
||||||
|
name.as_ptr()
|
||||||
|
} else {
|
||||||
|
std::ptr::null()
|
||||||
|
};
|
||||||
|
debug!("addr {:X} length {} name {:?}", addr, length, name);
|
||||||
|
|
||||||
|
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||||
|
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||||
|
libc::prctl(
|
||||||
|
libc::PR_SET_VMA,
|
||||||
|
libc::PR_SET_VMA_ANON_NAME,
|
||||||
|
base,
|
||||||
|
len,
|
||||||
|
name_ptr,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_request(
|
||||||
|
&self,
|
||||||
|
func_code: FunctionCode,
|
||||||
|
addr: u64,
|
||||||
|
length: u64,
|
||||||
|
arg: u64,
|
||||||
|
) -> Result<PvmemcontrolResp, Error> {
|
||||||
|
let result = match func_code {
|
||||||
|
FunctionCode::Info => {
|
||||||
|
return Ok(PvmemcontrolResp {
|
||||||
|
ret_errno: 0.into(),
|
||||||
|
ret_code: 0.into(),
|
||||||
|
ret_value: get_page_size().into(),
|
||||||
|
arg0: MAJOR_VERSION.into(),
|
||||||
|
arg1: MINOR_VERSION.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
FunctionCode::Dontneed => self.madvise(addr, length, libc::MADV_DONTNEED),
|
||||||
|
FunctionCode::Remove => self.madvise(addr, length, libc::MADV_REMOVE),
|
||||||
|
FunctionCode::Free => self.madvise(addr, length, libc::MADV_FREE),
|
||||||
|
FunctionCode::Pageout => self.madvise(addr, length, libc::MADV_PAGEOUT),
|
||||||
|
FunctionCode::Dontdump => self.madvise(addr, length, libc::MADV_DONTDUMP),
|
||||||
|
FunctionCode::SetVMAAnonName => self.set_vma_anon_name(addr, length, arg),
|
||||||
|
FunctionCode::Mlock => self.mlock(addr, length, false),
|
||||||
|
FunctionCode::Munlock => self.munlock(addr, length),
|
||||||
|
FunctionCode::MprotectNone => self.mprotect(addr, length, libc::PROT_NONE),
|
||||||
|
FunctionCode::MprotectR => self.mprotect(addr, length, libc::PROT_READ),
|
||||||
|
FunctionCode::MprotectW => self.mprotect(addr, length, libc::PROT_WRITE),
|
||||||
|
FunctionCode::MprotectRW => {
|
||||||
|
self.mprotect(addr, length, libc::PROT_READ | libc::PROT_WRITE)
|
||||||
|
}
|
||||||
|
FunctionCode::Mergeable => self.madvise(addr, length, libc::MADV_MERGEABLE),
|
||||||
|
FunctionCode::Unmergeable => self.madvise(addr, length, libc::MADV_UNMERGEABLE),
|
||||||
|
};
|
||||||
|
result.map(|_| PvmemcontrolResp::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_request(
|
||||||
|
&self,
|
||||||
|
PvmemcontrolReq {
|
||||||
|
func_code,
|
||||||
|
addr,
|
||||||
|
length,
|
||||||
|
arg,
|
||||||
|
}: PvmemcontrolReq,
|
||||||
|
) -> Result<PvmemcontrolResp, Error> {
|
||||||
|
let (func_code, addr, length, arg) = (
|
||||||
|
func_code.to_native(),
|
||||||
|
addr.to_native(),
|
||||||
|
length.to_native(),
|
||||||
|
arg.to_native(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let resp_or_err = FunctionCode::try_from(func_code)
|
||||||
|
.map_err(|_| Error::UnknownFunctionCode(func_code))
|
||||||
|
.and_then(|func_code| self.process_request(func_code, addr, length, arg));
|
||||||
|
|
||||||
|
let resp = match resp_or_err {
|
||||||
|
Ok(resp) => resp,
|
||||||
|
Err(e) => match e {
|
||||||
|
Error::InvalidArgument(arg) => PvmemcontrolResp {
|
||||||
|
ret_errno: (libc::EINVAL as u32).into(),
|
||||||
|
ret_code: (arg as u32).into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Error::LibcFail(err) => PvmemcontrolResp {
|
||||||
|
ret_errno: (err.raw_os_error().unwrap_or(libc::EFAULT) as u32).into(),
|
||||||
|
ret_code: 0u32.into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Error::UnknownFunctionCode(func_code) => PvmemcontrolResp {
|
||||||
|
ret_errno: (libc::EOPNOTSUPP as u32).into(),
|
||||||
|
ret_code: (func_code as u32).into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Error::GuestMemory(err) => {
|
||||||
|
warn!("{}", err);
|
||||||
|
PvmemcontrolResp {
|
||||||
|
ret_errno: (libc::EINVAL as u32).into(),
|
||||||
|
ret_code: (func_code as u32).into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// device error, stop responding
|
||||||
|
other => return Err(other),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Ok(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_pvmemcontrol_request(&self, guest_addr: GuestAddress) {
|
||||||
|
let request: PvmemcontrolReq = if let Ok(x) = self.mem.memory().read_obj(guest_addr) {
|
||||||
|
x
|
||||||
|
} else {
|
||||||
|
warn!("cannot read from guest address {:#x}", guest_addr.0);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let response: PvmemcontrolResp = match self.handle_request(request) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("cannot process request {:?} with error {}", request, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.mem.memory().write_obj(response, guest_addr).is_err() {
|
||||||
|
warn!("cannot write to guest address {:#x}", guest_addr.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_guest_write(&self, offset: u64, data: &[u8]) {
|
||||||
|
if offset as usize != std::mem::offset_of!(PvmemcontrolTransport, command) {
|
||||||
|
if data.len() != 4 && data.len() != 8 {
|
||||||
|
warn!("guest write is not 4 or 8 bytes long");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.dev.write().unwrap().write_transport(offset, data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let data = if data.len() == 4 {
|
||||||
|
let mut d = [0u8; 4];
|
||||||
|
d.iter_mut()
|
||||||
|
.zip(data.iter())
|
||||||
|
.for_each(|(d, data)| *d = *data);
|
||||||
|
d
|
||||||
|
} else {
|
||||||
|
warn!("guest write with non u32 at command register");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let data_cmd = u32::from_le_bytes(data);
|
||||||
|
let command = PvmemcontrolTransportCommand::try_from(data_cmd);
|
||||||
|
|
||||||
|
match command {
|
||||||
|
Ok(command) => self.dev.write().unwrap().run_command(&self.mem, command),
|
||||||
|
Err(_) => {
|
||||||
|
GuestConnection::try_from(data_cmd)
|
||||||
|
.and_then(|conn| {
|
||||||
|
self.dev
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.find_connection(conn)
|
||||||
|
.ok_or(Error::InvalidConnection(conn.command))
|
||||||
|
})
|
||||||
|
.map(|gpa| self.handle_pvmemcontrol_request(gpa))
|
||||||
|
.unwrap_or_else(|err| warn!("{:?}", err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_guest_read(&self, offset: u64, data: &mut [u8]) {
|
||||||
|
self.dev.read().unwrap().read_transport(offset, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PvmemcontrolDevice {
|
||||||
|
pub fn make_device(
|
||||||
|
id: String,
|
||||||
|
mem: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||||
|
) -> (PvmemcontrolPciDevice, PvmemcontrolBusDevice) {
|
||||||
|
let dev = RwLock::new(PvmemcontrolDevice::error());
|
||||||
|
let mut configuration = PciConfiguration::new(
|
||||||
|
PVMEMCONTROL_VENDOR_ID,
|
||||||
|
PVMEMCONTROL_DEVICE_ID,
|
||||||
|
0x1,
|
||||||
|
PciClassCode::BaseSystemPeripheral,
|
||||||
|
&PvmemcontrolSubclass::Other,
|
||||||
|
None,
|
||||||
|
PciHeaderType::Device,
|
||||||
|
PVMEMCONTROL_SUBSYSTEM_VENDOR_ID,
|
||||||
|
PVMEMCONTROL_SUBSYSTEM_ID,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let command: [u8; 2] = [0x03, 0x01]; // memory, io, SERR#
|
||||||
|
|
||||||
|
configuration.write_config_register(1, 0, &command);
|
||||||
|
(
|
||||||
|
PvmemcontrolPciDevice {
|
||||||
|
id,
|
||||||
|
configuration,
|
||||||
|
bar_regions: Vec::new(),
|
||||||
|
},
|
||||||
|
PvmemcontrolBusDevice { mem, dev },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PciDevice for PvmemcontrolPciDevice {
|
||||||
|
fn write_config_register(
|
||||||
|
&mut self,
|
||||||
|
reg_idx: usize,
|
||||||
|
offset: u64,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Option<Arc<Barrier>> {
|
||||||
|
self.configuration
|
||||||
|
.write_config_register(reg_idx, offset, data);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
||||||
|
self.configuration.read_config_register(reg_idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> Option<String> {
|
||||||
|
Some(self.id.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_bar_reprogramming(
|
||||||
|
&mut self,
|
||||||
|
reg_idx: usize,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Option<BarReprogrammingParams> {
|
||||||
|
self.configuration.detect_bar_reprogramming(reg_idx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocate_bars(
|
||||||
|
&mut self,
|
||||||
|
_allocator: &Arc<Mutex<SystemAllocator>>,
|
||||||
|
mmio32_allocator: &mut AddressAllocator,
|
||||||
|
_mmio64_allocator: &mut AddressAllocator,
|
||||||
|
resources: Option<Vec<Resource>>,
|
||||||
|
) -> Result<Vec<PciBarConfiguration>, PciDeviceError> {
|
||||||
|
let mut bars = Vec::new();
|
||||||
|
let region_type = PciBarRegionType::Memory32BitRegion;
|
||||||
|
let bar_id = 0;
|
||||||
|
let region_size = PVMEMCONTROL_DEVICE_MMIO_SIZE;
|
||||||
|
let restoring = resources.is_some();
|
||||||
|
let bar_addr = mmio32_allocator
|
||||||
|
.allocate(None, region_size, Some(PVMEMCONTROL_DEVICE_MMIO_ALIGN))
|
||||||
|
.ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
|
||||||
|
|
||||||
|
let bar = PciBarConfiguration::default()
|
||||||
|
.set_index(bar_id as usize)
|
||||||
|
.set_address(bar_addr.raw_value())
|
||||||
|
.set_size(region_size)
|
||||||
|
.set_region_type(region_type)
|
||||||
|
.set_prefetchable(PciBarPrefetchable::NotPrefetchable);
|
||||||
|
|
||||||
|
if !restoring {
|
||||||
|
self.configuration
|
||||||
|
.add_pci_bar(&bar)
|
||||||
|
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
bars.push(bar);
|
||||||
|
self.bar_regions.clone_from(&bars);
|
||||||
|
Ok(bars)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free_bars(
|
||||||
|
&mut self,
|
||||||
|
_allocator: &mut SystemAllocator,
|
||||||
|
mmio32_allocator: &mut AddressAllocator,
|
||||||
|
_mmio64_allocator: &mut AddressAllocator,
|
||||||
|
) -> Result<(), PciDeviceError> {
|
||||||
|
for bar in self.bar_regions.drain(..) {
|
||||||
|
mmio32_allocator.free(GuestAddress(bar.addr()), bar.size())
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), io::Error> {
|
||||||
|
for bar in self.bar_regions.iter_mut() {
|
||||||
|
if bar.addr() == old_base {
|
||||||
|
*bar = bar.set_address(new_base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pausable for PvmemcontrolPciDevice {
|
||||||
|
fn pause(&mut self) -> std::result::Result<(), MigratableError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resume(&mut self) -> std::result::Result<(), MigratableError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Snapshottable for PvmemcontrolPciDevice {
|
||||||
|
fn id(&self) -> String {
|
||||||
|
self.id.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
|
let mut snapshot = Snapshot::new_from_state(&())?;
|
||||||
|
|
||||||
|
// Snapshot PciConfiguration
|
||||||
|
snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
|
||||||
|
|
||||||
|
Ok(snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transportable for PvmemcontrolPciDevice {}
|
||||||
|
impl Migratable for PvmemcontrolPciDevice {}
|
||||||
|
|
||||||
|
impl BusDeviceSync for PvmemcontrolBusDevice {
|
||||||
|
fn read(&self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||||
|
self.handle_guest_read(offset, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||||
|
self.handle_guest_write(offset, data);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,18 +9,15 @@ use pci::{
|
|||||||
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
|
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
|
||||||
PCI_CONFIGURATION_ID,
|
PCI_CONFIGURATION_ID,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::{Arc, Barrier, Mutex};
|
use std::sync::{Arc, Barrier, Mutex};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
|
||||||
use versionize_derive::Versionize;
|
|
||||||
use vm_allocator::{AddressAllocator, SystemAllocator};
|
use vm_allocator::{AddressAllocator, SystemAllocator};
|
||||||
use vm_device::{BusDevice, Resource};
|
use vm_device::{BusDevice, Resource};
|
||||||
use vm_memory::{Address, GuestAddress};
|
use vm_memory::{Address, GuestAddress};
|
||||||
use vm_migration::{
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PVPANIC_VENDOR_ID: u16 = 0x1b36;
|
const PVPANIC_VENDOR_ID: u16 = 0x1b36;
|
||||||
const PVPANIC_DEVICE_ID: u16 = 0x0011;
|
const PVPANIC_DEVICE_ID: u16 = 0x0011;
|
||||||
@@ -60,23 +57,20 @@ pub struct PvPanicDevice {
|
|||||||
bar_regions: Vec<PciBarConfiguration>,
|
bar_regions: Vec<PciBarConfiguration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Versionize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct PvPanicDeviceState {
|
pub struct PvPanicDeviceState {
|
||||||
events: u8,
|
events: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VersionMapped for PvPanicDeviceState {}
|
|
||||||
|
|
||||||
impl PvPanicDevice {
|
impl PvPanicDevice {
|
||||||
pub fn new(id: String, snapshot: Option<Snapshot>) -> Result<Self, PvPanicError> {
|
pub fn new(id: String, snapshot: Option<Snapshot>) -> Result<Self, PvPanicError> {
|
||||||
let pci_configuration_state =
|
let pci_configuration_state =
|
||||||
vm_migration::versioned_state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID)
|
vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| {
|
||||||
.map_err(|e| {
|
PvPanicError::RetrievePciConfigurationState(anyhow!(
|
||||||
PvPanicError::RetrievePciConfigurationState(anyhow!(
|
"Failed to get PciConfigurationState from Snapshot: {}",
|
||||||
"Failed to get PciConfigurationState from Snapshot: {}",
|
e
|
||||||
e
|
))
|
||||||
))
|
})?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut configuration = PciConfiguration::new(
|
let mut configuration = PciConfiguration::new(
|
||||||
PVPANIC_VENDOR_ID,
|
PVPANIC_VENDOR_ID,
|
||||||
@@ -97,7 +91,7 @@ impl PvPanicDevice {
|
|||||||
|
|
||||||
let state: Option<PvPanicDeviceState> = snapshot
|
let state: Option<PvPanicDeviceState> = snapshot
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|s| s.to_versioned_state())
|
.map(|s| s.to_state())
|
||||||
.transpose()
|
.transpose()
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
PvPanicError::CreatePvPanicDevice(anyhow!(
|
PvPanicError::CreatePvPanicDevice(anyhow!(
|
||||||
@@ -210,7 +204,7 @@ impl PciDevice for PvPanicDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bars.push(bar);
|
bars.push(bar);
|
||||||
self.bar_regions = bars.clone();
|
self.bar_regions.clone_from(&bars);
|
||||||
|
|
||||||
Ok(bars)
|
Ok(bars)
|
||||||
}
|
}
|
||||||
@@ -259,7 +253,7 @@ impl Snapshottable for PvPanicDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
let mut snapshot = Snapshot::new_from_versioned_state(&self.state())?;
|
let mut snapshot = Snapshot::new_from_state(&self.state())?;
|
||||||
|
|
||||||
// Snapshot PciConfiguration
|
// Snapshot PciConfiguration
|
||||||
snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
|
snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ impl BusDevice for Tpm {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!("Invalid value passed to CRTL_REQ register");
|
error!("Invalid value passed to CTRL_REQ register");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ The Cloud Hypervisor API exposes the following actions through its endpoints:
|
|||||||
| Add vsock device to the VM | `/vm.add-vsock` | `/schemas/VsockConfig` | `/schemas/PciDeviceInfo` | The VM is booted |
|
| Add vsock device to the VM | `/vm.add-vsock` | `/schemas/VsockConfig` | `/schemas/PciDeviceInfo` | The VM is booted |
|
||||||
| Remove device from the VM | `/vm.remove-device` | `/schemas/VmRemoveDevice` | N/A | The VM is booted |
|
| Remove device from the VM | `/vm.remove-device` | `/schemas/VmRemoveDevice` | N/A | The VM is booted |
|
||||||
| Dump the VM counters | `/vm.counters` | N/A | `/schemas/VmCounters` | The VM is booted |
|
| Dump the VM counters | `/vm.counters` | N/A | `/schemas/VmCounters` | The VM is booted |
|
||||||
|
| Inject an NMI | `/vm.nmi` | N/A | N/A | The VM is booted |
|
||||||
| Prepare to receive a migration | `/vm.receive-migration` | `/schemas/ReceiveMigrationData` | N/A | N/A |
|
| Prepare to receive a migration | `/vm.receive-migration` | `/schemas/ReceiveMigrationData` | N/A | N/A |
|
||||||
| Start to send migration to target | `/vm.send-migration` | `/schemas/SendMigrationData` | N/A | The VM is booted and (shared mem or hugepages enabled) |
|
| Start to send migration to target | `/vm.send-migration` | `/schemas/SendMigrationData` | N/A | The VM is booted and (shared mem or hugepages enabled) |
|
||||||
|
|
||||||
@@ -269,7 +270,7 @@ definition in XML format:
|
|||||||
### Command Line Interface
|
### Command Line Interface
|
||||||
|
|
||||||
The Cloud Hypervisor Command Line Interface (CLI) can only be used for launching
|
The Cloud Hypervisor Command Line Interface (CLI) can only be used for launching
|
||||||
the Cloud Hypervisor binary, i.e. it can not be used for controlling the VMM or
|
the Cloud Hypervisor binary, i.e. it cannot be used for controlling the VMM or
|
||||||
the launched VM once they're up and running.
|
the launched VM once they're up and running.
|
||||||
|
|
||||||
If you want to inspect the VMM, or control the VM after launching Cloud
|
If you want to inspect the VMM, or control the VM after launching Cloud
|
||||||
|
|||||||
@@ -159,10 +159,10 @@ as we might need to update the direct kernel boot command line, replacing
|
|||||||
|
|
||||||
Update all references to the previous image name to the new one.
|
Update all references to the previous image name to the new one.
|
||||||
|
|
||||||
## NVIDIA image for VFIO baremetal CI
|
## NVIDIA image for VFIO bare-metal CI
|
||||||
|
|
||||||
Here we are going to describe how to create a cloud image that contains the
|
Here we are going to describe how to create a cloud image that contains the
|
||||||
necessary NVIDIA drivers for our VFIO baremetal CI.
|
necessary NVIDIA drivers for our VFIO bare-metal CI.
|
||||||
|
|
||||||
### Download base image
|
### Download base image
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ We usually start from one of the custom cloud image we have previously created
|
|||||||
but we can use a stock cloud image as well.
|
but we can use a stock cloud image as well.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
wget https://cloud-hypervisor.azureedge.net/jammy-server-cloudimg-amd64-custom-20230119-0.raw
|
wget https://ch-images.azureedge.net/jammy-server-cloudimg-amd64-custom-20230119-0.raw
|
||||||
mv jammy-server-cloudimg-amd64-custom-20230119-0.raw jammy-server-cloudimg-amd64-nvidia.raw
|
mv jammy-server-cloudimg-amd64-custom-20230119-0.raw jammy-server-cloudimg-amd64-nvidia.raw
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Cloud Hypervisor uses [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) for fuzzing individual components.
|
Cloud Hypervisor uses [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) for fuzzing individual components.
|
||||||
|
|
||||||
The fuzzers are are in the `fuzz/fuzz_targets` directory
|
The fuzzers are in the `fuzz/fuzz_targets` directory
|
||||||
|
|
||||||
## Preparation
|
## Preparation
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ On-line CPU(s) list: 0-7
|
|||||||
|
|
||||||
After a reboot the added CPUs will remain.
|
After a reboot the added CPUs will remain.
|
||||||
|
|
||||||
Removing CPUs works similarly by reducing the number in the "desired_vcpus" field of the reisze API. The CPUs will be automatically offlined inside the guest so there is no need to run any commands inside the guest:
|
Removing CPUs works similarly by reducing the number in the "desired_vcpus" field of the resize API. The CPUs will be automatically offlined inside the guest so there is no need to run any commands inside the guest:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./ch-remote --api-socket=/tmp/ch-socket resize --cpus 2
|
./ch-remote --api-socket=/tmp/ch-socket resize --cpus 2
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ 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
|
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
|
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
|
implementation of the virtual IOMMU is responsible for updating the physical
|
||||||
DMA Remapping table (DMAR) everytime the DMA mapping changes. This must happen
|
DMA Remapping table (DMAR) every time the DMA mapping changes. This must happen
|
||||||
through the VFIO framework on the host as this is the only userspace interface
|
through the VFIO framework on the host as this is the only userspace interface
|
||||||
to interact with a physical IOMMU.
|
to interact with a physical IOMMU.
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ On AArch64 architecture, the virtual IOMMU can still be used even if ACPI is not
|
|||||||
enabled. But the effect is different with what the aforementioned test showed.
|
enabled. But the effect is different with what the aforementioned test showed.
|
||||||
|
|
||||||
When ACPI is disabled, virtual IOMMU is supported through Flattened Device Tree
|
When ACPI is disabled, virtual IOMMU is supported through Flattened Device Tree
|
||||||
(FDT). In this case, the guest kernel can not tell which device should be
|
(FDT). In this case, the guest kernel cannot tell which device should be
|
||||||
IOMMU-attached and which should not. No matter how many devices you attached to
|
IOMMU-attached and which should not. No matter how many devices you attached to
|
||||||
the virtual IOMMU by setting `iommu=on` option, all the devices on the PCI bus
|
the virtual IOMMU by setting `iommu=on` option, all the devices on the PCI bus
|
||||||
will be attached to the virtual IOMMU (except the IOMMU itself). Each of the
|
will be attached to the virtual IOMMU (except the IOMMU itself). Each of the
|
||||||
|
|||||||
105
docs/landlock.md
Normal file
105
docs/landlock.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Sandboxing using Landlock
|
||||||
|
|
||||||
|
Landlock is a lightweight mechanism to allow unprivileged applications to
|
||||||
|
sandbox themselves.
|
||||||
|
|
||||||
|
During initial stages of running, applications can define the set of resources
|
||||||
|
(mostly files) they need to access during their lifetime. All such rules are
|
||||||
|
used to create a ruleset. Once the ruleset is applied, the process cannot access
|
||||||
|
any resources outside of the ruleset during its lifetime, even if it were
|
||||||
|
compromised.
|
||||||
|
|
||||||
|
Under the scope of `read` and `write` access, Landlock currently allows some
|
||||||
|
additional accesses (eg: for now, access to extended file attributes is always
|
||||||
|
allowed). Eventually, Landlock will only allow accesses similar to Unix
|
||||||
|
permissions.
|
||||||
|
|
||||||
|
## Host Setup
|
||||||
|
|
||||||
|
Landlock should be enabled in Host kernel to use it with cloud-hypervisor.
|
||||||
|
Please following [Kernel-Support](https://docs.kernel.org/userspace-api/landlock.html#kernel-support) link to enable Landlock on Host kernel.
|
||||||
|
|
||||||
|
|
||||||
|
Landlock support can be checked with following command:
|
||||||
|
```
|
||||||
|
$ sudo dmesg | grep -w landlock
|
||||||
|
[ 0.000000] landlock: Up and running.
|
||||||
|
```
|
||||||
|
Linux kernel confirms Landlock support with above message in dmesg.
|
||||||
|
|
||||||
|
## Enable Landlock
|
||||||
|
|
||||||
|
At the time of enabling Landlock, Cloud-Hypervisor process needs the complete
|
||||||
|
list of files it accesses over its lifetime. So, Landlock is enabled `vm_create`
|
||||||
|
stage of guest boot.
|
||||||
|
|
||||||
|
### Command Line
|
||||||
|
Append `--landlock` to Cloud-Hypervisor's command line to enable Landlock
|
||||||
|
support.
|
||||||
|
|
||||||
|
If you expect guest to access additional paths after it boots
|
||||||
|
(ex: during hotplug), those paths can be passed using `--landlock-rules` command
|
||||||
|
line parameter.
|
||||||
|
|
||||||
|
### API
|
||||||
|
Landlock can also be enabled during `vm.create` request by passing a config like below:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"landlock_enable": true,
|
||||||
|
"landlock_rules": [
|
||||||
|
{
|
||||||
|
"path": "/tmp/disk1",
|
||||||
|
"access": "rw"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/tmp/disk2",
|
||||||
|
"access": "rw"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
To enable Landlock:
|
||||||
|
|
||||||
|
```
|
||||||
|
./cloud-hypervisor \
|
||||||
|
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||||
|
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
||||||
|
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
||||||
|
--cpus boot=4 \
|
||||||
|
--memory size=1024M \
|
||||||
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
|
--landlock
|
||||||
|
```
|
||||||
|
Hotplugging any new file-backed resources to above guest will result in
|
||||||
|
**Permission Denied** error.
|
||||||
|
|
||||||
|
To enable Landlock with hotplug support:
|
||||||
|
|
||||||
|
```
|
||||||
|
./cloud-hypervisor \
|
||||||
|
--api-socket /tmpXXXX/ch.socket \
|
||||||
|
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||||
|
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
||||||
|
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
||||||
|
--cpus boot=4 \
|
||||||
|
--memory size=1024M \
|
||||||
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
|
--landlock \
|
||||||
|
--landlock-rules path="/path/to/hotplug1",access="rw" path="/path/to/hotplug2",access="rw"
|
||||||
|
|
||||||
|
./ch-remote --api-socket /tmpXXXX/ch.socket \
|
||||||
|
add-disk "path=/path/to/hotplug/blk.raw"
|
||||||
|
```
|
||||||
|
|
||||||
|
`--landlock-rules` accepts file or directory paths among its options.
|
||||||
|
|
||||||
|
# References
|
||||||
|
|
||||||
|
* https://landlock.io/
|
||||||
@@ -466,7 +466,7 @@ List of virtual CPUs attached to the guest NUMA node identified by the
|
|||||||
`guest_numa_id` option. This allows for describing a list of CPUs which
|
`guest_numa_id` option. This allows for describing a list of CPUs which
|
||||||
must be seen by the guest as belonging to the NUMA node `guest_numa_id`.
|
must be seen by the guest as belonging to the NUMA node `guest_numa_id`.
|
||||||
|
|
||||||
One can use this option for a fine grained description of the NUMA topology
|
One can use this option for a fine-grained description of the NUMA topology
|
||||||
regarding the CPUs associated with it, which might help the guest run more
|
regarding the CPUs associated with it, which might help the guest run more
|
||||||
efficiently.
|
efficiently.
|
||||||
|
|
||||||
@@ -573,7 +573,7 @@ _Example_
|
|||||||
### PCI bus
|
### PCI bus
|
||||||
|
|
||||||
Cloud Hypervisor supports guests with one or more PCI segments. The default PCI segment always
|
Cloud Hypervisor supports guests with one or more PCI segments. The default PCI segment always
|
||||||
has affinity to NUMA node 0. Be default, all other PCI segments have afffinity to NUMA node 0.
|
has affinity to NUMA node 0. Be default, all other PCI segments have affinity to NUMA node 0.
|
||||||
The user may configure the NUMA affinity for any additional PCI segments.
|
The user may configure the NUMA affinity for any additional PCI segments.
|
||||||
|
|
||||||
_Example_
|
_Example_
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ e.g. `dev_cli.sh`. The only prerequisite is [Docker installation](https://docs.d
|
|||||||
Please note that upon its first invocation, this script will pull a
|
Please note that upon its first invocation, this script will pull a
|
||||||
fairly large container image.
|
fairly large container image.
|
||||||
|
|
||||||
|
## Run the Performance Tests
|
||||||
|
|
||||||
To generate metrics data for all available performance tests (including
|
To generate metrics data for all available performance tests (including
|
||||||
boot time, block I/O throughput, and network throughput & latency) and
|
boot time, block I/O throughput, and network throughput & latency) and
|
||||||
output the result into a json file:
|
output the result into a json file:
|
||||||
@@ -27,3 +29,92 @@ To generate metrics data for selected performance tests, e.g. boot time only:
|
|||||||
```
|
```
|
||||||
$ ./scripts/dev_cli.sh tests --metrics -- -- --report-file /tmp/metrics.json --test-filter boot_time
|
$ ./scripts/dev_cli.sh tests --metrics -- -- --report-file /tmp/metrics.json --test-filter boot_time
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To set custom timeout or test iterations for all performance tests:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./scripts/dev_cli.sh tests --metrics -- -- --timeout 5 --iterations 10
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Tests Details
|
||||||
|
|
||||||
|
The following table lists the supported performance tests with default
|
||||||
|
timeout and number of iterations. The `timeout` defines the maximum
|
||||||
|
execution time of each test for each iteration. The `iteration` defines
|
||||||
|
how many times a test needs to be executed to generate the final metrics
|
||||||
|
data.
|
||||||
|
|
||||||
|
| **Type** | **Metric** | **Timeout(s)** | **Iterations** |
|
||||||
|
|------------|--------------------------------------------|----------------|----------------|
|
||||||
|
| Boot Time | boot_time_ms | 2 | 10 |
|
||||||
|
| | boot_time_pmem_ms | 2 | 10 |
|
||||||
|
| | boot_time_16_vcpus_ms | 2 | 10 |
|
||||||
|
| | boot_time_16_vcpus_pmem_ms | 2 | 10 |
|
||||||
|
| Virtio Net | virtio_net_latency_us | 10 | 5 |
|
||||||
|
| | virtio_net_throughput_single_queue_rx_gbps | 10 | 5 |
|
||||||
|
| | virtio_net_throughput_single_queue_tx_gbps | 10 | 5 |
|
||||||
|
| | virtio_net_throughput_multi_queue_rx_gbps | 10 | 5 |
|
||||||
|
| | virtio_net_throughput_multi_queue_tx_gbps | 10 | 5 |
|
||||||
|
| Block | block_read_MiBps | 10 | 5 |
|
||||||
|
| | block_write_MiBps | 10 | 5 |
|
||||||
|
| | block_random_read_MiBps | 10 | 5 |
|
||||||
|
| | block_random_write_MiBps | 10 | 5 |
|
||||||
|
| | block_multi_queue_read_MiBps | 10 | 5 |
|
||||||
|
| | block_multi_queue_write_MiBps | 10 | 5 |
|
||||||
|
| | block_multi_queue_random_read_MiBps | 10 | 5 |
|
||||||
|
| | block_multi_queue_random_write_MiBps | 10 | 5 |
|
||||||
|
| | block_read_IOPS | 10 | 5 |
|
||||||
|
| | block_write_IOPS | 10 | 5 |
|
||||||
|
| | block_random_read_IOPS | 10 | 5 |
|
||||||
|
| | block_random_write_IOPS | 10 | 5 |
|
||||||
|
| | block_multi_queue_read_IOPS | 10 | 5 |
|
||||||
|
| | block_multi_queue_write_IOPS | 10 | 5 |
|
||||||
|
| | block_multi_queue_random_read_IOPS | 10 | 5 |
|
||||||
|
| | block_multi_queue_random_write_IOPS | 10 | 5 |
|
||||||
|
| Other | restore_latency_time_ms | 2 | 10 |
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
Performance-metrics output the result into a json file if `report-file`
|
||||||
|
param is set. The fields included in JSON include:
|
||||||
|
|
||||||
|
| Field Name | Content |
|
||||||
|
|--------------------|------------------------------------------|
|
||||||
|
| git_human_readable | Recent tag information of git repository |
|
||||||
|
| git_revision | Commit id of HEAD |
|
||||||
|
| git_commit_date | Commit date of HEAD |
|
||||||
|
| date | Date for executing the program |
|
||||||
|
| results | A list of metrics |
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Here is an example of generating metrics data for the boot time using
|
||||||
|
`pmem`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./scripts/dev_cli.sh tests --metrics -- -- --test-filter boot_time_pmem_ms
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is a sample output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"git_human_readable": "v40.0",
|
||||||
|
"git_revision": "e9b263975786abbf895469b93dfc00f21ce39a88",
|
||||||
|
"git_commit_date": "Fri Jun 21 08:40:44 2024 +0000",
|
||||||
|
"date": "Tue Jul 16 16:35:29 UTC 2024",
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"name": "boot_time_pmem_ms",
|
||||||
|
"mean": 105.9461,
|
||||||
|
"std_dev": 7.140993312558129,
|
||||||
|
"max": 120.01499999999999,
|
||||||
|
"min": 92.37600000000002
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the metrics data above is for illustration purpose only and
|
||||||
|
does not represent the actual performance of Cloud Hypervisor on your
|
||||||
|
system.
|
||||||
|
|||||||
2
docs/releases.md
Executable file → Normal file
2
docs/releases.md
Executable file → Normal file
@@ -95,7 +95,7 @@ E - EOL
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### LTS Stablity Considerations
|
### LTS Stability Considerations
|
||||||
|
|
||||||
An LTS release is just a `MAJOR` release for which point releases are made for
|
An LTS release is just a `MAJOR` release for which point releases are made for
|
||||||
longer following the same rules for what can be backported to a `POINT` release.
|
longer following the same rules for what can be backported to a `POINT` release.
|
||||||
|
|||||||
@@ -93,6 +93,21 @@ start using it.
|
|||||||
At this point, the VM is fully restored and is identical to the VM which was
|
At this point, the VM is fully restored and is identical to the VM which was
|
||||||
snapshot earlier.
|
snapshot earlier.
|
||||||
|
|
||||||
|
## Restore a VM with new Net FDs
|
||||||
|
For a VM created with FDs explicitly passed to NetConfig, a set of valid FDs
|
||||||
|
need to be provided along with the VM restore command in the following syntax:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# First terminal
|
||||||
|
./cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock
|
||||||
|
|
||||||
|
# Second terminal
|
||||||
|
./ch-remote --api-socket=/tmp/cloud-hypervisor.sock restore source_url=file:///home/foo/snapshot net_fds=[net1@[23,24],net2@[25,26]]
|
||||||
|
```
|
||||||
|
In the example above, the net device with id `net1` will be backed by FDs '23'
|
||||||
|
and '24', and the net device with id `net2` will be backed by FDs '25' and '26'
|
||||||
|
from the restored VM.
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
VFIO devices and Intel SGX are out of scope.
|
VFIO devices and Intel SGX are out of scope.
|
||||||
|
|||||||
@@ -37,6 +37,6 @@ generating traces of the boot. These can be relocated for focus tracing on a
|
|||||||
narrow part of the code base.
|
narrow part of the code base.
|
||||||
|
|
||||||
A `tracer::trace_point!()` macro is also provided for an instantaneous trace
|
A `tracer::trace_point!()` macro is also provided for an instantaneous trace
|
||||||
point however this is not in use in the code base currently nor is handled by
|
point however this is neither in use in the code base currently nor is handled by
|
||||||
the visualisation script due to the difficulty in representation in the SVG.
|
the visualisation script due to the difficulty in representation in the SVG.
|
||||||
|
|
||||||
|
|||||||
60
docs/vfio.md
60
docs/vfio.md
@@ -123,3 +123,63 @@ $ ls /sys/kernel/iommu_groups/22/devices/
|
|||||||
This means these two devices are under the same IOMMU group 22. In such case,
|
This means these two devices are under the same IOMMU group 22. In such case,
|
||||||
it is important to bind both devices to VFIO and pass them both through the
|
it is important to bind both devices to VFIO and pass them both through the
|
||||||
VM, otherwise this could cause some functional and security issues.
|
VM, otherwise this could cause some functional and security issues.
|
||||||
|
|
||||||
|
### Advanced Configuration Options
|
||||||
|
|
||||||
|
When using NVIDIA GPUs in a VFIO passthrough configuration, advanced
|
||||||
|
configuration options are supported to enable GPUDirect P2P DMA over
|
||||||
|
PCIe. When enabled, loads and stores between GPUs use native PCIe
|
||||||
|
peer-to-peer transactions instead of a shared memory buffer. This drastically
|
||||||
|
decreases P2P latency between GPUs. This functionality is supported by
|
||||||
|
cloud-hypervisor on NVIDIA Turing, Ampere, Hopper, and Lovelace GPUs.
|
||||||
|
|
||||||
|
The NVIDIA driver does not enable GPUDirect P2P over PCIe within guests
|
||||||
|
by default because hardware support for routing P2P TLP between PCIe root
|
||||||
|
ports is optional. PCIe P2P should always be supported between devices
|
||||||
|
on the same PCIe switch. The `x_nv_gpudirect_clique` config argument may
|
||||||
|
be used to signal support for PCIe P2P traffic between NVIDIA VFIO endpoints.
|
||||||
|
The guest driver assumes that P2P traffic is supported between all endpoints
|
||||||
|
that are part of the same clique.
|
||||||
|
```
|
||||||
|
--device path=/sys/bus/pci/devices/0000:01:00.0/,x_nv_gpudirect_clique=0
|
||||||
|
```
|
||||||
|
|
||||||
|
The following command can be run on the guest to verify that GPUDirect P2P is
|
||||||
|
correctly enabled.
|
||||||
|
```
|
||||||
|
nvidia-smi topo -p2p r
|
||||||
|
GPU0 GPU1 GPU2 GPU3 GPU4 GPU5 GPU6 GPU7
|
||||||
|
GPU0 X OK OK OK OK OK OK OK
|
||||||
|
GPU1 OK X OK OK OK OK OK OK
|
||||||
|
GPU2 OK OK X OK OK OK OK OK
|
||||||
|
GPU3 OK OK OK X OK OK OK OK
|
||||||
|
GPU4 OK OK OK OK X OK OK OK
|
||||||
|
GPU5 OK OK OK OK OK X OK OK
|
||||||
|
GPU6 OK OK OK OK OK OK X OK
|
||||||
|
GPU7 OK OK OK OK OK OK OK X
|
||||||
|
```
|
||||||
|
|
||||||
|
Some VFIO devices have a 32-bit mmio BAR. When using many such devices, it is
|
||||||
|
possible to exhaust the 32-bit mmio space available on a PCI segment. The
|
||||||
|
following example demonstrates an example device with a 16 MiB 32-bit mmio BAR.
|
||||||
|
```
|
||||||
|
lspci -s 0000:01:00.0 -v
|
||||||
|
0000:01:00.0 3D controller: NVIDIA Corporation Device 26b9 (rev a1)
|
||||||
|
[...]
|
||||||
|
Memory at f9000000 (32-bit, non-prefetchable) [size=16M]
|
||||||
|
Memory at 46000000000 (64-bit, prefetchable) [size=64G]
|
||||||
|
Memory at 48040000000 (64-bit, prefetchable) [size=32M]
|
||||||
|
[...]
|
||||||
|
```
|
||||||
|
|
||||||
|
When using multiple PCI segments, the 32-bit mmio address space available to
|
||||||
|
be allocated to VFIO devices is equally split between all PCI segments by
|
||||||
|
default. This can be tuned with the `--pci-segment` flag. The following example
|
||||||
|
demonstrates a guest with two PCI segments. 2/3 of the 32-bit mmio address
|
||||||
|
space is available for use by devices on PCI segment 0 and 1/3 of the 32-bit
|
||||||
|
mmio address space is available for use by devices on PCI segment 1.
|
||||||
|
```
|
||||||
|
--platform num_pci_segments=2
|
||||||
|
--pci-segment pci_segment=0,mmio32_aperture_weight=2
|
||||||
|
--pci-segment pci_segment=1,mmio32_aperture_weight=1
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# VSOCK support
|
# VSOCK support
|
||||||
|
|
||||||
VSOCK provides a way for guest and host to communicate through a socket. VSOCK sockets support both stream and datagram types.
|
VSOCK provides a way for guest and host to communicate through a socket. `cloud-hypervisor` only supports stream VSOCK sockets.
|
||||||
|
|
||||||
The `virtio-vsock` is based on the [Firecracker](https://github.com/firecracker-microvm/firecracker/blob/main/docs/vsock.md) implementation, where additional details can be found.
|
The `virtio-vsock` is based on the [Firecracker](https://github.com/firecracker-microvm/firecracker/blob/main/docs/vsock.md) implementation, where additional details can be found.
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "event_monitor"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
name = "event_monitor"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
flume = "0.10.14"
|
flume = "0.11.0"
|
||||||
libc = "0.2.153"
|
libc = "0.2.155"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
serde = { version = "1.0.196", features = ["rc", "derive"] }
|
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||||
serde_json = "1.0.109"
|
serde_json = "1.0.120"
|
||||||
|
|||||||
617
fuzz/Cargo.lock
generated
617
fuzz/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,139 +1,133 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cloud-hypervisor-fuzz"
|
|
||||||
version = "0.0.0"
|
|
||||||
authors = ["Automatically generated"]
|
authors = ["Automatically generated"]
|
||||||
publish = false
|
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
name = "cloud-hypervisor-fuzz"
|
||||||
|
publish = false
|
||||||
|
version = "0.0.0"
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
cargo-fuzz = true
|
cargo-fuzz = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
igvm = []
|
igvm = []
|
||||||
|
pvmemcontrol = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
block = { path = "../block" }
|
block = { path = "../block" }
|
||||||
devices = { path = "../devices" }
|
devices = { path = "../devices" }
|
||||||
epoll = "4.3.1"
|
epoll = "4.3.3"
|
||||||
libc = "0.2.152"
|
libc = "0.2.155"
|
||||||
libfuzzer-sys = "0.4.7"
|
libfuzzer-sys = "0.4.7"
|
||||||
linux-loader = { version = "0.11.0", features = ["elf", "bzimage", "pe"] }
|
linux-loader = { version = "0.11.0", features = ["bzimage", "elf", "pe"] }
|
||||||
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" }
|
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" }
|
||||||
net_util = { path = "../net_util" }
|
net_util = { path = "../net_util" }
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
seccompiler = "0.4.0"
|
seccompiler = "0.4.0"
|
||||||
virtio-devices = { path = "../virtio-devices" }
|
virtio-devices = { path = "../virtio-devices" }
|
||||||
virtio-queue = "0.11.0"
|
virtio-queue = "0.12.0"
|
||||||
vmm = { path = "../vmm" }
|
|
||||||
vmm-sys-util = "0.12.1"
|
|
||||||
vm-memory = "0.14.0"
|
|
||||||
vm-migration = { path = "../vm-migration" }
|
|
||||||
vm-device = { path = "../vm-device" }
|
vm-device = { path = "../vm-device" }
|
||||||
|
vm-memory = "0.14.1"
|
||||||
|
vm-migration = { path = "../vm-migration" }
|
||||||
vm-virtio = { path = "../vm-virtio" }
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
|
vmm = { path = "../vmm", features = ["guest_debug"] }
|
||||||
[dependencies.cloud-hypervisor]
|
vmm-sys-util = "0.12.1"
|
||||||
path = ".."
|
|
||||||
|
|
||||||
[patch.crates-io]
|
|
||||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.7.0" }
|
|
||||||
versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch-0.1.6" }
|
|
||||||
|
|
||||||
# Prevent this from interfering with workspaces
|
# Prevent this from interfering with workspaces
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["."]
|
members = ["."]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "balloon"
|
name = "balloon"
|
||||||
path = "fuzz_targets/balloon.rs"
|
path = "fuzz_targets/balloon.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "block"
|
name = "block"
|
||||||
path = "fuzz_targets/block.rs"
|
path = "fuzz_targets/block.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "cmos"
|
name = "cmos"
|
||||||
path = "fuzz_targets/cmos.rs"
|
path = "fuzz_targets/cmos.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "console"
|
name = "console"
|
||||||
path = "fuzz_targets/console.rs"
|
path = "fuzz_targets/console.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "http_api"
|
name = "http_api"
|
||||||
path = "fuzz_targets/http_api.rs"
|
path = "fuzz_targets/http_api.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "iommu"
|
name = "iommu"
|
||||||
path = "fuzz_targets/iommu.rs"
|
path = "fuzz_targets/iommu.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "linux_loader"
|
name = "linux_loader"
|
||||||
path = "fuzz_targets/linux_loader.rs"
|
path = "fuzz_targets/linux_loader.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "linux_loader_cmdline"
|
name = "linux_loader_cmdline"
|
||||||
path = "fuzz_targets/linux_loader_cmdline.rs"
|
path = "fuzz_targets/linux_loader_cmdline.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "mem"
|
name = "mem"
|
||||||
path = "fuzz_targets/mem.rs"
|
path = "fuzz_targets/mem.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "net"
|
name = "net"
|
||||||
path = "fuzz_targets/net.rs"
|
path = "fuzz_targets/net.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "pmem"
|
name = "pmem"
|
||||||
path = "fuzz_targets/pmem.rs"
|
path = "fuzz_targets/pmem.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "qcow"
|
name = "qcow"
|
||||||
path = "fuzz_targets/qcow.rs"
|
path = "fuzz_targets/qcow.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "rng"
|
name = "rng"
|
||||||
path = "fuzz_targets/rng.rs"
|
path = "fuzz_targets/rng.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "serial"
|
name = "serial"
|
||||||
path = "fuzz_targets/serial.rs"
|
path = "fuzz_targets/serial.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "vhdx"
|
name = "vhdx"
|
||||||
path = "fuzz_targets/vhdx.rs"
|
path = "fuzz_targets/vhdx.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
doc = false
|
||||||
name = "watchdog"
|
name = "watchdog"
|
||||||
path = "fuzz_targets/watchdog.rs"
|
path = "fuzz_targets/watchdog.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ impl RequestHandler for StubApiRequestHandler {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
|
#[cfg(target_arch = "x86_64")]
|
||||||
fn vm_coredump(&mut self, _: &str) -> Result<(), VmError> {
|
fn vm_coredump(&mut self, _: &str) -> Result<(), VmError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -180,16 +180,20 @@ impl RequestHandler for StubApiRequestHandler {
|
|||||||
vdpa: None,
|
vdpa: None,
|
||||||
vsock: None,
|
vsock: None,
|
||||||
pvpanic: false,
|
pvpanic: false,
|
||||||
|
#[cfg(feature = "pvmemcontrol")]
|
||||||
|
pvmemcontrol: None,
|
||||||
iommu: false,
|
iommu: false,
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
sgx_epc: None,
|
sgx_epc: None,
|
||||||
numa: None,
|
numa: None,
|
||||||
watchdog: false,
|
watchdog: false,
|
||||||
#[cfg(feature = "guest_debug")]
|
|
||||||
gdb: false,
|
gdb: false,
|
||||||
|
pci_segments: None,
|
||||||
platform: None,
|
platform: None,
|
||||||
tpm: None,
|
tpm: None,
|
||||||
preserved_fds: None,
|
preserved_fds: None,
|
||||||
|
landlock_enable: false,
|
||||||
|
landlock_rules: None,
|
||||||
})),
|
})),
|
||||||
state: VmState::Running,
|
state: VmState::Running,
|
||||||
memory_actual_size: 0,
|
memory_actual_size: 0,
|
||||||
@@ -273,6 +277,10 @@ impl RequestHandler for StubApiRequestHandler {
|
|||||||
fn vm_send_migration(&mut self, _: VmSendMigrationData) -> Result<(), MigratableError> {
|
fn vm_send_migration(&mut self, _: VmSendMigrationData) -> Result<(), MigratableError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn vm_nmi(&mut self) -> Result<(), VmError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn http_receiver_stub(exit_evt: EventFd, api_evt: EventFd, api_receiver: Receiver<ApiRequest>) {
|
fn http_receiver_stub(exit_evt: EventFd, api_evt: EventFd, api_receiver: Receiver<ApiRequest>) {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
#![no_main]
|
#![no_main]
|
||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|||||||
@@ -1,39 +1,47 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hypervisor"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Microsoft Authors"]
|
authors = ["Microsoft Authors"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "Apache-2.0 OR BSD-3-Clause"
|
license = "Apache-2.0 OR BSD-3-Clause"
|
||||||
|
name = "hypervisor"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
kvm = ["kvm-ioctls", "kvm-bindings", "vfio-ioctls/kvm"]
|
kvm = ["kvm-bindings", "kvm-ioctls", "vfio-ioctls/kvm"]
|
||||||
mshv = ["mshv-ioctls", "mshv-bindings", "vfio-ioctls/mshv", "iced-x86"]
|
mshv = ["iced-x86", "mshv-bindings", "mshv-ioctls", "vfio-ioctls/mshv"]
|
||||||
sev_snp = ["igvm_parser", "igvm_defs"]
|
sev_snp = ["igvm", "igvm_defs"]
|
||||||
tdx = []
|
tdx = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.86"
|
||||||
byteorder = "1.4.3"
|
byteorder = "1.5.0"
|
||||||
igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main", package = "igvm_defs", optional = true }
|
igvm = { version = "0.3.3", optional = true }
|
||||||
igvm_parser = { git = "https://github.com/microsoft/igvm", branch = "main", package = "igvm", optional = true }
|
igvm_defs = { version = "0.3.1", optional = true }
|
||||||
libc = "0.2.153"
|
kvm-bindings = { version = "0.8.1", optional = true, features = ["serde"] }
|
||||||
log = "0.4.20"
|
kvm-ioctls = { version = "0.17.0", optional = true }
|
||||||
kvm-ioctls = { version = "0.16.0", optional = true }
|
libc = "0.2.155"
|
||||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.7.0", features = ["with-serde", "fam-wrappers"], optional = true }
|
log = "0.4.22"
|
||||||
mshv-bindings = { git = "https://github.com/rust-vmm/mshv", branch = "main", features = ["with-serde", "fam-wrappers"], optional = true }
|
mshv-bindings = { git = "https://github.com/rust-vmm/mshv", tag = "v0.2.0", features = [
|
||||||
mshv-ioctls = { git = "https://github.com/rust-vmm/mshv", branch = "main", optional = true}
|
"fam-wrappers",
|
||||||
serde = { version = "1.0.196", features = ["rc", "derive"] }
|
"with-serde",
|
||||||
serde_with = { version = "3.4.0", default-features = false, features = ["macros"] }
|
], optional = true }
|
||||||
|
mshv-ioctls = { git = "https://github.com/rust-vmm/mshv", tag = "v0.2.0", optional = true }
|
||||||
|
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||||
|
serde_with = { version = "3.9.0", default-features = false, features = [
|
||||||
|
"macros",
|
||||||
|
] }
|
||||||
|
thiserror = "1.0.62"
|
||||||
vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false }
|
vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false }
|
||||||
vm-memory = { version = "0.14.0", features = ["backend-mmap", "backend-atomic"] }
|
vm-memory = { version = "0.14.1", features = [
|
||||||
|
"backend-atomic",
|
||||||
|
"backend-mmap",
|
||||||
|
] }
|
||||||
vmm-sys-util = { version = "0.12.1", features = ["with-serde"] }
|
vmm-sys-util = { version = "0.12.1", features = ["with-serde"] }
|
||||||
thiserror = "1.0.52"
|
|
||||||
|
|
||||||
[target.'cfg(target_arch = "x86_64")'.dependencies.iced-x86]
|
[target.'cfg(target_arch = "x86_64")'.dependencies.iced-x86]
|
||||||
optional = true
|
|
||||||
version = "1.20.0"
|
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["std", "decoder", "op_code_info", "instr_info", "fast_fmt"]
|
features = ["decoder", "fast_fmt", "instr_info", "op_code_info", "std"]
|
||||||
|
optional = true
|
||||||
|
version = "1.21.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.10.1"
|
env_logger = "0.11.3"
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
|
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::{CpuState, GicState, HypervisorDeviceError, HypervisorVmError};
|
use crate::{CpuState, GicState, HypervisorDeviceError, HypervisorVmError};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::result;
|
use std::result;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
/// Errors thrown while setting up the VGIC.
|
/// Errors thrown while setting up the VGIC.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Error while calling KVM ioctl for setting up the global interrupt controller.
|
/// Error while calling KVM ioctl for setting up the global interrupt controller.
|
||||||
|
#[error("Failed creating GIC device: {0}")]
|
||||||
CreateGic(HypervisorVmError),
|
CreateGic(HypervisorVmError),
|
||||||
/// Error while setting device attributes for the GIC.
|
/// Error while setting device attributes for the GIC.
|
||||||
|
#[error("Failed setting device attributes for the GIC: {0}")]
|
||||||
SetDeviceAttribute(HypervisorDeviceError),
|
SetDeviceAttribute(HypervisorDeviceError),
|
||||||
/// Error while getting device attributes for the GIC.
|
/// Error while getting device attributes for the GIC.
|
||||||
|
#[error("Failed getting device attributes for the GIC: {0}")]
|
||||||
GetDeviceAttribute(HypervisorDeviceError),
|
GetDeviceAttribute(HypervisorDeviceError),
|
||||||
}
|
}
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
|
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
pub mod gic;
|
pub mod gic;
|
||||||
|
|||||||
@@ -137,14 +137,6 @@ pub trait PlatformEmulator {
|
|||||||
///
|
///
|
||||||
fn set_cpu_state(&self, cpu_id: usize, state: Self::CpuState) -> Result<(), PlatformError>;
|
fn set_cpu_state(&self, cpu_id: usize, state: Self::CpuState) -> Result<(), PlatformError>;
|
||||||
|
|
||||||
/// Translate a guest virtual address into a physical one
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `gva` - Guest virtual address to translate.
|
|
||||||
///
|
|
||||||
fn gva_to_gpa(&self, gva: u64) -> Result<u64, PlatformError>;
|
|
||||||
|
|
||||||
/// Fetch instruction bytes from memory.
|
/// Fetch instruction bytes from memory.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
|||||||
@@ -10,10 +10,8 @@
|
|||||||
// CMP-Compare Two Operands
|
// CMP-Compare Two Operands
|
||||||
//
|
//
|
||||||
|
|
||||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
|
||||||
use crate::arch::x86::emulator::instructions::*;
|
use crate::arch::x86::emulator::instructions::*;
|
||||||
use crate::arch::x86::regs::*;
|
use crate::arch::x86::regs::*;
|
||||||
use crate::arch::x86::Exception;
|
|
||||||
|
|
||||||
// CMP affects OF, SF, ZF, AF, PF and CF
|
// CMP affects OF, SF, ZF, AF, PF and CF
|
||||||
const FLAGS_MASK: u64 = CF | PF | AF | ZF | SF | OF;
|
const FLAGS_MASK: u64 = CF | PF | AF | ZF | SF | OF;
|
||||||
@@ -211,8 +209,6 @@ impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_imm8 {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#![allow(unused_mut)]
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::arch::x86::emulator::mock_vmm::*;
|
use crate::arch::x86::emulator::mock_vmm::*;
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub mod cmp;
|
|||||||
pub mod mov;
|
pub mod mov;
|
||||||
pub mod movs;
|
pub mod movs;
|
||||||
pub mod or;
|
pub mod or;
|
||||||
|
pub mod stos;
|
||||||
|
|
||||||
fn get_op<T: CpuStateManager>(
|
fn get_op<T: CpuStateManager>(
|
||||||
insn: &Instruction,
|
insn: &Instruction,
|
||||||
|
|||||||
@@ -12,9 +12,7 @@
|
|||||||
// Copies the second operand (source operand) to the first operand (destination operand).
|
// Copies the second operand (source operand) to the first operand (destination operand).
|
||||||
//
|
//
|
||||||
|
|
||||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
|
||||||
use crate::arch::x86::emulator::instructions::*;
|
use crate::arch::x86::emulator::instructions::*;
|
||||||
use crate::arch::x86::Exception;
|
|
||||||
|
|
||||||
macro_rules! mov_rm_r {
|
macro_rules! mov_rm_r {
|
||||||
($bound:ty) => {
|
($bound:ty) => {
|
||||||
@@ -271,7 +269,6 @@ impl<T: CpuStateManager> InstructionHandler<T> for Mov_RAX_moffs64 {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#![allow(unused_mut)]
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::arch::x86::emulator::mock_vmm::*;
|
use crate::arch::x86::emulator::mock_vmm::*;
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,8 @@
|
|||||||
// MOVS - Move Data from String to String
|
// MOVS - Move Data from String to String
|
||||||
//
|
//
|
||||||
|
|
||||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
|
||||||
use crate::arch::x86::emulator::instructions::*;
|
use crate::arch::x86::emulator::instructions::*;
|
||||||
use crate::arch::x86::regs::DF;
|
use crate::arch::x86::regs::DF;
|
||||||
use crate::arch::x86::Exception;
|
|
||||||
|
|
||||||
macro_rules! movs {
|
macro_rules! movs {
|
||||||
($bound:ty) => {
|
($bound:ty) => {
|
||||||
@@ -42,7 +40,7 @@ macro_rules! movs {
|
|||||||
let len = std::mem::size_of::<$bound>();
|
let len = std::mem::size_of::<$bound>();
|
||||||
|
|
||||||
while count > 0 {
|
while count > 0 {
|
||||||
let mut memory: [u8; 4] = [0; 4];
|
let mut memory: [u8; 8] = [0; 8];
|
||||||
|
|
||||||
let src = state
|
let src = state
|
||||||
.linearize(Register::DS, rsi, false)
|
.linearize(Register::DS, rsi, false)
|
||||||
@@ -85,6 +83,11 @@ macro_rules! movs {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Movsq_m64_m64;
|
||||||
|
impl<T: CpuStateManager> InstructionHandler<T> for Movsq_m64_m64 {
|
||||||
|
movs!(u64);
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Movsd_m32_m32;
|
pub struct Movsd_m32_m32;
|
||||||
impl<T: CpuStateManager> InstructionHandler<T> for Movsd_m32_m32 {
|
impl<T: CpuStateManager> InstructionHandler<T> for Movsd_m32_m32 {
|
||||||
movs!(u32);
|
movs!(u32);
|
||||||
@@ -102,10 +105,43 @@ impl<T: CpuStateManager> InstructionHandler<T> for Movsb_m8_m8 {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#![allow(unused_mut)]
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::arch::x86::emulator::mock_vmm::*;
|
use crate::arch::x86::emulator::mock_vmm::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rep_movsq_m64_m64() {
|
||||||
|
let ip: u64 = 0x1000;
|
||||||
|
let memory: [u8; 32] = [
|
||||||
|
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||||
|
0xdd, 0xcc, 0xbb, 0xaa, // 0xaabbccdd
|
||||||
|
0xa5, 0x5a, 0xa5, 0x5a, // 0x5aa55aa5
|
||||||
|
0xcd, 0xcd, 0xcd, 0xcd, // 0xcdcdcdcd
|
||||||
|
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||||
|
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||||
|
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||||
|
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||||
|
];
|
||||||
|
let insn = [0xf3, 0x48, 0xa5]; // rep movsq
|
||||||
|
let regs = vec![
|
||||||
|
(Register::ECX, 2),
|
||||||
|
(Register::ESI, 0),
|
||||||
|
(Register::EDI, 0x10),
|
||||||
|
];
|
||||||
|
let mut data = [0u8; 8];
|
||||||
|
|
||||||
|
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||||
|
|
||||||
|
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||||
|
|
||||||
|
vmm.read_memory(0x10, &mut data).unwrap();
|
||||||
|
assert_eq!(0xaabbccdd12345678, <u64>::from_le_bytes(data));
|
||||||
|
vmm.read_memory(0x18, &mut data).unwrap();
|
||||||
|
assert_eq!(0xcdcdcdcd5aa55aa5, <u64>::from_le_bytes(data));
|
||||||
|
// The rest should be default value 0 from MockVmm
|
||||||
|
vmm.read_memory(0x20, &mut data).unwrap();
|
||||||
|
assert_eq!(0x0, <u64>::from_le_bytes(data));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rep_movsd_m32_m32() {
|
fn test_rep_movsd_m32_m32() {
|
||||||
let ip: u64 = 0x1000;
|
let ip: u64 = 0x1000;
|
||||||
|
|||||||
@@ -10,9 +10,7 @@
|
|||||||
// OR - Logical inclusive OR
|
// OR - Logical inclusive OR
|
||||||
//
|
//
|
||||||
|
|
||||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
|
||||||
use crate::arch::x86::emulator::instructions::*;
|
use crate::arch::x86::emulator::instructions::*;
|
||||||
use crate::arch::x86::Exception;
|
|
||||||
|
|
||||||
macro_rules! or_rm_r {
|
macro_rules! or_rm_r {
|
||||||
($bound:ty) => {
|
($bound:ty) => {
|
||||||
@@ -52,7 +50,6 @@ impl<T: CpuStateManager> InstructionHandler<T> for Or_rm8_r8 {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#![allow(unused_mut)]
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::arch::x86::emulator::mock_vmm::*;
|
use crate::arch::x86::emulator::mock_vmm::*;
|
||||||
|
|||||||
236
hypervisor/src/arch/x86/emulator/instructions/stos.rs
Normal file
236
hypervisor/src/arch/x86/emulator/instructions/stos.rs
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2024 Microsoft
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
|
||||||
|
//
|
||||||
|
// STOS - Store String
|
||||||
|
//
|
||||||
|
|
||||||
|
use crate::arch::x86::emulator::instructions::*;
|
||||||
|
use crate::arch::x86::regs::DF;
|
||||||
|
|
||||||
|
macro_rules! stos {
|
||||||
|
($bound:ty) => {
|
||||||
|
fn emulate(
|
||||||
|
&self,
|
||||||
|
insn: &Instruction,
|
||||||
|
state: &mut T,
|
||||||
|
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||||
|
) -> Result<(), EmulationError<Exception>> {
|
||||||
|
let mut count: u64 = if insn.has_rep_prefix() {
|
||||||
|
state
|
||||||
|
.read_reg(Register::ECX)
|
||||||
|
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
|
||||||
|
let rax = state
|
||||||
|
.read_reg(Register::RAX)
|
||||||
|
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||||
|
|
||||||
|
let mut rdi = state
|
||||||
|
.read_reg(Register::RDI)
|
||||||
|
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||||
|
|
||||||
|
let df = (state.flags() & DF) != 0;
|
||||||
|
let len = std::mem::size_of::<$bound>();
|
||||||
|
let rax_bytes = rax.to_le_bytes();
|
||||||
|
|
||||||
|
while count > 0 {
|
||||||
|
let dst = state
|
||||||
|
.linearize(Register::ES, rdi, true)
|
||||||
|
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||||
|
|
||||||
|
platform
|
||||||
|
.write_memory(dst, &rax_bytes[0..len])
|
||||||
|
.map_err(EmulationError::PlatformEmulationError)?;
|
||||||
|
|
||||||
|
if df {
|
||||||
|
rdi = rdi.wrapping_sub(len as u64);
|
||||||
|
} else {
|
||||||
|
rdi = rdi.wrapping_add(len as u64);
|
||||||
|
}
|
||||||
|
count -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if insn.has_rep_prefix() {
|
||||||
|
state
|
||||||
|
.write_reg(Register::ECX, 0)
|
||||||
|
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Stosq_m64_RAX;
|
||||||
|
impl<T: CpuStateManager> InstructionHandler<T> for Stosq_m64_RAX {
|
||||||
|
stos!(u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Stosd_m32_EAX;
|
||||||
|
impl<T: CpuStateManager> InstructionHandler<T> for Stosd_m32_EAX {
|
||||||
|
stos!(u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Stosw_m16_AX;
|
||||||
|
impl<T: CpuStateManager> InstructionHandler<T> for Stosw_m16_AX {
|
||||||
|
stos!(u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Stosb_m8_AL;
|
||||||
|
impl<T: CpuStateManager> InstructionHandler<T> for Stosb_m8_AL {
|
||||||
|
stos!(u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::arch::x86::emulator::mock_vmm::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rep_stosb() {
|
||||||
|
let ip: u64 = 0x1000;
|
||||||
|
let memory: [u8; 12] = [
|
||||||
|
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||||
|
0xdd, 0xcc, 0xbb, 0xaa, // 0xaabbccdd
|
||||||
|
0xa5, 0x5a, 0xa5, 0x5a, // 0x5aa55aa5
|
||||||
|
];
|
||||||
|
let insn = [0xf3, 0xaa]; // rep stosb
|
||||||
|
let regs = vec![
|
||||||
|
(Register::ECX, 3),
|
||||||
|
(Register::EDI, 0x0),
|
||||||
|
(Register::RAX, 0x123456ff),
|
||||||
|
];
|
||||||
|
let mut data = [0u8; 4];
|
||||||
|
|
||||||
|
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||||
|
|
||||||
|
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||||
|
|
||||||
|
vmm.read_memory(0, &mut data).unwrap();
|
||||||
|
assert_eq!(0x12ffffff, <u32>::from_le_bytes(data));
|
||||||
|
vmm.read_memory(4, &mut data).unwrap();
|
||||||
|
assert_eq!(0xaabbccdd, <u32>::from_le_bytes(data));
|
||||||
|
vmm.read_memory(8, &mut data).unwrap();
|
||||||
|
assert_eq!(0x5aa55aa5, <u32>::from_le_bytes(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stosw() {
|
||||||
|
let ip: u64 = 0x1000;
|
||||||
|
let memory: [u8; 4] = [
|
||||||
|
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||||
|
];
|
||||||
|
let insn = [0x66, 0xab]; // stosw
|
||||||
|
let regs = vec![(Register::EDI, 0x1), (Register::AX, 0xaabb)];
|
||||||
|
let mut data = [0u8; 4];
|
||||||
|
|
||||||
|
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||||
|
|
||||||
|
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||||
|
|
||||||
|
vmm.read_memory(0x0, &mut data).unwrap();
|
||||||
|
assert_eq!(0x12aabb78, <u32>::from_le_bytes(data));
|
||||||
|
// The rest should be default value 0 from MockVmm
|
||||||
|
vmm.read_memory(0x4, &mut data).unwrap();
|
||||||
|
assert_eq!(0x0, <u32>::from_le_bytes(data));
|
||||||
|
vmm.read_memory(0x8 + 8, &mut data).unwrap();
|
||||||
|
assert_eq!(0x0, <u32>::from_le_bytes(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rep_stosw() {
|
||||||
|
let ip: u64 = 0x1000;
|
||||||
|
let memory: [u8; 8] = [
|
||||||
|
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||||
|
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||||
|
];
|
||||||
|
let insn = [0x66, 0xf3, 0xab]; // rep stosw
|
||||||
|
let regs = vec![
|
||||||
|
(Register::ECX, 2),
|
||||||
|
(Register::EDI, 0x2),
|
||||||
|
(Register::AX, 0xaabb),
|
||||||
|
];
|
||||||
|
let mut data = [0u8; 4];
|
||||||
|
|
||||||
|
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||||
|
|
||||||
|
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||||
|
|
||||||
|
vmm.read_memory(0x0, &mut data).unwrap();
|
||||||
|
assert_eq!(0xaabb5678, <u32>::from_le_bytes(data));
|
||||||
|
vmm.read_memory(0x4, &mut data).unwrap();
|
||||||
|
assert_eq!(0x0000aabb, <u32>::from_le_bytes(data));
|
||||||
|
vmm.read_memory(0x8, &mut data).unwrap();
|
||||||
|
assert_eq!(0x0, <u32>::from_le_bytes(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rep_stosd() {
|
||||||
|
let ip: u64 = 0x1000;
|
||||||
|
let memory: [u8; 12] = [
|
||||||
|
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||||
|
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||||
|
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||||
|
];
|
||||||
|
let insn = [0xf3, 0xab]; // rep stosd
|
||||||
|
let regs = vec![
|
||||||
|
(Register::ECX, 2),
|
||||||
|
(Register::EDI, 0x8),
|
||||||
|
(Register::EAX, 0xaabbccdd),
|
||||||
|
];
|
||||||
|
let mut data = [0u8; 4];
|
||||||
|
|
||||||
|
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||||
|
|
||||||
|
// Go backwards this time
|
||||||
|
let mut state = vmm.cpu_state(0).unwrap();
|
||||||
|
state.set_flags(state.flags() | DF);
|
||||||
|
vmm.set_cpu_state(0, state).unwrap();
|
||||||
|
|
||||||
|
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||||
|
|
||||||
|
vmm.read_memory(0x0, &mut data).unwrap();
|
||||||
|
assert_eq!(0x12345678, <u32>::from_le_bytes(data));
|
||||||
|
vmm.read_memory(0x4, &mut data).unwrap();
|
||||||
|
assert_eq!(0xaabbccdd, <u32>::from_le_bytes(data));
|
||||||
|
vmm.read_memory(0x8, &mut data).unwrap();
|
||||||
|
assert_eq!(0xaabbccdd, <u32>::from_le_bytes(data));
|
||||||
|
vmm.read_memory(0xc, &mut data).unwrap();
|
||||||
|
assert_eq!(0x0, <u32>::from_le_bytes(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rep_stosq() {
|
||||||
|
let ip: u64 = 0x1000;
|
||||||
|
let memory: [u8; 8] = [
|
||||||
|
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||||
|
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||||
|
];
|
||||||
|
let insn = [0xf3, 0x48, 0xab]; // rep stosq
|
||||||
|
let regs = vec![
|
||||||
|
(Register::ECX, 2),
|
||||||
|
(Register::RDI, 0x0),
|
||||||
|
(Register::RAX, 0x11223344aabbccdd),
|
||||||
|
];
|
||||||
|
let mut data = [0u8; 8];
|
||||||
|
|
||||||
|
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
|
||||||
|
|
||||||
|
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||||
|
|
||||||
|
vmm.read_memory(0x0, &mut data).unwrap();
|
||||||
|
assert_eq!(0x11223344aabbccdd, <u64>::from_le_bytes(data));
|
||||||
|
vmm.read_memory(0x8, &mut data).unwrap();
|
||||||
|
assert_eq!(0x11223344aabbccdd, <u64>::from_le_bytes(data));
|
||||||
|
vmm.read_memory(0x10, &mut data).unwrap();
|
||||||
|
assert_eq!(0x0, <u64>::from_le_bytes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -135,7 +135,7 @@ pub trait CpuStateManager: Clone {
|
|||||||
.checked_add(segment_register.base)
|
.checked_add(segment_register.base)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
PlatformError::InvalidAddress(anyhow!(
|
PlatformError::InvalidAddress(anyhow!(
|
||||||
"Logical address {:#x} can not be linearized with segment {:#x?}",
|
"Logical address {:#x} cannot be linearized with segment {:#x?}",
|
||||||
logical_addr,
|
logical_addr,
|
||||||
segment_register
|
segment_register
|
||||||
))
|
))
|
||||||
@@ -148,7 +148,7 @@ pub trait CpuStateManager: Clone {
|
|||||||
// Must not write to a read-only segment.
|
// Must not write to a read-only segment.
|
||||||
if segment_type_ro(segment_type) && write {
|
if segment_type_ro(segment_type) && write {
|
||||||
return Err(PlatformError::InvalidAddress(anyhow!(
|
return Err(PlatformError::InvalidAddress(anyhow!(
|
||||||
"Can not write to a read-only segment"
|
"Cannot write to a read-only segment"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,11 +532,17 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
|||||||
(mov, Mov_moffs64_RAX),
|
(mov, Mov_moffs64_RAX),
|
||||||
(mov, Mov_RAX_moffs64),
|
(mov, Mov_RAX_moffs64),
|
||||||
// MOVS
|
// MOVS
|
||||||
|
(movs, Movsq_m64_m64),
|
||||||
(movs, Movsd_m32_m32),
|
(movs, Movsd_m32_m32),
|
||||||
(movs, Movsw_m16_m16),
|
(movs, Movsw_m16_m16),
|
||||||
(movs, Movsb_m8_m8),
|
(movs, Movsb_m8_m8),
|
||||||
// OR
|
// OR
|
||||||
(or, Or_rm8_r8)
|
(or, Or_rm8_r8),
|
||||||
|
// STOS
|
||||||
|
(stos, Stosb_m8_AL),
|
||||||
|
(stos, Stosw_m16_AX),
|
||||||
|
(stos, Stosd_m32_EAX),
|
||||||
|
(stos, Stosq_m64_RAX)
|
||||||
);
|
);
|
||||||
|
|
||||||
handler
|
handler
|
||||||
@@ -561,7 +567,7 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
|||||||
|
|
||||||
decoder.set_ip(state.ip());
|
decoder.set_ip(state.ip());
|
||||||
|
|
||||||
while decoder.can_decode() && !stop_emulation {
|
while !stop_emulation {
|
||||||
decoder.decode_out(&mut insn);
|
decoder.decode_out(&mut insn);
|
||||||
|
|
||||||
if decoder.last_error() == DecoderError::NoMoreBytes {
|
if decoder.last_error() == DecoderError::NoMoreBytes {
|
||||||
@@ -588,6 +594,7 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
|||||||
// Once we have the new stream, we must create a new decoder
|
// Once we have the new stream, we must create a new decoder
|
||||||
// and emulate one last instruction from the last decoded IP.
|
// and emulate one last instruction from the last decoded IP.
|
||||||
decoder = Decoder::new(64, &fetched_insn_stream, DecoderOptions::NONE);
|
decoder = Decoder::new(64, &fetched_insn_stream, DecoderOptions::NONE);
|
||||||
|
decoder.set_ip(last_decoded_ip);
|
||||||
decoder.decode_out(&mut insn);
|
decoder.decode_out(&mut insn);
|
||||||
if decoder.last_error() != DecoderError::None {
|
if decoder.last_error() != DecoderError::None {
|
||||||
return Err(EmulationError::InstructionFetchingError(anyhow!(
|
return Err(EmulationError::InstructionFetchingError(anyhow!(
|
||||||
@@ -595,8 +602,6 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
|||||||
insn_format!(insn)
|
insn_format!(insn)
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
stop_emulation = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emulate the decoded instruction
|
// Emulate the decoded instruction
|
||||||
@@ -648,13 +653,9 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod mock_vmm {
|
mod mock_vmm {
|
||||||
#![allow(unused_mut)]
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
use crate::arch::x86::emulator::EmulatorCpuState as CpuState;
|
||||||
use crate::arch::x86::emulator::{Emulator, EmulatorCpuState as CpuState};
|
|
||||||
use crate::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
use crate::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
||||||
use crate::arch::x86::Exception;
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -755,10 +756,6 @@ mod mock_vmm {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gva_to_gpa(&self, gva: u64) -> Result<u64, PlatformError> {
|
|
||||||
Ok(gva)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fetch(&self, ip: u64, instruction_bytes: &mut [u8]) -> Result<(), PlatformError> {
|
fn fetch(&self, ip: u64, instruction_bytes: &mut [u8]) -> Result<(), PlatformError> {
|
||||||
let rip = self
|
let rip = self
|
||||||
.state
|
.state
|
||||||
@@ -772,16 +769,80 @@ mod mock_vmm {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#![allow(unused_mut)]
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::arch::x86::emulator::mock_vmm::*;
|
use crate::arch::x86::emulator::mock_vmm::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Emulate executing an empty stream. Instructions should be fetched from
|
||||||
|
// memory.
|
||||||
|
//
|
||||||
|
// mov rax, 0x1000
|
||||||
|
// mov rbx, qword ptr [rax+10h]
|
||||||
|
fn test_empty_instruction_stream() {
|
||||||
|
let target_rax: u64 = 0x1000;
|
||||||
|
let target_rbx: u64 = 0x1234567812345678;
|
||||||
|
let ip: u64 = 0x1000;
|
||||||
|
let cpu_id = 0;
|
||||||
|
let memory = [
|
||||||
|
// Code at IP
|
||||||
|
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||||
|
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||||
|
// Padding
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, // Padding is all zeroes
|
||||||
|
// Data at IP + 0x10 (0x1234567812345678 in LE)
|
||||||
|
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut vmm = MockVmm::new(ip, vec![], Some((ip, &memory)));
|
||||||
|
assert!(vmm.emulate_insn(cpu_id, &[], Some(2)).is_ok());
|
||||||
|
|
||||||
|
let rax: u64 = vmm
|
||||||
|
.cpu_state(cpu_id)
|
||||||
|
.unwrap()
|
||||||
|
.read_reg(Register::RAX)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(rax, target_rax);
|
||||||
|
|
||||||
|
let rbx: u64 = vmm
|
||||||
|
.cpu_state(cpu_id)
|
||||||
|
.unwrap()
|
||||||
|
.read_reg(Register::RBX)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(rbx, target_rbx);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Emulate executing an empty stream. Instructions should be fetched from
|
||||||
|
// memory. The emulation should abort.
|
||||||
|
//
|
||||||
|
// mov rax, 0x1000
|
||||||
|
// mov rbx, qword ptr [rax+10h]
|
||||||
|
// ... garbage ...
|
||||||
|
fn test_empty_instruction_stream_bad() {
|
||||||
|
let ip: u64 = 0x1000;
|
||||||
|
let cpu_id = 0;
|
||||||
|
let memory = [
|
||||||
|
// Code at IP
|
||||||
|
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||||
|
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||||
|
// Padding
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, // Garbage
|
||||||
|
// Data at IP + 0x10 (0x1234567812345678 in LE)
|
||||||
|
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut vmm = MockVmm::new(ip, vec![], Some((ip, &memory)));
|
||||||
|
assert!(vmm.emulate_insn(cpu_id, &[], None).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
// Emulate truncated instruction stream, which should cause a fetch.
|
// Emulate truncated instruction stream, which should cause a fetch.
|
||||||
//
|
//
|
||||||
// mov rax, 0x1000
|
// mov rax, 0x1000
|
||||||
|
// mov rbx, qword ptr [rax+10h]
|
||||||
// Test with a first instruction truncated.
|
// Test with a first instruction truncated.
|
||||||
fn test_fetch_first_instruction() {
|
fn test_fetch_first_instruction() {
|
||||||
|
let target_rax: u64 = 0x1000;
|
||||||
let ip: u64 = 0x1000;
|
let ip: u64 = 0x1000;
|
||||||
let cpu_id = 0;
|
let cpu_id = 0;
|
||||||
let memory = [
|
let memory = [
|
||||||
@@ -806,7 +867,7 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.read_reg(Register::RAX)
|
.read_reg(Register::RAX)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(rax, ip);
|
assert_eq!(rax, target_rax);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -844,6 +905,52 @@ mod tests {
|
|||||||
assert_eq!(rbx, target_rax);
|
assert_eq!(rbx, target_rax);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Emulate only one instruction.
|
||||||
|
//
|
||||||
|
// mov rax, 0x1000
|
||||||
|
// mov rbx, qword ptr [rax+10h]
|
||||||
|
// The emulation should stop after the first instruction.
|
||||||
|
fn test_emulate_one_instruction() {
|
||||||
|
let target_rax: u64 = 0x1000;
|
||||||
|
let ip: u64 = 0x1000;
|
||||||
|
let cpu_id = 0;
|
||||||
|
let memory = [
|
||||||
|
// Code at IP
|
||||||
|
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||||
|
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||||
|
// Padding
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, // Padding is all zeroes
|
||||||
|
// Data at IP + 0x10 (0x1234567812345678 in LE)
|
||||||
|
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
|
||||||
|
];
|
||||||
|
let insn = [
|
||||||
|
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||||
|
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut vmm = MockVmm::new(ip, vec![], Some((ip, &memory)));
|
||||||
|
assert!(vmm.emulate_insn(cpu_id, &insn, Some(1)).is_ok());
|
||||||
|
|
||||||
|
let new_ip: u64 = vmm.cpu_state(cpu_id).unwrap().ip();
|
||||||
|
assert_eq!(new_ip, ip + 0x7 /* length of mov rax,0x1000 */);
|
||||||
|
|
||||||
|
let rax: u64 = vmm
|
||||||
|
.cpu_state(cpu_id)
|
||||||
|
.unwrap()
|
||||||
|
.read_reg(Register::RAX)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(rax, target_rax);
|
||||||
|
|
||||||
|
// The second instruction is not executed so RBX should be zero.
|
||||||
|
let rbx: u64 = vmm
|
||||||
|
.cpu_state(cpu_id)
|
||||||
|
.unwrap()
|
||||||
|
.read_reg(Register::RBX)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(rbx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
// Emulate truncated instruction stream, which should cause a fetch.
|
// Emulate truncated instruction stream, which should cause a fetch.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ pub enum Exception {
|
|||||||
pub mod regs;
|
pub mod regs;
|
||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "with-serde", derive(Deserialize, Serialize))]
|
|
||||||
pub struct SegmentRegister {
|
pub struct SegmentRegister {
|
||||||
pub base: u64,
|
pub base: u64,
|
||||||
pub limit: u32,
|
pub limit: u32,
|
||||||
@@ -174,7 +173,6 @@ macro_rules! msr_data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "with-serde", derive(Deserialize, Serialize))]
|
|
||||||
pub struct StandardRegisters {
|
pub struct StandardRegisters {
|
||||||
pub rax: u64,
|
pub rax: u64,
|
||||||
pub rbx: u64,
|
pub rbx: u64,
|
||||||
@@ -197,14 +195,12 @@ pub struct StandardRegisters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "with-serde", derive(Deserialize, Serialize))]
|
|
||||||
pub struct DescriptorTable {
|
pub struct DescriptorTable {
|
||||||
pub base: u64,
|
pub base: u64,
|
||||||
pub limit: u16,
|
pub limit: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "with-serde", derive(Deserialize, Serialize))]
|
|
||||||
pub struct SpecialRegisters {
|
pub struct SpecialRegisters {
|
||||||
pub cs: SegmentRegister,
|
pub cs: SegmentRegister,
|
||||||
pub ds: SegmentRegister,
|
pub ds: SegmentRegister,
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
|
|||||||
@@ -107,14 +107,28 @@ pub enum HypervisorCpuError {
|
|||||||
///
|
///
|
||||||
/// Setting Saved Processor Extended States error
|
/// Setting Saved Processor Extended States error
|
||||||
///
|
///
|
||||||
|
#[cfg(feature = "kvm")]
|
||||||
#[error("Failed to set Saved Processor Extended States: {0}")]
|
#[error("Failed to set Saved Processor Extended States: {0}")]
|
||||||
SetXsaveState(#[source] anyhow::Error),
|
SetXsaveState(#[source] anyhow::Error),
|
||||||
///
|
///
|
||||||
/// Getting Saved Processor Extended States error
|
/// Getting Saved Processor Extended States error
|
||||||
///
|
///
|
||||||
|
#[cfg(feature = "kvm")]
|
||||||
#[error("Failed to get Saved Processor Extended States: {0}")]
|
#[error("Failed to get Saved Processor Extended States: {0}")]
|
||||||
GetXsaveState(#[source] anyhow::Error),
|
GetXsaveState(#[source] anyhow::Error),
|
||||||
///
|
///
|
||||||
|
/// Getting the VP state components error
|
||||||
|
///
|
||||||
|
#[cfg(feature = "mshv")]
|
||||||
|
#[error("Failed to get VP State Components: {0}")]
|
||||||
|
GetAllVpStateComponents(#[source] anyhow::Error),
|
||||||
|
///
|
||||||
|
/// Setting the VP state components error
|
||||||
|
///
|
||||||
|
#[cfg(feature = "mshv")]
|
||||||
|
#[error("Failed to set VP State Components: {0}")]
|
||||||
|
SetAllVpStateComponents(#[source] anyhow::Error),
|
||||||
|
///
|
||||||
/// Setting Extended Control Registers error
|
/// Setting Extended Control Registers error
|
||||||
///
|
///
|
||||||
#[error("Failed to set Extended Control Registers: {0}")]
|
#[error("Failed to set Extended Control Registers: {0}")]
|
||||||
@@ -278,18 +292,17 @@ pub enum HypervisorCpuError {
|
|||||||
#[cfg(feature = "sev_snp")]
|
#[cfg(feature = "sev_snp")]
|
||||||
#[error("Failed to set sev control register: {0}")]
|
#[error("Failed to set sev control register: {0}")]
|
||||||
SetSevControlRegister(#[source] anyhow::Error),
|
SetSevControlRegister(#[source] anyhow::Error),
|
||||||
|
|
||||||
|
/// Error injecting NMI
|
||||||
|
///
|
||||||
|
#[error("Failed to inject NMI")]
|
||||||
|
Nmi(#[source] anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum VmExit<'a> {
|
pub enum VmExit {
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
IoOut(u16 /* port */, &'a [u8] /* data */),
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
IoIn(u16 /* port */, &'a mut [u8] /* data */),
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
IoapicEoi(u8 /* vector */),
|
IoapicEoi(u8 /* vector */),
|
||||||
MmioRead(u64 /* address */, &'a mut [u8]),
|
|
||||||
MmioWrite(u64 /* address */, &'a [u8]),
|
|
||||||
Ignore,
|
Ignore,
|
||||||
Reset,
|
Reset,
|
||||||
Shutdown,
|
Shutdown,
|
||||||
@@ -505,4 +518,10 @@ pub trait Vcpu: Send + Sync {
|
|||||||
fn set_sev_control_register(&self, _reg: u64) -> Result<()> {
|
fn set_sev_control_register(&self, _reg: u64) -> Result<()> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
///
|
||||||
|
/// Trigger NMI interrupt
|
||||||
|
///
|
||||||
|
fn nmi(&self) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ use std::sync::Arc;
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
///
|
|
||||||
///
|
|
||||||
pub enum HypervisorError {
|
pub enum HypervisorError {
|
||||||
///
|
///
|
||||||
/// Hypervisor availability check error
|
/// Hypervisor availability check error
|
||||||
@@ -168,7 +166,7 @@ pub trait Hypervisor: Send + Sync {
|
|||||||
CpuVendor::AMD
|
CpuVendor::AMD
|
||||||
} else {
|
} else {
|
||||||
// Not known yet, the corresponding manufacturer manual should contain the
|
// Not known yet, the corresponding manufacturer manual should contain the
|
||||||
// necesssary info. See also https://wiki.osdev.org/CPUID#CPU_Vendor_ID_String
|
// necessary info. See also https://wiki.osdev.org/CPUID#CPU_Vendor_ID_String
|
||||||
CpuVendor::default()
|
CpuVendor::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,46 +77,61 @@ static VGIC_DIST_REGS: &[DistReg] = &[
|
|||||||
VGIC_DIST_REG!(GICD_IPRIORITYR, 8, 0),
|
VGIC_DIST_REG!(GICD_IPRIORITYR, 8, 0),
|
||||||
];
|
];
|
||||||
|
|
||||||
fn dist_attr_access(gic: &DeviceFd, offset: u32, val: &u32, set: bool) -> Result<()> {
|
fn dist_attr_set(gic: &DeviceFd, offset: u32, val: u32) -> Result<()> {
|
||||||
|
let gic_dist_attr = kvm_device_attr {
|
||||||
|
group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
|
||||||
|
attr: offset as u64,
|
||||||
|
addr: &val as *const u32 as u64,
|
||||||
|
flags: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
gic.set_device_attr(&gic_dist_attr).map_err(|e| {
|
||||||
|
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dist_attr_get(gic: &DeviceFd, offset: u32) -> Result<u32> {
|
||||||
|
let mut val = 0;
|
||||||
|
|
||||||
let mut gic_dist_attr = kvm_device_attr {
|
let mut gic_dist_attr = kvm_device_attr {
|
||||||
group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
|
group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
|
||||||
attr: offset as u64,
|
attr: offset as u64,
|
||||||
addr: val as *const u32 as u64,
|
addr: &mut val as *mut u32 as u64,
|
||||||
flags: 0,
|
flags: 0,
|
||||||
};
|
};
|
||||||
if set {
|
|
||||||
gic.set_device_attr(&gic_dist_attr).map_err(|e| {
|
// get_device_attr should be marked as unsafe, and will be in future.
|
||||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
// SAFETY: gic_dist_attr.addr is safe to write to.
|
||||||
})?;
|
gic.get_device_attr(&mut gic_dist_attr).map_err(|e| {
|
||||||
} else {
|
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||||
gic.get_device_attr(&mut gic_dist_attr).map_err(|e| {
|
})?;
|
||||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
|
||||||
})?;
|
Ok(val)
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the distributor control register.
|
/// Get the distributor control register.
|
||||||
pub fn read_ctlr(gic: &DeviceFd) -> Result<u32> {
|
pub fn read_ctlr(gic: &DeviceFd) -> Result<u32> {
|
||||||
let val: u32 = 0;
|
dist_attr_get(gic, GICD_CTLR)
|
||||||
dist_attr_access(gic, GICD_CTLR, &val, false)?;
|
|
||||||
Ok(val)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the distributor control register.
|
/// Set the distributor control register.
|
||||||
pub fn write_ctlr(gic: &DeviceFd, val: u32) -> Result<()> {
|
pub fn write_ctlr(gic: &DeviceFd, val: u32) -> Result<()> {
|
||||||
dist_attr_access(gic, GICD_CTLR, &val, true)
|
dist_attr_set(gic, GICD_CTLR, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_interrupts_num(gic: &DeviceFd) -> Result<u32> {
|
fn get_interrupts_num(gic: &DeviceFd) -> Result<u32> {
|
||||||
let num_irq = 0;
|
let mut num_irq = 0;
|
||||||
|
|
||||||
let mut nr_irqs_attr = kvm_device_attr {
|
let mut nr_irqs_attr = kvm_device_attr {
|
||||||
group: KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
|
group: KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
|
||||||
attr: 0,
|
attr: 0,
|
||||||
addr: &num_irq as *const u32 as u64,
|
addr: &mut num_irq as *mut u32 as u64,
|
||||||
flags: 0,
|
flags: 0,
|
||||||
};
|
};
|
||||||
|
// get_device_attr should be marked as unsafe, and will be in future.
|
||||||
|
// SAFETY: nr_irqs_attr.addr is safe to write to.
|
||||||
gic.get_device_attr(&mut nr_irqs_attr).map_err(|e| {
|
gic.get_device_attr(&mut nr_irqs_attr).map_err(|e| {
|
||||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||||
})?;
|
})?;
|
||||||
@@ -158,8 +173,7 @@ pub fn set_dist_regs(gic: &DeviceFd, state: &[u32]) -> Result<()> {
|
|||||||
let end = compute_reg_len(gic, dreg, base)?;
|
let end = compute_reg_len(gic, dreg, base)?;
|
||||||
|
|
||||||
while base < end {
|
while base < end {
|
||||||
let val = state[idx];
|
dist_attr_set(gic, base, state[idx])?;
|
||||||
dist_attr_access(gic, base, &val, true)?;
|
|
||||||
idx += 1;
|
idx += 1;
|
||||||
base += REG_SIZE as u32;
|
base += REG_SIZE as u32;
|
||||||
}
|
}
|
||||||
@@ -175,9 +189,7 @@ pub fn get_dist_regs(gic: &DeviceFd) -> Result<Vec<u32>> {
|
|||||||
let end = compute_reg_len(gic, dreg, base)?;
|
let end = compute_reg_len(gic, dreg, base)?;
|
||||||
|
|
||||||
while base < end {
|
while base < end {
|
||||||
let val: u32 = 0;
|
state.push(dist_attr_get(gic, base)?);
|
||||||
dist_attr_access(gic, base, &val, false)?;
|
|
||||||
state.push(val);
|
|
||||||
base += REG_SIZE as u32;
|
base += REG_SIZE as u32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,23 +79,38 @@ static VGIC_ICC_REGS: &[u64] = &[
|
|||||||
SYS_ICC_AP1R3_EL1,
|
SYS_ICC_AP1R3_EL1,
|
||||||
];
|
];
|
||||||
|
|
||||||
fn icc_attr_access(gic: &DeviceFd, offset: u64, typer: u64, val: &u32, set: bool) -> Result<()> {
|
fn icc_attr_set(gic: &DeviceFd, offset: u64, typer: u64, val: u32) -> Result<()> {
|
||||||
|
let gic_icc_attr = kvm_device_attr {
|
||||||
|
group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
|
||||||
|
attr: ((typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | offset), // this needs the mpidr
|
||||||
|
addr: &val as *const u32 as u64,
|
||||||
|
flags: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
gic.set_device_attr(&gic_icc_attr).map_err(|e| {
|
||||||
|
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn icc_attr_get(gic: &DeviceFd, offset: u64, typer: u64) -> Result<u32> {
|
||||||
|
let mut val = 0;
|
||||||
|
|
||||||
let mut gic_icc_attr = kvm_device_attr {
|
let mut gic_icc_attr = kvm_device_attr {
|
||||||
group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
|
group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
|
||||||
attr: ((typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | offset), // this needs the mpidr
|
attr: ((typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | offset), // this needs the mpidr
|
||||||
addr: val as *const u32 as u64,
|
addr: &mut val as *mut u32 as u64,
|
||||||
flags: 0,
|
flags: 0,
|
||||||
};
|
};
|
||||||
if set {
|
|
||||||
gic.set_device_attr(&gic_icc_attr).map_err(|e| {
|
// get_device_attr should be marked as unsafe, and will be in future.
|
||||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
// SAFETY: gic_icc_attr.addr is safe to write to.
|
||||||
})?;
|
gic.get_device_attr(&mut gic_icc_attr).map_err(|e| {
|
||||||
} else {
|
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||||
gic.get_device_attr(&mut gic_icc_attr).map_err(|e| {
|
})?;
|
||||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
|
||||||
})?;
|
Ok(val)
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get ICC registers.
|
/// Get ICC registers.
|
||||||
@@ -107,10 +122,9 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
|||||||
for ix in gicr_typer {
|
for ix in gicr_typer {
|
||||||
let i = *ix;
|
let i = *ix;
|
||||||
for icc_offset in VGIC_ICC_REGS {
|
for icc_offset in VGIC_ICC_REGS {
|
||||||
let val = 0;
|
|
||||||
if *icc_offset == SYS_ICC_CTLR_EL1 {
|
if *icc_offset == SYS_ICC_CTLR_EL1 {
|
||||||
// calculate priority bits by reading the ctrl_el1 register.
|
// calculate priority bits by reading the ctrl_el1 register.
|
||||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
let val = icc_attr_get(gic, *icc_offset, i)?;
|
||||||
// The priority bits are found in the ICC_CTLR_EL1 register (bits from 10:8).
|
// The priority bits are found in the ICC_CTLR_EL1 register (bits from 10:8).
|
||||||
// See page 194 from https://static.docs.arm.com/ihi0069/c/IHI0069C_gic_
|
// See page 194 from https://static.docs.arm.com/ihi0069/c/IHI0069C_gic_
|
||||||
// architecture_specification.pdf.
|
// architecture_specification.pdf.
|
||||||
@@ -130,8 +144,7 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
|||||||
// 7 bits of priority.
|
// 7 bits of priority.
|
||||||
else if *icc_offset == SYS_ICC_AP0R1_EL1 || *icc_offset == SYS_ICC_AP1R1_EL1 {
|
else if *icc_offset == SYS_ICC_AP0R1_EL1 || *icc_offset == SYS_ICC_AP1R1_EL1 {
|
||||||
if num_priority_bits >= 6 {
|
if num_priority_bits >= 6 {
|
||||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
state.push(icc_attr_get(gic, *icc_offset, i)?);
|
||||||
state.push(val);
|
|
||||||
}
|
}
|
||||||
} else if *icc_offset == SYS_ICC_AP0R2_EL1
|
} else if *icc_offset == SYS_ICC_AP0R2_EL1
|
||||||
|| *icc_offset == SYS_ICC_AP0R3_EL1
|
|| *icc_offset == SYS_ICC_AP0R3_EL1
|
||||||
@@ -139,12 +152,10 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
|||||||
|| *icc_offset == SYS_ICC_AP1R3_EL1
|
|| *icc_offset == SYS_ICC_AP1R3_EL1
|
||||||
{
|
{
|
||||||
if num_priority_bits == 7 {
|
if num_priority_bits == 7 {
|
||||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
state.push(icc_attr_get(gic, *icc_offset, i)?);
|
||||||
state.push(val);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
state.push(icc_attr_get(gic, *icc_offset, i)?);
|
||||||
state.push(val);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,7 +176,7 @@ pub fn set_icc_regs(gic: &DeviceFd, gicr_typer: &[u64], state: &[u32]) -> Result
|
|||||||
}
|
}
|
||||||
if *icc_offset == SYS_ICC_AP0R1_EL1 || *icc_offset == SYS_ICC_AP1R1_EL1 {
|
if *icc_offset == SYS_ICC_AP0R1_EL1 || *icc_offset == SYS_ICC_AP1R1_EL1 {
|
||||||
if num_priority_bits >= 6 {
|
if num_priority_bits >= 6 {
|
||||||
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
|
icc_attr_set(gic, *icc_offset, i, state[idx])?;
|
||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -176,12 +187,12 @@ pub fn set_icc_regs(gic: &DeviceFd, gicr_typer: &[u64], state: &[u32]) -> Result
|
|||||||
|| *icc_offset == SYS_ICC_AP1R3_EL1
|
|| *icc_offset == SYS_ICC_AP1R3_EL1
|
||||||
{
|
{
|
||||||
if num_priority_bits == 7 {
|
if num_priority_bits == 7 {
|
||||||
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
|
icc_attr_set(gic, *icc_offset, i, state[idx])?;
|
||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
|
icc_attr_set(gic, *icc_offset, i, state[idx])?;
|
||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
|
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
mod dist_regs;
|
mod dist_regs;
|
||||||
mod icc_regs;
|
mod icc_regs;
|
||||||
@@ -6,7 +8,7 @@ mod redist_regs;
|
|||||||
|
|
||||||
use crate::arch::aarch64::gic::{Error, Result, Vgic, VgicConfig};
|
use crate::arch::aarch64::gic::{Error, Result, Vgic, VgicConfig};
|
||||||
use crate::device::HypervisorDeviceError;
|
use crate::device::HypervisorDeviceError;
|
||||||
use crate::kvm::{kvm_bindings, KvmVm};
|
use crate::kvm::KvmVm;
|
||||||
use crate::{CpuState, Vm};
|
use crate::{CpuState, Vm};
|
||||||
use dist_regs::{get_dist_regs, read_ctlr, set_dist_regs, write_ctlr};
|
use dist_regs::{get_dist_regs, read_ctlr, set_dist_regs, write_ctlr};
|
||||||
use icc_regs::{get_icc_regs, set_icc_regs};
|
use icc_regs::{get_icc_regs, set_icc_regs};
|
||||||
@@ -14,7 +16,6 @@ use kvm_ioctls::DeviceFd;
|
|||||||
use redist_regs::{construct_gicr_typers, get_redist_regs, set_redist_regs};
|
use redist_regs::{construct_gicr_typers, get_redist_regs, set_redist_regs};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::convert::TryInto;
|
|
||||||
|
|
||||||
const GITS_CTLR: u32 = 0x0000;
|
const GITS_CTLR: u32 = 0x0000;
|
||||||
const GITS_IIDR: u32 = 0x0004;
|
const GITS_IIDR: u32 = 0x0004;
|
||||||
@@ -23,34 +24,38 @@ const GITS_CWRITER: u32 = 0x0088;
|
|||||||
const GITS_CREADR: u32 = 0x0090;
|
const GITS_CREADR: u32 = 0x0090;
|
||||||
const GITS_BASER: u32 = 0x0100;
|
const GITS_BASER: u32 = 0x0100;
|
||||||
|
|
||||||
/// Access an ITS device attribute.
|
fn gicv3_its_attr_set(its_device: &DeviceFd, group: u32, attr: u32, val: u64) -> Result<()> {
|
||||||
///
|
let gicv3_its_attr = kvm_bindings::kvm_device_attr {
|
||||||
/// This is a helper function to get/set the ITS device attribute depending
|
group,
|
||||||
/// the bool parameter `set` provided.
|
attr: attr as u64,
|
||||||
pub fn gicv3_its_attr_access(
|
addr: &val as *const u64 as u64,
|
||||||
its_device: &DeviceFd,
|
flags: 0,
|
||||||
group: u32,
|
};
|
||||||
attr: u32,
|
|
||||||
val: &u64,
|
its_device
|
||||||
set: bool,
|
.set_device_attr(&gicv3_its_attr)
|
||||||
) -> Result<()> {
|
.map_err(|e| Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gicv3_its_attr_get(its_device: &DeviceFd, group: u32, attr: u32) -> Result<u64> {
|
||||||
|
let mut val = 0;
|
||||||
|
|
||||||
let mut gicv3_its_attr = kvm_bindings::kvm_device_attr {
|
let mut gicv3_its_attr = kvm_bindings::kvm_device_attr {
|
||||||
group,
|
group,
|
||||||
attr: attr as u64,
|
attr: attr as u64,
|
||||||
addr: val as *const u64 as u64,
|
addr: &mut val as *mut u64 as u64,
|
||||||
flags: 0,
|
flags: 0,
|
||||||
};
|
};
|
||||||
if set {
|
|
||||||
its_device.set_device_attr(&gicv3_its_attr).map_err(|e| {
|
// get_device_attr should be marked as unsafe, and will be in future.
|
||||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
// SAFETY: gicv3_its_attr.addr is safe to write to.
|
||||||
})
|
its_device
|
||||||
} else {
|
.get_device_attr(&mut gicv3_its_attr)
|
||||||
its_device
|
.map_err(|e| {
|
||||||
.get_device_attr(&mut gicv3_its_attr)
|
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||||
.map_err(|e| {
|
})?;
|
||||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
|
||||||
})
|
Ok(val)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Function that saves/restores ITS tables into guest RAM.
|
/// Function that saves/restores ITS tables into guest RAM.
|
||||||
@@ -323,60 +328,43 @@ impl Vgic for KvmGicV3Its {
|
|||||||
|
|
||||||
let icc_state = get_icc_regs(&self.device, &gicr_typers)?;
|
let icc_state = get_icc_regs(&self.device, &gicr_typers)?;
|
||||||
|
|
||||||
let its_baser_state: [u64; 8] = [0; 8];
|
let mut its_baser_state: [u64; 8] = [0; 8];
|
||||||
for i in 0..8 {
|
for i in 0..8 {
|
||||||
gicv3_its_attr_access(
|
its_baser_state[i as usize] = gicv3_its_attr_get(
|
||||||
self.its_device.as_ref().unwrap(),
|
self.its_device.as_ref().unwrap(),
|
||||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
GITS_BASER + i * 8,
|
GITS_BASER + i * 8,
|
||||||
&its_baser_state[i as usize],
|
|
||||||
false,
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let its_ctlr_state: u64 = 0;
|
let its_ctlr_state = gicv3_its_attr_get(
|
||||||
gicv3_its_attr_access(
|
|
||||||
self.its_device.as_ref().unwrap(),
|
self.its_device.as_ref().unwrap(),
|
||||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
GITS_CTLR,
|
GITS_CTLR,
|
||||||
&its_ctlr_state,
|
|
||||||
false,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let its_cbaser_state: u64 = 0;
|
let its_cbaser_state = gicv3_its_attr_get(
|
||||||
gicv3_its_attr_access(
|
|
||||||
self.its_device.as_ref().unwrap(),
|
self.its_device.as_ref().unwrap(),
|
||||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
GITS_CBASER,
|
GITS_CBASER,
|
||||||
&its_cbaser_state,
|
|
||||||
false,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let its_creadr_state: u64 = 0;
|
let its_creadr_state = gicv3_its_attr_get(
|
||||||
gicv3_its_attr_access(
|
|
||||||
self.its_device.as_ref().unwrap(),
|
self.its_device.as_ref().unwrap(),
|
||||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
GITS_CREADR,
|
GITS_CREADR,
|
||||||
&its_creadr_state,
|
|
||||||
false,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let its_cwriter_state: u64 = 0;
|
let its_cwriter_state = gicv3_its_attr_get(
|
||||||
gicv3_its_attr_access(
|
|
||||||
self.its_device.as_ref().unwrap(),
|
self.its_device.as_ref().unwrap(),
|
||||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
GITS_CWRITER,
|
GITS_CWRITER,
|
||||||
&its_cwriter_state,
|
|
||||||
false,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let its_iidr_state: u64 = 0;
|
let its_iidr_state = gicv3_its_attr_get(
|
||||||
gicv3_its_attr_access(
|
|
||||||
self.its_device.as_ref().unwrap(),
|
self.its_device.as_ref().unwrap(),
|
||||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
GITS_IIDR,
|
GITS_IIDR,
|
||||||
&its_iidr_state,
|
|
||||||
false,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(Gicv3ItsState {
|
Ok(Gicv3ItsState {
|
||||||
@@ -406,57 +394,51 @@ impl Vgic for KvmGicV3Its {
|
|||||||
set_icc_regs(&self.device, &gicr_typers, &state.icc)?;
|
set_icc_regs(&self.device, &gicr_typers, &state.icc)?;
|
||||||
|
|
||||||
//Restore GICv3ITS registers
|
//Restore GICv3ITS registers
|
||||||
gicv3_its_attr_access(
|
gicv3_its_attr_set(
|
||||||
self.its_device.as_ref().unwrap(),
|
self.its_device.as_ref().unwrap(),
|
||||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
GITS_IIDR,
|
GITS_IIDR,
|
||||||
&state.its_iidr,
|
state.its_iidr,
|
||||||
true,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
gicv3_its_attr_access(
|
gicv3_its_attr_set(
|
||||||
self.its_device.as_ref().unwrap(),
|
self.its_device.as_ref().unwrap(),
|
||||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
GITS_CBASER,
|
GITS_CBASER,
|
||||||
&state.its_cbaser,
|
state.its_cbaser,
|
||||||
true,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
gicv3_its_attr_access(
|
gicv3_its_attr_set(
|
||||||
self.its_device.as_ref().unwrap(),
|
self.its_device.as_ref().unwrap(),
|
||||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
GITS_CREADR,
|
GITS_CREADR,
|
||||||
&state.its_creadr,
|
state.its_creadr,
|
||||||
true,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
gicv3_its_attr_access(
|
gicv3_its_attr_set(
|
||||||
self.its_device.as_ref().unwrap(),
|
self.its_device.as_ref().unwrap(),
|
||||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
GITS_CWRITER,
|
GITS_CWRITER,
|
||||||
&state.its_cwriter,
|
state.its_cwriter,
|
||||||
true,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
for i in 0..8 {
|
for i in 0..8 {
|
||||||
gicv3_its_attr_access(
|
gicv3_its_attr_set(
|
||||||
self.its_device.as_ref().unwrap(),
|
self.its_device.as_ref().unwrap(),
|
||||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
GITS_BASER + i * 8,
|
GITS_BASER + i * 8,
|
||||||
&state.its_baser[i as usize],
|
state.its_baser[i as usize],
|
||||||
true,
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore ITS tables
|
// Restore ITS tables
|
||||||
gicv3_its_tables_access(self.its_device.as_ref().unwrap(), false)?;
|
gicv3_its_tables_access(self.its_device.as_ref().unwrap(), false)?;
|
||||||
|
|
||||||
gicv3_its_attr_access(
|
gicv3_its_attr_set(
|
||||||
self.its_device.as_ref().unwrap(),
|
self.its_device.as_ref().unwrap(),
|
||||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
GITS_CTLR,
|
GITS_CTLR,
|
||||||
&state.its_ctlr,
|
state.its_ctlr,
|
||||||
true,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user