mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
824 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ed8e01faf | ||
|
|
1a5c6631a5 | ||
|
|
acec7e34fc | ||
|
|
deca570544 | ||
|
|
7cfb2139d0 | ||
|
|
1a7a76511f | ||
|
|
8a22b2987f | ||
|
|
1eb8a4671f | ||
|
|
2fe3586eba | ||
|
|
805cb303d5 | ||
|
|
97307a7fc1 | ||
|
|
6ed1f83a87 | ||
|
|
04aff2e2f9 | ||
|
|
0268089ac6 | ||
|
|
5d5d338e10 | ||
|
|
8f1404eeef | ||
|
|
9dba097224 | ||
|
|
ce5c5833cb | ||
|
|
3cb2b72e92 | ||
|
|
88fda7c305 | ||
|
|
b8b5dccfd8 | ||
|
|
6880692a78 | ||
|
|
76911fbeb4 | ||
|
|
0eb245e177 | ||
|
|
c866ccb3a1 | ||
|
|
bb68e0bc3b | ||
|
|
f81f4490b0 | ||
|
|
c419d3286b | ||
|
|
d76743ed0b | ||
|
|
1e2aa769e5 | ||
|
|
8091c28dd5 | ||
|
|
eca1df4e2d | ||
|
|
3d8728488d | ||
|
|
650cbce017 | ||
|
|
058946772a | ||
|
|
b443900cc2 | ||
|
|
8fad60bada | ||
|
|
2e8a552c24 | ||
|
|
7c917c0e74 | ||
|
|
aee54fc8f3 | ||
|
|
e10e1f6eed | ||
|
|
0ccb597baf | ||
|
|
a3cbc1ff72 | ||
|
|
e7cf513064 | ||
|
|
3dc15a9259 | ||
|
|
bcee2fbd2d | ||
|
|
1b87f332b0 | ||
|
|
7839e121f6 | ||
|
|
2c4fa258a6 | ||
|
|
0e0159db0c | ||
|
|
b5bcdbaf48 | ||
|
|
c7b794a7c5 | ||
|
|
280bef834b | ||
|
|
df92495e31 | ||
|
|
e62aafdf58 | ||
|
|
a14c70019a | ||
|
|
48c8649891 | ||
|
|
b4eb015d86 | ||
|
|
3e00247a47 | ||
|
|
6bce7f7937 | ||
|
|
f3d3d8daed | ||
|
|
dcddfbff91 | ||
|
|
787426d4e6 | ||
|
|
c357adae44 | ||
|
|
9f5325fd52 | ||
|
|
7f3fa39d81 | ||
|
|
9a5f3fc2a7 | ||
|
|
7932cd22ca | ||
|
|
195eba188a | ||
|
|
9341cce8b3 | ||
|
|
2ac7232288 | ||
|
|
905135fe8c | ||
|
|
57f532a760 | ||
|
|
831dff8b4e | ||
|
|
4db7530f28 | ||
|
|
239e2adc68 | ||
|
|
1c60591eab | ||
|
|
5e53bbf405 | ||
|
|
fbaceea4c0 | ||
|
|
56ec5ca260 | ||
|
|
3ffd2cb9be | ||
|
|
cacec04df6 | ||
|
|
72ec98b8a8 | ||
|
|
a174ad011e | ||
|
|
e9cc23ea94 | ||
|
|
6c245f6cf1 | ||
|
|
dd92715ed2 | ||
|
|
b3d1278838 | ||
|
|
92c2101f7c | ||
|
|
ff46fb69d0 | ||
|
|
efc583c13e | ||
|
|
d882f8c928 | ||
|
|
213da7d862 | ||
|
|
9c5528490e | ||
|
|
7bfb51489b | ||
|
|
e1ef141112 | ||
|
|
5f27d649a6 | ||
|
|
cab103d172 | ||
|
|
084fdd9d10 | ||
|
|
74c064e477 | ||
|
|
14f27e4219 | ||
|
|
9a8ff68bc1 | ||
|
|
b461200211 | ||
|
|
aba2513322 | ||
|
|
cb8fc4dc9f | ||
|
|
47be87c7cc | ||
|
|
66d52199a7 | ||
|
|
f840327ffb | ||
|
|
719e36049b | ||
|
|
f583f993ee | ||
|
|
bcb1dfb86f | ||
|
|
d7a69f8aa1 | ||
|
|
f36b5f3e3c | ||
|
|
cdaa4d3ad7 | ||
|
|
8c73af048a | ||
|
|
e2121c5d75 | ||
|
|
9a7199a116 | ||
|
|
c549bbd72a | ||
|
|
c39d43dee1 | ||
|
|
b76e0b358f | ||
|
|
9bc309b416 | ||
|
|
65a39f43cb | ||
|
|
324c924ca4 | ||
|
|
75ad89670b | ||
|
|
d4f01bdb43 | ||
|
|
e1c9a956da | ||
|
|
c2137e33a0 | ||
|
|
c05010887f | ||
|
|
334aa8c941 | ||
|
|
5d2df70a79 | ||
|
|
af88ff1c49 | ||
|
|
40dc3e7c10 | ||
|
|
56b9149325 | ||
|
|
7ae631e0c1 | ||
|
|
84454f142d | ||
|
|
a335cbb8d5 | ||
|
|
0cf9218d3f | ||
|
|
2439625785 | ||
|
|
01e2826f26 | ||
|
|
01acea3102 | ||
|
|
5ae04349da | ||
|
|
aca6fdbd0e | ||
|
|
e4bb6409ae | ||
|
|
b282ff44d4 | ||
|
|
8160c2884b | ||
|
|
c91b16d2a1 | ||
|
|
65429cebae | ||
|
|
4449b0d3c2 | ||
|
|
589f93da92 | ||
|
|
5296bd10c1 | ||
|
|
d100c014bc | ||
|
|
2cf75116d9 | ||
|
|
a45f7ac1bc | ||
|
|
e75de542df | ||
|
|
252963190b | ||
|
|
93de4c67e5 | ||
|
|
23791a45c8 | ||
|
|
355e7468e4 | ||
|
|
496ceed1d0 | ||
|
|
1205bce0ef | ||
|
|
c03fe508c1 | ||
|
|
6895e634cc | ||
|
|
86015bef78 | ||
|
|
bd724fc304 | ||
|
|
6e6f66de2e | ||
|
|
daa935b5c9 | ||
|
|
6fa1ec4f3c | ||
|
|
363ca66c2d | ||
|
|
b8f5911c4e | ||
|
|
30b74e74cd | ||
|
|
c400702272 | ||
|
|
dce846fdfd | ||
|
|
bbda08717f | ||
|
|
2eeeaf5753 | ||
|
|
7299202d01 | ||
|
|
1f63350eba | ||
|
|
cb26ad511b | ||
|
|
139621778b | ||
|
|
ae4e01adc9 | ||
|
|
6d6cafafb2 | ||
|
|
0d3c5c966b | ||
|
|
18012d9ee8 | ||
|
|
a6da5f9e77 | ||
|
|
c418074360 | ||
|
|
f0b64547c2 | ||
|
|
1f94208090 | ||
|
|
5268bf5a8c | ||
|
|
a097e29292 | ||
|
|
9f07c7940f | ||
|
|
1adcf5225b | ||
|
|
c439324451 | ||
|
|
66b46a277e | ||
|
|
9adfc04f75 | ||
|
|
28f383bae9 | ||
|
|
b04e437cfd | ||
|
|
0a00df2e7e | ||
|
|
aa76c9db2e | ||
|
|
f56471566b | ||
|
|
14544d64e4 | ||
|
|
2646b24047 | ||
|
|
ac2469077f | ||
|
|
b2b577257f | ||
|
|
8f9ffe5417 | ||
|
|
a4a0751051 | ||
|
|
904aea76ab | ||
|
|
b5c6b04b36 | ||
|
|
656b9f97f9 | ||
|
|
db66cb6e9b | ||
|
|
7e0ccce225 | ||
|
|
2ad615cd32 | ||
|
|
151668b260 | ||
|
|
f237789753 | ||
|
|
0f7cae4130 | ||
|
|
b5be62a167 | ||
|
|
6eaf44ec61 | ||
|
|
8ee0377456 | ||
|
|
d42502fab9 | ||
|
|
b534619d37 | ||
|
|
2aec0a92a5 | ||
|
|
7c3b361fa6 | ||
|
|
da8136e49d | ||
|
|
1f4a34ffc8 | ||
|
|
3b18caf229 | ||
|
|
7ca4d40d49 | ||
|
|
d844f6113d | ||
|
|
8eb3aa56ca | ||
|
|
f446a0f97f | ||
|
|
e3efa9a5e2 | ||
|
|
30a0127765 | ||
|
|
ed54036bfb | ||
|
|
328e950a6e | ||
|
|
a2cfe71c0a | ||
|
|
b8779ddc9e | ||
|
|
51a93bc635 | ||
|
|
9ef1a68539 | ||
|
|
fbc7011346 | ||
|
|
2f2bd927b3 | ||
|
|
bd90938f08 | ||
|
|
4806357f52 | ||
|
|
72d67fe96d | ||
|
|
f44343408e | ||
|
|
82220e610f | ||
|
|
b9e861fd57 | ||
|
|
b31589f6bc | ||
|
|
d30de8ecee | ||
|
|
d203c62b42 | ||
|
|
a528ec8f2e | ||
|
|
cfa5ff9fd5 | ||
|
|
235eb5225a | ||
|
|
3ffc655a38 | ||
|
|
7367ca70c2 | ||
|
|
534c3f57bb | ||
|
|
1bad026377 | ||
|
|
fdcfec081b | ||
|
|
9be27bf92e | ||
|
|
71d68e4f90 | ||
|
|
b5cf1f5a9e | ||
|
|
fb4fe17b52 | ||
|
|
5d1abec565 | ||
|
|
dbd8629fad | ||
|
|
884d528b31 | ||
|
|
767b4f0e59 | ||
|
|
375382cb08 | ||
|
|
b945a341b3 | ||
|
|
70e926372a | ||
|
|
f213083386 | ||
|
|
f8803e4639 | ||
|
|
87a4f4ae32 | ||
|
|
01f0c1e313 | ||
|
|
dab1cab4a7 | ||
|
|
f643ba6111 | ||
|
|
8614a0ab2f | ||
|
|
459892e07f | ||
|
|
ac3414503c | ||
|
|
5784285894 | ||
|
|
fd72612e91 | ||
|
|
da58b65997 | ||
|
|
55e6dbdb06 | ||
|
|
dfd109b1c6 | ||
|
|
079a2685dd | ||
|
|
d9bc216870 | ||
|
|
c82226fdae | ||
|
|
bfc65bff2a | ||
|
|
0b00442022 | ||
|
|
7c457378e5 | ||
|
|
3d06657f06 | ||
|
|
a7c4483b8b | ||
|
|
85f7913bb3 | ||
|
|
08c4e5031f | ||
|
|
78796f96b7 | ||
|
|
4c299c6c00 | ||
|
|
5b75c57cc4 | ||
|
|
0c1c8881ef | ||
|
|
40035a6067 | ||
|
|
126e37e3c8 | ||
|
|
b0da4f3a5e | ||
|
|
156653ff1c | ||
|
|
b92fe648e9 | ||
|
|
d4eaf746b5 | ||
|
|
810ed7e887 | ||
|
|
86e4067437 | ||
|
|
e0c0d0e142 | ||
|
|
37a2c13a90 | ||
|
|
740994542a | ||
|
|
99694cf5c1 | ||
|
|
17072e9a6f | ||
|
|
e1cc702327 | ||
|
|
37e2784299 | ||
|
|
6f5d4702d4 | ||
|
|
4e4c5fb6aa | ||
|
|
1939dbbf0b | ||
|
|
7e2c4b63c2 | ||
|
|
2d2623238d | ||
|
|
af02262b4b | ||
|
|
3f5ecbd326 | ||
|
|
e5211a6e1d | ||
|
|
7a6cca35e3 | ||
|
|
23411d45ba | ||
|
|
9a8a34a8a9 | ||
|
|
5c7164e55f | ||
|
|
9dbea4fe9c | ||
|
|
20c9a67f9d | ||
|
|
fd4ad1e124 | ||
|
|
d052b9ec12 | ||
|
|
cbf6ee8b8d | ||
|
|
a12ec27c33 | ||
|
|
776ce52b9f | ||
|
|
78f9d2cc85 | ||
|
|
a9a475c058 | ||
|
|
4ad8a22392 | ||
|
|
1bb0e54e92 | ||
|
|
46f96f27a4 | ||
|
|
d2bc7bb4c6 | ||
|
|
32ad4982dd | ||
|
|
5d8de62362 | ||
|
|
b176ddfe2a | ||
|
|
bfa37f89c4 | ||
|
|
ee871278ee | ||
|
|
b8311cac38 | ||
|
|
28a4e43731 | ||
|
|
1eb4ebe3d1 | ||
|
|
73e8fd4d72 | ||
|
|
943377e6a3 | ||
|
|
9e9aba7c0b | ||
|
|
431c16dc44 | ||
|
|
e4e2b5d89f | ||
|
|
21506a4a76 | ||
|
|
b161a570ec | ||
|
|
b16fdb1b3a | ||
|
|
d72d7fd93c | ||
|
|
7a18e247f4 | ||
|
|
d8e1898b46 | ||
|
|
8876e0f575 | ||
|
|
715c354d19 | ||
|
|
57697f92fa | ||
|
|
b18ae72d76 | ||
|
|
5b168f54a6 | ||
|
|
84b3992146 | ||
|
|
40c63dcf5e | ||
|
|
970bc05271 | ||
|
|
4da6bcd5d5 | ||
|
|
3671f5e94c | ||
|
|
40da6210f4 | ||
|
|
3c6dfd7709 | ||
|
|
e294688904 | ||
|
|
5c010d489d | ||
|
|
c5d15fd938 | ||
|
|
827229d8e4 | ||
|
|
6837de9057 | ||
|
|
7c9a83f6e1 | ||
|
|
2571cc8041 | ||
|
|
3b8d1f1411 | ||
|
|
9762c8bc28 | ||
|
|
7c302373ed | ||
|
|
19c5e91b6e | ||
|
|
4a5939973c | ||
|
|
0c27f69f1c | ||
|
|
7d5d9bdcf4 | ||
|
|
85ad72490f | ||
|
|
80e48b545d | ||
|
|
eb18ea61f4 | ||
|
|
bfbd75b2cf | ||
|
|
6dc3d60b2d | ||
|
|
aa34d545f6 | ||
|
|
261c039831 | ||
|
|
db6516931d | ||
|
|
030d6046b2 | ||
|
|
7390475636 | ||
|
|
c4564f3ba8 | ||
|
|
27ba8133a4 | ||
|
|
f5c550affb | ||
|
|
1d9f27c9fb | ||
|
|
8b7aafad16 | ||
|
|
46d082763f | ||
|
|
6170ba06d1 | ||
|
|
63304d0be7 | ||
|
|
1cd186c83c | ||
|
|
5581486149 | ||
|
|
3e3c163285 | ||
|
|
6190e64f2f | ||
|
|
88c30764e0 | ||
|
|
91145afa9b | ||
|
|
50d57b2b6f | ||
|
|
68401e6e4a | ||
|
|
7fad74cb04 | ||
|
|
b8c0cd5cd4 | ||
|
|
874ffe60ca | ||
|
|
e9793020c2 | ||
|
|
d370ea585b | ||
|
|
1e11e6789a | ||
|
|
f6da2bb5e1 | ||
|
|
987e52090f | ||
|
|
195ac4c5dc | ||
|
|
685a1760f3 | ||
|
|
e39924d45a | ||
|
|
b06fd80fa9 | ||
|
|
c8c3cad8cb | ||
|
|
e311fd66cd | ||
|
|
fbd624d816 | ||
|
|
305e095d15 | ||
|
|
62aaccee28 | ||
|
|
13724dbd22 | ||
|
|
16cb40733a | ||
|
|
9440304183 | ||
|
|
78f9ddc6be | ||
|
|
9b0996a71f | ||
|
|
f685f0e0b2 | ||
|
|
40a3c31df4 | ||
|
|
04dc496808 | ||
|
|
d0c0a98eda | ||
|
|
205e587fad | ||
|
|
2bb153de2b | ||
|
|
c853ed3fdc | ||
|
|
a59ff42a95 | ||
|
|
7c86ef8a69 | ||
|
|
c7438c8b22 | ||
|
|
3df2e2629b | ||
|
|
06d83d2eb3 | ||
|
|
9c3b489f0e | ||
|
|
3965a8505b | ||
|
|
a8cde12b14 | ||
|
|
fd95acc60f | ||
|
|
c85fba0c43 | ||
|
|
fea1f370ff | ||
|
|
6307db5699 | ||
|
|
548426c128 | ||
|
|
c3a75dafc5 | ||
|
|
ee7fcdb3cf | ||
|
|
afc83582be | ||
|
|
2a8bdf710d | ||
|
|
210a9d40c8 | ||
|
|
5bb10adf22 | ||
|
|
97f8f0fb31 | ||
|
|
5bc311184e | ||
|
|
7f96eb2b67 | ||
|
|
ab4b30edd3 | ||
|
|
cc78a597cd | ||
|
|
ebcbab739e | ||
|
|
0c7541473c | ||
|
|
169d6f6756 | ||
|
|
af8def364d | ||
|
|
c45a9a390d | ||
|
|
b5929645c6 | ||
|
|
ec9e6edcd0 | ||
|
|
00873e5f84 | ||
|
|
9e53efa3ca | ||
|
|
4f22f57651 | ||
|
|
1e1cbc53c1 | ||
|
|
8255d5f774 | ||
|
|
e3a8d6c13c | ||
|
|
581bf4aad5 | ||
|
|
16b82f3dfe | ||
|
|
675a8f808c | ||
|
|
59e5048436 | ||
|
|
7b3e79cdfa | ||
|
|
66ffaceffc | ||
|
|
4e307788b7 | ||
|
|
8739f6cccb | ||
|
|
5599cbef50 | ||
|
|
4c52ad2550 | ||
|
|
a0c07474a3 | ||
|
|
be0cbb09b1 | ||
|
|
d24aa887b6 | ||
|
|
835a31e283 | ||
|
|
57c8c250fd | ||
|
|
62abc117ab | ||
|
|
1c54fc3ab7 | ||
|
|
57ce0986f7 | ||
|
|
c8cad394b5 | ||
|
|
b02aff5761 | ||
|
|
f282cc001a | ||
|
|
c48e82915d | ||
|
|
66a3bed086 | ||
|
|
45cc26f940 | ||
|
|
77955bd8f9 | ||
|
|
e61ee6bcac | ||
|
|
fd9bd1c86c | ||
|
|
cf9e81c05a | ||
|
|
a4a2d6fbee | ||
|
|
94083793a6 | ||
|
|
8ae966e975 | ||
|
|
f68fe54b9b | ||
|
|
54e1796da6 | ||
|
|
c8be08adf6 | ||
|
|
ccfa34d066 | ||
|
|
d65a0b68b9 | ||
|
|
56028fb214 | ||
|
|
933d41cf2f | ||
|
|
61f9a4ec6c | ||
|
|
080ea31813 | ||
|
|
d6db2fdf96 | ||
|
|
f1aa09f178 | ||
|
|
8cbb7b15a9 | ||
|
|
b65502c3c1 | ||
|
|
3ef7c8eac5 | ||
|
|
526cf32a78 | ||
|
|
b0ddc5bd60 | ||
|
|
b72f6046e5 | ||
|
|
8a2bd01e25 | ||
|
|
d433ae1656 | ||
|
|
0348f480fa | ||
|
|
74565538ae | ||
|
|
99baee8d37 | ||
|
|
030a86db17 | ||
|
|
3eb5b67dc3 | ||
|
|
c27d6df233 | ||
|
|
30cd3cb764 | ||
|
|
e31c2be60a | ||
|
|
fb853a50b1 | ||
|
|
8c38d9576c | ||
|
|
5e98810c2d | ||
|
|
f8875acec2 | ||
|
|
fa8fcf5f4c | ||
|
|
3e847112db | ||
|
|
a0a89b1346 | ||
|
|
aee1155870 | ||
|
|
5bd05b1af0 | ||
|
|
727287b69d | ||
|
|
deedfcdc35 | ||
|
|
2122233047 | ||
|
|
afce21ba59 | ||
|
|
ade5097878 | ||
|
|
24922ce1e3 | ||
|
|
c1be41bfbf | ||
|
|
d361fc1a36 | ||
|
|
49214cf02b | ||
|
|
d78b2ec8b5 | ||
|
|
946c484590 | ||
|
|
cd700bf449 | ||
|
|
cf7a05ecb5 | ||
|
|
0497a7c311 | ||
|
|
05a2b3fac2 | ||
|
|
d33c0563af | ||
|
|
4ed0e1a3c8 | ||
|
|
5ed2a654e8 | ||
|
|
ae04fe432c | ||
|
|
c8d142eb55 | ||
|
|
8533d63514 | ||
|
|
0ef69fa592 | ||
|
|
29f924405a | ||
|
|
282134a490 | ||
|
|
1fbdca16bf | ||
|
|
32a2000ecc | ||
|
|
6a499f02d9 | ||
|
|
0a9c052ebd | ||
|
|
bfb12b7777 | ||
|
|
c89095ab85 | ||
|
|
9260c4c10e | ||
|
|
4822ed79e1 | ||
|
|
ddbef7450d | ||
|
|
c1d9edbfc0 | ||
|
|
0d209e135e | ||
|
|
e22b6ec768 | ||
|
|
b59243f6cf | ||
|
|
07a09eda27 | ||
|
|
38c41a5074 | ||
|
|
6e4c90f305 | ||
|
|
707bb0ba72 | ||
|
|
29881a2d6a | ||
|
|
a330a1569a | ||
|
|
cf6480f012 | ||
|
|
a838d0bde1 | ||
|
|
f58b6f8417 | ||
|
|
71ae9cfe06 | ||
|
|
f0b7a09a49 | ||
|
|
bc84a1c79b | ||
|
|
9c5be6f660 | ||
|
|
8e9a5a0dbb | ||
|
|
d561b45552 | ||
|
|
9353856426 | ||
|
|
9af477e964 | ||
|
|
27515a6ec4 | ||
|
|
169a6bb83b | ||
|
|
19167e7647 | ||
|
|
2cdc3e3546 | ||
|
|
de9471fc72 | ||
|
|
b5450ca72c | ||
|
|
8008aad545 | ||
|
|
3bd47ffdc1 | ||
|
|
acfbee5b7a | ||
|
|
62c6efb2e5 | ||
|
|
aec0d309ee | ||
|
|
5fc12862e6 | ||
|
|
f4159ca016 | ||
|
|
6d63018d9f | ||
|
|
073e0f979b | ||
|
|
50a995b63d | ||
|
|
ccdea0274c | ||
|
|
688ead51c6 | ||
|
|
9792c9aafa | ||
|
|
48963e322a | ||
|
|
a59fbf0e37 | ||
|
|
a6b839b35c | ||
|
|
aa3d5cfbfe | ||
|
|
ddd0552c83 | ||
|
|
1fc8c9165a | ||
|
|
47f47ad53f | ||
|
|
69d1186ccb | ||
|
|
d0601a1473 | ||
|
|
b91b829a28 | ||
|
|
7928a697dc | ||
|
|
b5fb744828 | ||
|
|
29607f38ad | ||
|
|
9c3a73706f | ||
|
|
024675d1d8 | ||
|
|
2c913b8130 | ||
|
|
c397c9c95e | ||
|
|
54f814f34a | ||
|
|
ad8adcb955 | ||
|
|
f24094392e | ||
|
|
eee218f1f5 | ||
|
|
5ada3f59c8 | ||
|
|
89008a49cf | ||
|
|
295f4df7bf | ||
|
|
769194cac8 | ||
|
|
13a3ac6a77 | ||
|
|
b26777cb01 | ||
|
|
24c8cce012 | ||
|
|
c6854c5a97 | ||
|
|
b2e5dbaecb | ||
|
|
5bbf2dca80 | ||
|
|
1df952726a | ||
|
|
9ea087597f | ||
|
|
952f9bd3fe | ||
|
|
c29caf2a85 | ||
|
|
5fbeacabad | ||
|
|
f7502057d9 | ||
|
|
ff54909d2c | ||
|
|
6664e5a6e7 | ||
|
|
5209026f52 | ||
|
|
5db9b0ec99 | ||
|
|
c4fb0f36bd | ||
|
|
9cdcbb5121 | ||
|
|
a194dad98c | ||
|
|
b339aa6b88 | ||
|
|
276c9a10fd | ||
|
|
36360c7630 | ||
|
|
b959849149 | ||
|
|
50a89c6808 | ||
|
|
192d69d601 | ||
|
|
b8342ebeb4 | ||
|
|
58009af85d | ||
|
|
0655f25da4 | ||
|
|
2547f144cd | ||
|
|
ec7700544c | ||
|
|
0c60fa8268 | ||
|
|
76e15a4240 | ||
|
|
7659055eec | ||
|
|
5209bd6120 | ||
|
|
c2acea5156 | ||
|
|
fd9972ecc5 | ||
|
|
ca4ab99f42 | ||
|
|
977d824cf8 | ||
|
|
38fa630ce7 | ||
|
|
a59b361058 | ||
|
|
fa5b5fd602 | ||
|
|
7765398045 | ||
|
|
9282ad82b0 | ||
|
|
a09feecc36 | ||
|
|
060df69f07 | ||
|
|
f6c8e4b045 | ||
|
|
2824642e80 | ||
|
|
41cfdb50cd | ||
|
|
12e20effd7 | ||
|
|
9fc86a91e2 | ||
|
|
da8ce25abf | ||
|
|
23e3b022eb | ||
|
|
30033bdaea | ||
|
|
d578c408b7 | ||
|
|
55a3a38e14 | ||
|
|
6006068951 | ||
|
|
28ab6cea0e | ||
|
|
4ebeeb1310 | ||
|
|
77f6cd5ef4 | ||
|
|
f2c2a44ca6 | ||
|
|
8d418e1fca | ||
|
|
c90f77e399 | ||
|
|
a105089702 | ||
|
|
c366efc19e | ||
|
|
c9b68a3be6 | ||
|
|
72b736153e | ||
|
|
79a2afe44e | ||
|
|
7d299f1055 | ||
|
|
23f9ec50fb | ||
|
|
c99d3f70ac | ||
|
|
33d339c406 | ||
|
|
22649c4a87 | ||
|
|
ef363d79fb | ||
|
|
92f3f86669 | ||
|
|
f6d6495b93 | ||
|
|
68d90cbe65 | ||
|
|
d0b59ca3a0 | ||
|
|
981bb72a09 | ||
|
|
843a010163 | ||
|
|
c381b36b87 | ||
|
|
ff4d378f1b | ||
|
|
74a4aba257 | ||
|
|
7b376fa8e0 | ||
|
|
23afe89089 | ||
|
|
3b43551d98 | ||
|
|
c3df42d42f | ||
|
|
6b424b5f6b | ||
|
|
b2dd03b2dc | ||
|
|
4ebca7f4ea | ||
|
|
d036210ecd | ||
|
|
39d080e0c1 | ||
|
|
d83c9a74f4 | ||
|
|
d26866e018 | ||
|
|
ba7864e111 | ||
|
|
5881809126 | ||
|
|
21ba34542d | ||
|
|
ab456affb3 | ||
|
|
852bf0b2ce | ||
|
|
642a1a610f | ||
|
|
bdfe6a69ef | ||
|
|
7889fc9207 | ||
|
|
cb826aa2f1 | ||
|
|
df522cf12c | ||
|
|
a920ef48de | ||
|
|
9cd8aa1af9 | ||
|
|
f6b63af06d | ||
|
|
8ff1142bf4 | ||
|
|
5de4d03238 | ||
|
|
302e4c17d2 | ||
|
|
b49c7dfd65 | ||
|
|
985da4d3fe | ||
|
|
2ec7d1ae05 | ||
|
|
fc47dd6645 | ||
|
|
2e924f458e | ||
|
|
20fde6b924 | ||
|
|
72d054bf40 | ||
|
|
e0d79196c8 | ||
|
|
a286e960ed | ||
|
|
f70852c04b | ||
|
|
8f740f5c02 | ||
|
|
17c9e2cd4b | ||
|
|
b5653d5278 | ||
|
|
28abe63f46 | ||
|
|
49b49421d0 | ||
|
|
ef12216c19 | ||
|
|
e05ed9240a | ||
|
|
c91d25ea64 | ||
|
|
315a730128 | ||
|
|
230e8a23a0 | ||
|
|
f583aa9d30 | ||
|
|
76adea46d0 | ||
|
|
7f1f335950 | ||
|
|
2e6f93e1a9 | ||
|
|
539708e45e | ||
|
|
2e87d5ae02 | ||
|
|
14af74cb5b | ||
|
|
73612401a5 | ||
|
|
858f64b3dc | ||
|
|
225bedcb20 | ||
|
|
8367784cf3 | ||
|
|
ecc1da95dd | ||
|
|
184baff355 | ||
|
|
3ad94ddf7c | ||
|
|
fabd63072b | ||
|
|
05cdef17f4 | ||
|
|
6ccd32c904 | ||
|
|
50dae04c05 | ||
|
|
44ac11efc7 | ||
|
|
f452fe7497 | ||
|
|
a2e1e13918 | ||
|
|
a4134f6b25 | ||
|
|
d2de263774 | ||
|
|
9f2e7f455f | ||
|
|
faba6a3fb3 | ||
|
|
2aaf0540b6 | ||
|
|
d82e74e4bf | ||
|
|
f9a3476016 | ||
|
|
aaa364a9c5 | ||
|
|
d20e07b271 | ||
|
|
3074b178bd | ||
|
|
4751a5949d | ||
|
|
25d567b6c6 | ||
|
|
bb1ef5f1b8 | ||
|
|
d9e1eec47d | ||
|
|
a4c076bd09 | ||
|
|
8a27735826 | ||
|
|
e8c72b1b01 | ||
|
|
ee537d9bde | ||
|
|
873c79a931 | ||
|
|
7a51be48c6 | ||
|
|
7f9cdf1183 | ||
|
|
72e5ed3372 | ||
|
|
ca48f1c995 | ||
|
|
444905071b | ||
|
|
385d734847 | ||
|
|
16c2eebfd1 | ||
|
|
21dcac7721 | ||
|
|
3ee56112cb | ||
|
|
03db48306b | ||
|
|
dee42ebb29 | ||
|
|
7cc729c7d9 | ||
|
|
a8643dc523 | ||
|
|
1fc6d50f3e | ||
|
|
dbf4a252ad | ||
|
|
add9b13497 | ||
|
|
3d097eef44 | ||
|
|
8c85dd32fa | ||
|
|
aac86f4523 | ||
|
|
f3e889c204 |
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: File a bug report
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behaviour:
|
||||
|
||||
**Version**
|
||||
|
||||
Output of `cloud-hypervisor --version`:
|
||||
|
||||
Did you build from source, if so build command line (e.g. features):
|
||||
|
||||
**VM configuration**
|
||||
|
||||
What command line did you run (or JSON config data):
|
||||
|
||||
Guest OS version details:
|
||||
|
||||
Host OS version details:
|
||||
|
||||
**Logs**
|
||||
|
||||
Output of `cloud-hypervisor -v` from either standard error or via `--log-file`:
|
||||
|
||||
Linux kernel output:
|
||||
18
.github/dependabot.yml
vendored
Normal file
18
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 3
|
||||
allow:
|
||||
- dependency-type: direct
|
||||
- dependency-type: indirect
|
||||
- package-ecosystem: cargo
|
||||
directory: "/fuzz"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 3
|
||||
allow:
|
||||
- dependency-type: direct
|
||||
- dependency-type: indirect
|
||||
32
.github/workflows/cross-build.yaml
vendored
32
.github/workflows/cross-build.yaml
vendored
@@ -1,32 +0,0 @@
|
||||
name: Cloud Hypervisor Cross Build
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- 1.46.0
|
||||
target:
|
||||
- aarch64-unknown-linux-gnu
|
||||
- aarch64-unknown-linux-musl
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- name: Install arm64 libfdt
|
||||
run: wget http://ftp.us.debian.org/debian/pool/main/d/device-tree-compiler/libfdt-dev_1.6.0-1_arm64.deb && dpkg-deb -xv libfdt-dev_1.6.0-1_arm64.deb ./tlibfdtdev && sudo mkdir /tmmmp && mkdir target && mkdir target/debug && mkdir target/debug/deps && sudo cp ./tlibfdtdev/usr/lib/aarch64-linux-gnu/libfdt.a target/debug/deps/libfdt.a && echo "libfdt installed"
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --target=${{ matrix.target }} --no-default-features --features "kvm"
|
||||
9
.github/workflows/quality-aarch64.yaml
vendored
9
.github/workflows/quality-aarch64.yaml
vendored
@@ -6,12 +6,19 @@ jobs:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Quality (clippy, rustfmt)
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
rust:
|
||||
- stable
|
||||
target:
|
||||
- aarch64-unknown-linux-gnu
|
||||
experimental: [false]
|
||||
include:
|
||||
- rust: beta
|
||||
target: aarch64-unknown-linux-gnu
|
||||
experimental: true
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
@@ -22,8 +29,6 @@ jobs:
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- name: Install arm64 libfdt
|
||||
run: wget http://ftp.us.debian.org/debian/pool/main/d/device-tree-compiler/libfdt-dev_1.6.0-1_arm64.deb && dpkg-deb -xv libfdt-dev_1.6.0-1_arm64.deb ./tlibfdtdev && sudo mkdir /tmmmp && mkdir target && mkdir target/debug && mkdir target/debug/deps && sudo cp ./tlibfdtdev/usr/lib/aarch64-linux-gnu/libfdt.a target/debug/deps/libfdt.a && echo "libfdt installed"
|
||||
- name: Formatting (rustfmt)
|
||||
run: cargo fmt -- --check
|
||||
- name: Clippy (kvm)
|
||||
|
||||
13
.github/workflows/quality.yaml
vendored
13
.github/workflows/quality.yaml
vendored
@@ -6,12 +6,19 @@ jobs:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Quality (clippy, rustfmt)
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
rust:
|
||||
- stable
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
experimental: [false]
|
||||
include:
|
||||
- rust: beta
|
||||
target: x86_64-unknown-linux-gnu
|
||||
experimental: true
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
@@ -35,6 +42,9 @@ jobs:
|
||||
- name: Clippy (acpi,kvm)
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "acpi,kvm" -- -D warnings
|
||||
|
||||
- name: Clippy (acpi,kvm,tdx)
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "acpi,kvm,tdx" -- -D warnings
|
||||
|
||||
- name: Clippy (kvm)
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "kvm" -- -D warnings
|
||||
|
||||
@@ -43,3 +53,6 @@ jobs:
|
||||
|
||||
- name: Clippy (mshv)
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "mshv" -- -D warnings
|
||||
|
||||
- name: Clippy (integration tests)
|
||||
run: cargo clippy --all --all-targets --tests --features "integration_tests" -- -D warnings
|
||||
|
||||
890
Cargo.lock
generated
890
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
43
Cargo.toml
43
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cloud-hypervisor"
|
||||
version = "0.12.0"
|
||||
version = "16.0.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
default-run = "cloud-hypervisor"
|
||||
@@ -13,38 +13,40 @@ homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
anyhow = "1.0.40"
|
||||
api_client = { path = "api_client" }
|
||||
clap = { version = "2.33.3", features = ["wrap_help"] }
|
||||
epoll = ">=4.0.1"
|
||||
epoll = "4.3.1"
|
||||
event_monitor = { path = "event_monitor" }
|
||||
hypervisor = { path = "hypervisor" }
|
||||
libc = "0.2.81"
|
||||
log = { version = "0.4.11", features = ["std"] }
|
||||
libc = "0.2.96"
|
||||
log = { version = "0.4.14", features = ["std"] }
|
||||
option_parser = { path = "option_parser" }
|
||||
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.22.0" }
|
||||
serde_json = "1.0.60"
|
||||
thiserror = "1.0"
|
||||
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.24.4" }
|
||||
serde_json = "1.0.64"
|
||||
signal-hook = "0.3.9"
|
||||
thiserror = "1.0.25"
|
||||
vmm = { path = "vmm" }
|
||||
vmm-sys-util = "0.7.0"
|
||||
wait-timeout = "0.2.0"
|
||||
vm-memory = "0.4.0"
|
||||
vmm-sys-util = "0.8.0"
|
||||
vm-memory = "0.5.0"
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version = "2.33.3", features = ["wrap_help"] }
|
||||
|
||||
# List of patched crates
|
||||
[patch.'https://github.com/rust-vmm/vhost']
|
||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "ch", package = "vhost", features = ["vhost-user-master", "vhost-user-slave"] }
|
||||
[patch.crates-io]
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.4.0", features = ["with-serde", "fam-wrappers"] }
|
||||
versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch" }
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory", rev = "5bd7138758183a73ac0da27ce40c004d95f1a7e9"}
|
||||
|
||||
[dev-dependencies]
|
||||
ssh2 = "0.9.0"
|
||||
dirs = "3.0.1"
|
||||
credibility = "0.1.3"
|
||||
tempdir = "0.3.7"
|
||||
dirs = "3.0.2"
|
||||
lazy_static= "1.4.0"
|
||||
tempfile = "3.1.0"
|
||||
serde_json = "1.0.60"
|
||||
net_util = { path = "net_util" }
|
||||
serde_json = "1.0.64"
|
||||
test_infra = { path = "test_infra" }
|
||||
wait-timeout = "0.2.0"
|
||||
|
||||
[features]
|
||||
default = ["acpi", "cmos", "io_uring", "kvm"]
|
||||
@@ -56,6 +58,7 @@ fwdebug = ["vmm/fwdebug"]
|
||||
kvm = ["vmm/kvm"]
|
||||
mshv = ["vmm/mshv"]
|
||||
io_uring = ["vmm/io_uring"]
|
||||
tdx = ["vmm/tdx"]
|
||||
|
||||
# Integration tests require a special environment to run in
|
||||
integration_tests = []
|
||||
@@ -65,15 +68,16 @@ members = [
|
||||
"acpi_tables",
|
||||
"api_client",
|
||||
"arch",
|
||||
"arch_gen",
|
||||
"block_util",
|
||||
"devices",
|
||||
"event_monitor",
|
||||
"hypervisor",
|
||||
"net_gen",
|
||||
"net_util",
|
||||
"option_parser",
|
||||
"pci",
|
||||
"qcow",
|
||||
"rate_limiter",
|
||||
"vhost_user_backend",
|
||||
"vhost_user_block",
|
||||
"vhost_user_net",
|
||||
@@ -84,3 +88,4 @@ members = [
|
||||
"vm-migration",
|
||||
"vm-virtio"
|
||||
]
|
||||
exclude = ["test_infra"]
|
||||
|
||||
51
Jenkinsfile
vendored
51
Jenkinsfile
vendored
@@ -22,8 +22,7 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Build') {
|
||||
failFast true
|
||||
parallel {
|
||||
parallel {
|
||||
stage ('Worker build') {
|
||||
agent { node { label 'groovy' } }
|
||||
stages {
|
||||
@@ -47,6 +46,7 @@ pipeline{
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "sudo modprobe openvswitch"
|
||||
sh "scripts/dev_cli.sh tests --integration"
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,7 @@ pipeline{
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "sudo modprobe openvswitch"
|
||||
sh "scripts/dev_cli.sh tests --integration"
|
||||
}
|
||||
}
|
||||
@@ -99,6 +100,7 @@ pipeline{
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "sudo modprobe openvswitch"
|
||||
sh "scripts/dev_cli.sh tests --integration --libc musl"
|
||||
}
|
||||
}
|
||||
@@ -106,7 +108,10 @@ pipeline{
|
||||
}
|
||||
stage ('Worker build SGX') {
|
||||
agent { node { label 'bionic-sgx' } }
|
||||
when { branch 'master' }
|
||||
when {
|
||||
beforeAgent true
|
||||
branch 'master'
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
@@ -137,8 +142,44 @@ pipeline{
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Worker build VFIO') {
|
||||
agent { node { label 'bionic-vfio' } }
|
||||
when {
|
||||
beforeAgent true
|
||||
branch 'master'
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Worker build - Windows guest') {
|
||||
agent { node { label 'groovy-win' } }
|
||||
agent { node { label 'groovy' } }
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
@@ -150,7 +191,7 @@ pipeline{
|
||||
sh "mkdir ${env.HOME}/workloads"
|
||||
azureDownload(storageCredentialId: 'ch-image-store',
|
||||
containerName: 'private-images',
|
||||
includeFilesPattern: 'OVMF.fd',
|
||||
includeFilesPattern: 'OVMF-4b47d0c6c8.fd',
|
||||
downloadType: 'container',
|
||||
downloadDirLoc: "${env.HOME}/workloads")
|
||||
azureDownload(storageCredentialId: 'ch-image-store',
|
||||
|
||||
44
README.md
44
README.md
@@ -26,7 +26,8 @@
|
||||
|
||||
# 1. What is Cloud Hypervisor?
|
||||
|
||||
Cloud Hypervisor is an open source Virtual Machine Monitor (VMM) that runs on top of [KVM](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt).
|
||||
Cloud Hypervisor is an open source Virtual Machine Monitor (VMM) that runs on top of [KVM](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt) and the MSHV hypervisors .
|
||||
|
||||
The project focuses on exclusively running modern, cloud workloads, on top of a limited set of hardware architectures and platforms.
|
||||
Cloud workloads refers to those that are usually run by customers inside a cloud provider. For our purposes this means modern operating systems with most I/O handled by paravirtualised devices (i.e. virtio), no requirement for legacy devices, and 64-bit CPUs.
|
||||
|
||||
@@ -36,7 +37,7 @@ Cloud Hypervisor is implemented in [Rust](https://www.rust-lang.org/) and is bas
|
||||
|
||||
### High Level
|
||||
|
||||
- KVM based
|
||||
- Runs on KVM or MSHV
|
||||
- Minimal emulation
|
||||
- Low latency
|
||||
- Low memory footprint
|
||||
@@ -53,7 +54,7 @@ Cloud Hypervisor supports the `x86-64` and `AArch64` architectures. There are so
|
||||
|
||||
### Guest OS
|
||||
|
||||
Cloud Hypervisor supports `64-bit Linux` with support for _modern_ 64-bit Windows guests currently under development.
|
||||
Cloud Hypervisor supports `64-bit Linux` and Windows 10/Windows Server 2019.
|
||||
|
||||
# 2. Getting Started
|
||||
|
||||
@@ -148,7 +149,7 @@ We need to get the latest `rust-hypervisor-firmware` release and also a working
|
||||
$ pushd $CLOUDH
|
||||
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img
|
||||
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw
|
||||
$ wget https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/download/0.2.8/hypervisor-fw
|
||||
$ wget https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/download/0.3.1/hypervisor-fw
|
||||
$ popd
|
||||
```
|
||||
|
||||
@@ -171,7 +172,7 @@ Multiple arguments can be given to the `--disk` parameter.
|
||||
|
||||
#### Building your kernel
|
||||
|
||||
Cloud Hypervisor also supports direct kernel boot into a `vmlinux` ELF kernel or `bzImage`. In order to support virtio-fs and virtio-iommu we have our own development branch. You are of course able to use your own kernel but these instructions will continue with the version that we develop and test against.
|
||||
Cloud Hypervisor also supports direct kernel boot into a `vmlinux` ELF kernel. In order to support virtio-iommu we have our own development branch. You are of course able to use your own kernel but these instructions will continue with the version that we develop and test against.
|
||||
|
||||
To build the kernel:
|
||||
|
||||
@@ -179,7 +180,7 @@ To build the kernel:
|
||||
|
||||
# Clone the Cloud Hypervisor Linux branch
|
||||
$ pushd $CLOUDH
|
||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b virtio-fs-virtio-iommu-virtio-mem-5.6-rc4 linux-cloud-hypervisor
|
||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-5.12 linux-cloud-hypervisor
|
||||
$ pushd linux-cloud-hypervisor
|
||||
|
||||
# Use the cloud-hypervisor kernel config to build your kernel
|
||||
@@ -204,7 +205,7 @@ $ popd
|
||||
#### Booting the guest VM
|
||||
|
||||
Now we can directly boot into our custom kernel and make it use the Ubuntu root partition.
|
||||
If we want to have 4 vCPUs and 512 MBytes of memory:
|
||||
If we want to have 4 vCPUs and 1024 MBytes of memory:
|
||||
|
||||
```shell
|
||||
$ pushd $CLOUDH
|
||||
@@ -241,23 +242,40 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
|
||||
# 3. Status
|
||||
|
||||
Cloud Hypervisor is under active development. No API or feature stability is guaranteed.
|
||||
Cloud Hypervisor is under active development. The following stability guarantees are currently made:
|
||||
|
||||
As of 2020-07-02, the following cloud images are supported:
|
||||
* The API (including command line options) will not be removed or changed in a
|
||||
breaking way without a minimum of 2 releases notice. Where possible warnings
|
||||
will be given about the use of deprecated functionality and the deprecations
|
||||
will be documented in the release notes.
|
||||
* Point releases will be made between individual releases where there are
|
||||
substantial bug fixes or security issues that need to be fixed.
|
||||
|
||||
Currently the following items are **not** guaranteed across updates:
|
||||
|
||||
* Snapshot/restore is not supported across different versions
|
||||
* Live migration is not supported across different versions
|
||||
* The following features are considered experimental and may change
|
||||
substantially between releases: TDX, SGX.
|
||||
|
||||
|
||||
As of 2021-04-29, the following cloud images are supported:
|
||||
|
||||
- [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (cloudimg)
|
||||
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (cloudimg)
|
||||
- [Ubuntu Groovy](https://cloud-images.ubuntu.com/groovy/current/) (cloudimg)
|
||||
- [Ubuntu Hirsute](https://cloud-images.ubuntu.com/hirsute/current/) (cloudimg)
|
||||
|
||||
Direct kernel boot to userspace should work with a rootfs from most distributions.
|
||||
|
||||
## Hot Plug
|
||||
|
||||
Cloud Hypervisor supports hotplug of CPUs, passthrough devices (VFIO), `virtio-{net,block,pmem,fs,vsock}` and memory resizing. This [document](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/hotplug.md) details how to add devices to
|
||||
Cloud Hypervisor supports hotplug of CPUs, passthrough devices (VFIO), `virtio-{net,block,pmem,fs,vsock}` and memory resizing. This [document](docs/hotplug.md) details how to add devices to
|
||||
a running VM.
|
||||
|
||||
## Device Model
|
||||
|
||||
Details of the device model can be found in this [documentation](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/device_model.md).
|
||||
Details of the device model can be found in this [documentation](docs/device_model.md).
|
||||
|
||||
## TODO
|
||||
|
||||
@@ -317,3 +335,7 @@ etc, are all equal and welcome means of contribution. See the [CONTRIBUTING](CON
|
||||
|
||||
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LWQ1MTA1ZDVmODkwMWQ1MTRhYzk4ZGNlN2UwNTI3ZmFlODU0OTcwOWZjMTkwZDExYWE3YjFmNzgzY2FmNDAyMjI)
|
||||
and [join us on Slack](https://cloud-hypervisor.slack.com/).
|
||||
|
||||
## Security issues
|
||||
|
||||
Please use the GitHub security advisories feature for reporting issues: https://github.com/cloud-hypervisor/cloud-hypervisor/security/advisories/new
|
||||
|
||||
@@ -5,5 +5,4 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vm-memory = "0.4.0"
|
||||
|
||||
vm-memory = "0.5.0"
|
||||
|
||||
@@ -95,8 +95,7 @@ pub type Byte = u8;
|
||||
|
||||
impl Aml for Byte {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(0x0a); /* BytePrefix */
|
||||
let mut bytes = vec![0x0a]; /* BytePrefix */
|
||||
bytes.push(*self);
|
||||
bytes
|
||||
}
|
||||
@@ -106,8 +105,7 @@ pub type Word = u16;
|
||||
|
||||
impl Aml for Word {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(0x0bu8); /* WordPrefix */
|
||||
let mut bytes = vec![0x0bu8]; /* WordPrefix */
|
||||
bytes.append(&mut self.to_le_bytes().to_vec());
|
||||
bytes
|
||||
}
|
||||
@@ -117,8 +115,7 @@ pub type DWord = u32;
|
||||
|
||||
impl Aml for DWord {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(0x0c); /* DWordPrefix */
|
||||
let mut bytes = vec![0x0c]; /* DWordPrefix */
|
||||
bytes.append(&mut self.to_le_bytes().to_vec());
|
||||
bytes
|
||||
}
|
||||
@@ -128,8 +125,7 @@ pub type QWord = u64;
|
||||
|
||||
impl Aml for QWord {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(0x0e); /* QWordPrefix */
|
||||
let mut bytes = vec![0x0e]; /* QWordPrefix */
|
||||
bytes.append(&mut self.to_le_bytes().to_vec());
|
||||
bytes
|
||||
}
|
||||
@@ -147,8 +143,7 @@ impl Aml for Name {
|
||||
|
||||
impl Name {
|
||||
pub fn new(path: Path, inner: &dyn Aml) -> Self {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(0x08); /* NameOp */
|
||||
let mut bytes = vec![0x08]; /* NameOp */
|
||||
bytes.append(&mut path.to_aml_bytes());
|
||||
bytes.append(&mut inner.to_aml_bytes());
|
||||
Name { bytes }
|
||||
@@ -161,8 +156,7 @@ pub struct Package<'a> {
|
||||
|
||||
impl<'a> Aml for Package<'a> {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(self.children.len() as u8);
|
||||
let mut bytes = vec![self.children.len() as u8];
|
||||
for child in &self.children {
|
||||
bytes.append(&mut child.to_aml_bytes());
|
||||
}
|
||||
@@ -239,11 +233,11 @@ fn create_pkg_length(data: &[u8], include_self: bool) -> Vec<u8> {
|
||||
result
|
||||
}
|
||||
|
||||
pub struct EISAName {
|
||||
pub struct EisaName {
|
||||
value: DWord,
|
||||
}
|
||||
|
||||
impl EISAName {
|
||||
impl EisaName {
|
||||
pub fn new(name: &str) -> Self {
|
||||
assert_eq!(name.len(), 7);
|
||||
|
||||
@@ -258,11 +252,11 @@ impl EISAName {
|
||||
| name.chars().nth(6).unwrap().to_digit(16).unwrap())
|
||||
.swap_bytes();
|
||||
|
||||
EISAName { value }
|
||||
EisaName { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl Aml for EISAName {
|
||||
impl Aml for EisaName {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
self.value.to_aml_bytes()
|
||||
}
|
||||
@@ -289,8 +283,7 @@ impl Aml for Usize {
|
||||
}
|
||||
|
||||
fn create_aml_string(v: &str) -> Vec<u8> {
|
||||
let mut data = Vec::new();
|
||||
data.push(0x0D); /* String Op */
|
||||
let mut data = vec![0x0D]; /* String Op */
|
||||
data.extend_from_slice(v.as_bytes());
|
||||
data.push(0x0); /* NullChar */
|
||||
data
|
||||
@@ -374,9 +367,7 @@ impl Memory32Fixed {
|
||||
|
||||
impl Aml for Memory32Fixed {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
bytes.push(0x86); /* Memory32Fixed */
|
||||
let mut bytes = vec![0x86]; /* Memory32Fixed */
|
||||
bytes.append(&mut 9u16.to_le_bytes().to_vec());
|
||||
|
||||
// 9 bytes of payload
|
||||
@@ -390,7 +381,7 @@ impl Aml for Memory32Fixed {
|
||||
#[derive(Copy, Clone)]
|
||||
enum AddressSpaceType {
|
||||
Memory,
|
||||
IO,
|
||||
Io,
|
||||
BusNumber,
|
||||
}
|
||||
|
||||
@@ -421,7 +412,7 @@ impl<T> AddressSpace<T> {
|
||||
|
||||
pub fn new_io(min: T, max: T) -> Self {
|
||||
AddressSpace {
|
||||
r#type: AddressSpaceType::IO,
|
||||
r#type: AddressSpaceType::Io,
|
||||
min,
|
||||
max,
|
||||
type_flags: 3, /* EntireRange */
|
||||
@@ -510,16 +501,16 @@ impl Aml for AddressSpace<u64> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IO {
|
||||
pub struct Io {
|
||||
min: u16,
|
||||
max: u16,
|
||||
alignment: u8,
|
||||
length: u8,
|
||||
}
|
||||
|
||||
impl IO {
|
||||
impl Io {
|
||||
pub fn new(min: u16, max: u16, alignment: u8, length: u8) -> Self {
|
||||
IO {
|
||||
Io {
|
||||
min,
|
||||
max,
|
||||
alignment,
|
||||
@@ -528,11 +519,10 @@ impl IO {
|
||||
}
|
||||
}
|
||||
|
||||
impl Aml for IO {
|
||||
impl Aml for Io {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
let mut bytes = vec![0x47]; /* Io Port Descriptor */
|
||||
|
||||
bytes.push(0x47); /* IO Port Descriptor */
|
||||
bytes.push(1); /* IODecode16 */
|
||||
bytes.append(&mut self.min.to_le_bytes().to_vec());
|
||||
bytes.append(&mut self.max.to_le_bytes().to_vec());
|
||||
@@ -571,9 +561,7 @@ impl Interrupt {
|
||||
|
||||
impl Aml for Interrupt {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
bytes.push(0x89); /* Extended IRQ Descriptor */
|
||||
let mut bytes = vec![0x89]; /* Extended IRQ Descriptor */
|
||||
bytes.append(&mut 6u16.to_le_bytes().to_vec());
|
||||
let flags = (self.shared as u8) << 3
|
||||
| (self.active_low as u8) << 2
|
||||
@@ -699,8 +687,7 @@ impl<'a> Return<'a> {
|
||||
|
||||
impl<'a> Aml for Return<'a> {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(0xa4); /* ReturnOp */
|
||||
let mut bytes = vec![0xa4]; /* ReturnOp */
|
||||
bytes.append(&mut self.value.to_aml_bytes());
|
||||
bytes
|
||||
}
|
||||
@@ -745,9 +732,9 @@ impl Field {
|
||||
) -> Self {
|
||||
Field {
|
||||
path,
|
||||
fields,
|
||||
access_type,
|
||||
update_rule,
|
||||
fields,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -788,14 +775,14 @@ impl Aml for Field {
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum OpRegionSpace {
|
||||
SystemMemory,
|
||||
SystemIO,
|
||||
PCIConfig,
|
||||
SystemIo,
|
||||
PConfig,
|
||||
EmbeddedControl,
|
||||
SMBus,
|
||||
SystemCMOS,
|
||||
Smbus,
|
||||
SystemCmos,
|
||||
PciBarTarget,
|
||||
IPMI,
|
||||
GeneralPurposeIO,
|
||||
Ipmi,
|
||||
GeneralPurposeIo,
|
||||
GenericSerialBus,
|
||||
}
|
||||
|
||||
@@ -864,8 +851,8 @@ impl<'a> Aml for If<'a> {
|
||||
}
|
||||
|
||||
pub struct Equal<'a> {
|
||||
right: &'a dyn Aml,
|
||||
left: &'a dyn Aml,
|
||||
right: &'a dyn Aml,
|
||||
}
|
||||
|
||||
impl<'a> Equal<'a> {
|
||||
@@ -876,8 +863,7 @@ impl<'a> Equal<'a> {
|
||||
|
||||
impl<'a> Aml for Equal<'a> {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(0x93); /* LEqualOp */
|
||||
let mut bytes = vec![0x93]; /* LEqualOp */
|
||||
bytes.extend_from_slice(&self.left.to_aml_bytes());
|
||||
bytes.extend_from_slice(&self.right.to_aml_bytes());
|
||||
bytes
|
||||
@@ -885,8 +871,8 @@ impl<'a> Aml for Equal<'a> {
|
||||
}
|
||||
|
||||
pub struct LessThan<'a> {
|
||||
right: &'a dyn Aml,
|
||||
left: &'a dyn Aml,
|
||||
right: &'a dyn Aml,
|
||||
}
|
||||
|
||||
impl<'a> LessThan<'a> {
|
||||
@@ -897,8 +883,7 @@ impl<'a> LessThan<'a> {
|
||||
|
||||
impl<'a> Aml for LessThan<'a> {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(0x95); /* LLessOp */
|
||||
let mut bytes = vec![0x95]; /* LLessOp */
|
||||
bytes.extend_from_slice(&self.left.to_aml_bytes());
|
||||
bytes.extend_from_slice(&self.right.to_aml_bytes());
|
||||
bytes
|
||||
@@ -940,8 +925,7 @@ impl<'a> Store<'a> {
|
||||
|
||||
impl<'a> Aml for Store<'a> {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(0x70); /* StoreOp */
|
||||
let mut bytes = vec![0x70]; /* StoreOp */
|
||||
bytes.extend_from_slice(&self.value.to_aml_bytes());
|
||||
bytes.extend_from_slice(&self.name.to_aml_bytes());
|
||||
bytes
|
||||
@@ -961,8 +945,7 @@ impl Mutex {
|
||||
|
||||
impl Aml for Mutex {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(0x5b); /* ExtOpPrefix */
|
||||
let mut bytes = vec![0x5b]; /* ExtOpPrefix */
|
||||
bytes.push(0x01); /* MutexOp */
|
||||
bytes.extend_from_slice(&self.path.to_aml_bytes());
|
||||
bytes.push(self.sync_level);
|
||||
@@ -983,8 +966,7 @@ impl Acquire {
|
||||
|
||||
impl Aml for Acquire {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(0x5b); /* ExtOpPrefix */
|
||||
let mut bytes = vec![0x5b]; /* ExtOpPrefix */
|
||||
bytes.push(0x23); /* AcquireOp */
|
||||
bytes.extend_from_slice(&self.mutex.to_aml_bytes());
|
||||
bytes.extend_from_slice(&self.timeout.to_le_bytes());
|
||||
@@ -1004,8 +986,7 @@ impl Release {
|
||||
|
||||
impl Aml for Release {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(0x5b); /* ExtOpPrefix */
|
||||
let mut bytes = vec![0x5b]; /* ExtOpPrefix */
|
||||
bytes.push(0x27); /* ReleaseOp */
|
||||
bytes.extend_from_slice(&self.mutex.to_aml_bytes());
|
||||
bytes
|
||||
@@ -1025,8 +1006,7 @@ impl<'a> Notify<'a> {
|
||||
|
||||
impl<'a> Aml for Notify<'a> {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(0x86); /* NotifyOp */
|
||||
let mut bytes = vec![0x86]; /* NotifyOp */
|
||||
bytes.extend_from_slice(&self.object.to_aml_bytes());
|
||||
bytes.extend_from_slice(&self.value.to_aml_bytes());
|
||||
bytes
|
||||
@@ -1076,14 +1056,13 @@ macro_rules! binary_op {
|
||||
|
||||
impl<'a> $name<'a> {
|
||||
pub fn new(target: &'a dyn Aml, a: &'a dyn Aml, b: &'a dyn Aml) -> Self {
|
||||
$name { target, a, b }
|
||||
$name { a, b, target }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Aml for $name<'a> {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push($opcode); /* Op for the binary operator */
|
||||
let mut bytes = vec![$opcode]; /* Op for the binary operator */
|
||||
bytes.extend_from_slice(&self.a.to_aml_bytes());
|
||||
bytes.extend_from_slice(&self.b.to_aml_bytes());
|
||||
bytes.extend_from_slice(&self.target.to_aml_bytes());
|
||||
@@ -1180,8 +1159,7 @@ impl<'a, T> CreateField<'a, T> {
|
||||
|
||||
impl<'a> Aml for CreateField<'a, u64> {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(0x8f); /* CreateQWordFieldOp */
|
||||
let mut bytes = vec![0x8f]; /* CreateQWordFieldOp */
|
||||
bytes.extend_from_slice(&self.buffer.to_aml_bytes());
|
||||
bytes.extend_from_slice(&self.offset.to_aml_bytes());
|
||||
bytes.extend_from_slice(&self.field.to_aml_bytes());
|
||||
@@ -1191,8 +1169,7 @@ impl<'a> Aml for CreateField<'a, u64> {
|
||||
|
||||
impl<'a> Aml for CreateField<'a, u32> {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(0x8a); /* CreateDWordFieldOp */
|
||||
let mut bytes = vec![0x8a]; /* CreateDWordFieldOp */
|
||||
bytes.extend_from_slice(&self.buffer.to_aml_bytes());
|
||||
bytes.extend_from_slice(&self.offset.to_aml_bytes());
|
||||
bytes.extend_from_slice(&self.field.to_aml_bytes());
|
||||
@@ -1235,12 +1212,12 @@ mod tests {
|
||||
Device::new(
|
||||
"_SB_.COM1".into(),
|
||||
vec![
|
||||
&Name::new("_HID".into(), &EISAName::new("PNP0501")),
|
||||
&Name::new("_HID".into(), &EisaName::new("PNP0501")),
|
||||
&Name::new(
|
||||
"_CRS".into(),
|
||||
&ResourceTemplate::new(vec![
|
||||
&Interrupt::new(true, true, false, false, 4),
|
||||
&IO::new(0x3f8, 0x3f8, 0, 0x8)
|
||||
&Io::new(0x3f8, 0x3f8, 0, 0x8)
|
||||
])
|
||||
)
|
||||
]
|
||||
@@ -1476,7 +1453,7 @@ mod tests {
|
||||
"_CRS".into(),
|
||||
&ResourceTemplate::new(vec![
|
||||
&Interrupt::new(true, true, false, false, 4),
|
||||
&IO::new(0x3f8, 0x3f8, 0, 0x8)
|
||||
&Io::new(0x3f8, 0x3f8, 0, 0x8)
|
||||
])
|
||||
)
|
||||
.to_aml_bytes(),
|
||||
@@ -1519,7 +1496,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_eisa_name() {
|
||||
assert_eq!(
|
||||
Name::new("_HID".into(), &EISAName::new("PNP0501")).to_aml_bytes(),
|
||||
Name::new("_HID".into(), &EisaName::new("PNP0501")).to_aml_bytes(),
|
||||
[0x08, 0x5F, 0x48, 0x49, 0x44, 0x0C, 0x41, 0xD0, 0x05, 0x01],
|
||||
)
|
||||
}
|
||||
@@ -1665,14 +1642,14 @@ mod tests {
|
||||
#[test]
|
||||
fn test_op_region() {
|
||||
/*
|
||||
OperationRegion (PRST, SystemIO, 0x0CD8, 0x0C)
|
||||
OperationRegion (PRST, SystemIo, 0x0CD8, 0x0C)
|
||||
*/
|
||||
let op_region_data = [
|
||||
0x5Bu8, 0x80, 0x50, 0x52, 0x53, 0x54, 0x01, 0x0B, 0xD8, 0x0C, 0x0A, 0x0C,
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
OpRegion::new("PRST".into(), OpRegionSpace::SystemIO, 0xcd8, 0xc).to_aml_bytes(),
|
||||
OpRegion::new("PRST".into(), OpRegionSpace::SystemIo, 0xcd8, 0xc).to_aml_bytes(),
|
||||
&op_region_data[..]
|
||||
);
|
||||
}
|
||||
@@ -1766,7 +1743,7 @@ mod tests {
|
||||
Device::new(
|
||||
"_SB_.MHPC".into(),
|
||||
vec![
|
||||
&Name::new("_HID".into(), &EISAName::new("PNP0A06")),
|
||||
&Name::new("_HID".into(), &EisaName::new("PNP0A06")),
|
||||
&mutex,
|
||||
&Method::new(
|
||||
"TEST".into(),
|
||||
@@ -1807,7 +1784,7 @@ mod tests {
|
||||
Device::new(
|
||||
"_SB_.MHPC".into(),
|
||||
vec![
|
||||
&Name::new("_HID".into(), &EISAName::new("PNP0A06")),
|
||||
&Name::new("_HID".into(), &EisaName::new("PNP0A06")),
|
||||
&Method::new(
|
||||
"TEST".into(),
|
||||
0,
|
||||
@@ -1848,7 +1825,7 @@ mod tests {
|
||||
Device::new(
|
||||
"_SB_.MHPC".into(),
|
||||
vec![
|
||||
&Name::new("_HID".into(), &EISAName::new("PNP0A06")),
|
||||
&Name::new("_HID".into(), &EisaName::new("PNP0A06")),
|
||||
&Method::new(
|
||||
"TEST".into(),
|
||||
0,
|
||||
|
||||
@@ -7,7 +7,7 @@ use vm_memory::ByteValued;
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct RSDP {
|
||||
pub struct Rsdp {
|
||||
pub signature: [u8; 8],
|
||||
pub checksum: u8,
|
||||
pub oem_id: [u8; 6],
|
||||
@@ -19,17 +19,17 @@ pub struct RSDP {
|
||||
_reserved: [u8; 3],
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for RSDP {}
|
||||
unsafe impl ByteValued for Rsdp {}
|
||||
|
||||
impl RSDP {
|
||||
impl Rsdp {
|
||||
pub fn new(oem_id: [u8; 6], xsdt_addr: u64) -> Self {
|
||||
let mut rsdp = RSDP {
|
||||
let mut rsdp = Rsdp {
|
||||
signature: *b"RSD PTR ",
|
||||
checksum: 0,
|
||||
oem_id,
|
||||
revision: 2,
|
||||
_rsdt_addr: 0,
|
||||
length: std::mem::size_of::<RSDP>() as u32,
|
||||
length: std::mem::size_of::<Rsdp>() as u32,
|
||||
xsdt_addr,
|
||||
extended_checksum: 0,
|
||||
_reserved: [0; 3],
|
||||
@@ -41,18 +41,18 @@ impl RSDP {
|
||||
}
|
||||
|
||||
pub fn len() -> usize {
|
||||
std::mem::size_of::<RSDP>()
|
||||
std::mem::size_of::<Rsdp>()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::RSDP;
|
||||
use super::Rsdp;
|
||||
use vm_memory::bytes::ByteValued;
|
||||
|
||||
#[test]
|
||||
fn test_rsdp() {
|
||||
let rsdp = RSDP::new(*b"CHYPER", 0xdead_beef);
|
||||
let rsdp = Rsdp::new(*b"CHYPER", 0xdead_beef);
|
||||
let sum = rsdp
|
||||
.as_slice()
|
||||
.iter()
|
||||
|
||||
@@ -22,14 +22,23 @@ impl GenericAddress {
|
||||
address: u64::from(address),
|
||||
}
|
||||
}
|
||||
pub fn mmio_address<T>(address: u64) -> Self {
|
||||
GenericAddress {
|
||||
address_space_id: 0,
|
||||
register_bit_width: 8 * std::mem::size_of::<T>() as u8,
|
||||
register_bit_offset: 0,
|
||||
access_size: std::mem::size_of::<T>() as u8,
|
||||
address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SDT {
|
||||
pub struct Sdt {
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
impl SDT {
|
||||
impl Sdt {
|
||||
pub fn new(
|
||||
signature: [u8; 4],
|
||||
length: u32,
|
||||
@@ -53,7 +62,7 @@ impl SDT {
|
||||
assert_eq!(data.len(), 36);
|
||||
|
||||
data.resize(length as usize, 0);
|
||||
let mut sdt = SDT { data };
|
||||
let mut sdt = Sdt { data };
|
||||
|
||||
sdt.update_checksum();
|
||||
sdt
|
||||
@@ -117,11 +126,11 @@ impl SDT {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SDT;
|
||||
use super::Sdt;
|
||||
|
||||
#[test]
|
||||
fn test_sdt() {
|
||||
let mut sdt = SDT::new(*b"TEST", 40, 1, *b"CLOUDH", *b"TESTTEST", 1);
|
||||
let mut sdt = Sdt::new(*b"TEST", 40, 1, *b"CLOUDH", *b"TESTTEST", 1);
|
||||
let sum: u8 = sdt
|
||||
.as_slice()
|
||||
.iter()
|
||||
|
||||
@@ -37,7 +37,7 @@ impl fmt::Display for Error {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum StatusCode {
|
||||
Continue,
|
||||
OK,
|
||||
Ok,
|
||||
NoContent,
|
||||
BadRequest,
|
||||
NotFound,
|
||||
@@ -50,7 +50,7 @@ impl StatusCode {
|
||||
fn from_raw(code: usize) -> StatusCode {
|
||||
match code {
|
||||
100 => StatusCode::Continue,
|
||||
200 => StatusCode::OK,
|
||||
200 => StatusCode::Ok,
|
||||
204 => StatusCode::NoContent,
|
||||
400 => StatusCode::BadRequest,
|
||||
404 => StatusCode::NotFound,
|
||||
@@ -69,18 +69,15 @@ impl StatusCode {
|
||||
fn is_server_error(self) -> bool {
|
||||
!matches!(
|
||||
self,
|
||||
StatusCode::OK | StatusCode::Continue | StatusCode::NoContent
|
||||
StatusCode::Ok | StatusCode::Continue | StatusCode::NoContent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
|
||||
let header_str = format!("{}: ", header);
|
||||
if let Some(o) = res.find(&header_str) {
|
||||
Some(&res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
res.find(&header_str)
|
||||
.map(|o| &res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
|
||||
}
|
||||
|
||||
fn get_status_code(res: &str) -> Result<StatusCode, Error> {
|
||||
|
||||
@@ -2,28 +2,26 @@
|
||||
name = "arch"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
acpi = ["acpi_tables"]
|
||||
tdx = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
byteorder = "1.3.4"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-memory = { version = "0.4.0", features = ["backend-mmap"] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
arch_gen = { path = "../arch_gen" }
|
||||
anyhow = "1.0"
|
||||
byteorder = "1.4.3"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.96"
|
||||
linux-loader = { version = "0.3.0", features = ["elf", "bzimage", "pe"] }
|
||||
log = "0.4.14"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
thiserror = "1.0"
|
||||
versionize = "0.1.6"
|
||||
versionize_derive = "0.1.4"
|
||||
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "master" }
|
||||
vm-memory = { version = "0.5.0", features = ["backend-mmap", "backend-bitmap"] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
|
||||
[dependencies.linux-loader]
|
||||
git = "https://github.com/rust-vmm/linux-loader"
|
||||
features = ["elf", "bzimage"]
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.7.3"
|
||||
|
||||
@@ -6,23 +6,22 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use libc::{c_char, c_int, c_void};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{CStr, CString, NulError};
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::Debug;
|
||||
use std::ptr::null;
|
||||
use std::{io, result};
|
||||
use std::result;
|
||||
|
||||
use super::super::DeviceType;
|
||||
use super::super::GuestMemoryMmap;
|
||||
use super::super::InitramfsConfig;
|
||||
use super::get_fdt_addr;
|
||||
use super::gic::GICDevice;
|
||||
use super::gic::GicDevice;
|
||||
use super::layout::{
|
||||
FDT_MAX_SIZE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, PCI_MMCONFIG_SIZE,
|
||||
PCI_MMCONFIG_START,
|
||||
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
||||
PCI_HIGH_BASE, PCI_MMCONFIG_SIZE, PCI_MMCONFIG_START,
|
||||
};
|
||||
use crate::aarch64::fdt::Error::CstringFDTTransform;
|
||||
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
||||
|
||||
// This is a value for uniquely identifying the FDT node declaring the interrupt controller.
|
||||
const GIC_PHANDLE: u32 = 1;
|
||||
@@ -30,6 +29,8 @@ const GIC_PHANDLE: u32 = 1;
|
||||
const MSI_PHANDLE: u32 = 2;
|
||||
// This is a value for uniquely identifying the FDT node containing the clock definition.
|
||||
const CLOCK_PHANDLE: u32 = 3;
|
||||
// This is a value for uniquely identifying the FDT node containing the gpio controller.
|
||||
const GPIO_PHANDLE: u32 = 4;
|
||||
|
||||
// Read the documentation specified when appending the root node to the FDT.
|
||||
const ADDRESS_CELLS: u32 = 0x2;
|
||||
@@ -45,24 +46,12 @@ const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
|
||||
const IRQ_TYPE_EDGE_RISING: u32 = 1;
|
||||
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
||||
|
||||
// This links to libfdt which handles the creation of the binary blob
|
||||
// flattened device tree (fdt) that is passed to the kernel and indicates
|
||||
// the hardware configuration of the machine.
|
||||
#[link(name = "fdt")]
|
||||
extern "C" {
|
||||
fn fdt_create(buf: *mut c_void, bufsize: c_int) -> c_int;
|
||||
fn fdt_finish_reservemap(fdt: *mut c_void) -> c_int;
|
||||
fn fdt_begin_node(fdt: *mut c_void, name: *const c_char) -> c_int;
|
||||
fn fdt_property(fdt: *mut c_void, name: *const c_char, val: *const c_void, len: c_int)
|
||||
-> c_int;
|
||||
fn fdt_end_node(fdt: *mut c_void) -> c_int;
|
||||
fn fdt_open_into(fdt: *const c_void, buf: *mut c_void, bufsize: c_int) -> c_int;
|
||||
fn fdt_finish(fdt: *const c_void) -> c_int;
|
||||
fn fdt_pack(fdt: *mut c_void) -> c_int;
|
||||
}
|
||||
// Keys and Buttons
|
||||
// System Power Down
|
||||
const KEY_POWER: u32 = 116;
|
||||
|
||||
/// Trait for devices to be added to the Flattened Device Tree.
|
||||
pub trait DeviceInfoForFDT {
|
||||
pub trait DeviceInfoForFdt {
|
||||
/// Returns the address where this device will be loaded.
|
||||
fn addr(&self) -> u64;
|
||||
/// Returns the associated interrupt for this device.
|
||||
@@ -74,53 +63,41 @@ pub trait DeviceInfoForFDT {
|
||||
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to append node to the FDT.
|
||||
AppendFDTNode(io::Error),
|
||||
/// Failed to append a property to the FDT.
|
||||
AppendFDTProperty(io::Error),
|
||||
/// Syscall for creating FDT failed.
|
||||
CreateFDT(io::Error),
|
||||
/// Failed to obtain a C style string.
|
||||
CstringFDTTransform(NulError),
|
||||
/// Failure in calling syscall for terminating this FDT.
|
||||
FinishFDTReserveMap(io::Error),
|
||||
/// Failure in writing FDT in memory.
|
||||
WriteFDTToMemory(GuestMemoryError),
|
||||
WriteFdtToMemory(GuestMemoryError),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Creates the flattened device tree for this aarch64 VM.
|
||||
pub fn create_fdt<T: DeviceInfoForFDT + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline: &CStr,
|
||||
vcpu_mpidr: Vec<u64>,
|
||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||
gic_device: &dyn GICDevice,
|
||||
gic_device: &dyn GicDevice,
|
||||
initrd: &Option<InitramfsConfig>,
|
||||
pci_space_address: &(u64, u64),
|
||||
) -> Result<Vec<u8>> {
|
||||
) -> FdtWriterResult<Vec<u8>> {
|
||||
// Allocate stuff necessary for the holding the blob.
|
||||
let mut fdt = vec![0; FDT_MAX_SIZE];
|
||||
|
||||
allocate_fdt(&mut fdt)?;
|
||||
let mut fdt = FdtWriter::new(&[]).unwrap();
|
||||
|
||||
// For an explanation why these nodes were introduced in the blob take a look at
|
||||
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L845
|
||||
// Look for "Required nodes and properties".
|
||||
|
||||
// Header or the root node as per above mentioned documentation.
|
||||
append_begin_node(&mut fdt, "")?;
|
||||
append_property_string(&mut fdt, "compatible", "linux,dummy-virt")?;
|
||||
let root_node = fdt.begin_node("")?;
|
||||
fdt.property_string("compatible", "linux,dummy-virt")?;
|
||||
// For info on #address-cells and size-cells read "Note about cells and address representation"
|
||||
// from the above mentioned txt file.
|
||||
append_property_u32(&mut fdt, "#address-cells", ADDRESS_CELLS)?;
|
||||
append_property_u32(&mut fdt, "#size-cells", SIZE_CELLS)?;
|
||||
fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
|
||||
fdt.property_u32("#size-cells", SIZE_CELLS)?;
|
||||
// This is not mandatory but we use it to point the root node to the node
|
||||
// containing description of the interrupt controller for this VM.
|
||||
append_property_u32(&mut fdt, "interrupt-parent", GIC_PHANDLE)?;
|
||||
fdt.property_u32("interrupt-parent", GIC_PHANDLE)?;
|
||||
create_cpu_nodes(&mut fdt, &vcpu_mpidr)?;
|
||||
create_memory_node(&mut fdt, guest_mem)?;
|
||||
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
||||
create_chosen_node(&mut fdt, cmdline.to_str().unwrap(), initrd)?;
|
||||
create_gic_node(&mut fdt, gic_device)?;
|
||||
create_timer_node(&mut fdt)?;
|
||||
create_clock_node(&mut fdt)?;
|
||||
@@ -129,307 +106,136 @@ pub fn create_fdt<T: DeviceInfoForFDT + Clone + Debug, S: ::std::hash::BuildHash
|
||||
create_pci_nodes(&mut fdt, pci_space_address.0, pci_space_address.1)?;
|
||||
|
||||
// End Header node.
|
||||
append_end_node(&mut fdt)?;
|
||||
fdt.end_node(root_node)?;
|
||||
|
||||
// Allocate another buffer so we can format and then write fdt to guest.
|
||||
let mut fdt_final = vec![0; FDT_MAX_SIZE];
|
||||
finish_fdt(&mut fdt, &mut fdt_final)?;
|
||||
let fdt_final = fdt.finish()?;
|
||||
|
||||
// Write FDT to memory.
|
||||
let fdt_address = GuestAddress(get_fdt_addr(&guest_mem));
|
||||
guest_mem
|
||||
.write_slice(fdt_final.as_slice(), fdt_address)
|
||||
.map_err(Error::WriteFDTToMemory)?;
|
||||
Ok(fdt_final)
|
||||
}
|
||||
|
||||
// Following are auxiliary functions for allocating and finishing the FDT.
|
||||
fn allocate_fdt(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
// Safe since we allocated this array with FDT_MAX_SIZE.
|
||||
let mut fdt_ret = unsafe { fdt_create(fdt.as_mut_ptr() as *mut c_void, FDT_MAX_SIZE as c_int) };
|
||||
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::CreateFDT(io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// The flattened device trees created with fdt_create() contains a list of
|
||||
// reserved memory areas. We need to call `fdt_finish_reservemap` so as to make sure that there is a
|
||||
// terminator in the reservemap list and whatever happened to be at the
|
||||
// start of the FDT data section would end up being interpreted as
|
||||
// reservemap entries.
|
||||
// Safe since we previously allocated this array.
|
||||
fdt_ret = unsafe { fdt_finish_reservemap(fdt.as_mut_ptr() as *mut c_void) };
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
|
||||
}
|
||||
pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
// Write FDT to memory.
|
||||
let fdt_address = GuestAddress(get_fdt_addr());
|
||||
guest_mem
|
||||
.write_slice(fdt_final.as_slice(), fdt_address)
|
||||
.map_err(Error::WriteFdtToMemory)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn finish_fdt(from_fdt: &mut Vec<u8>, to_fdt: &mut Vec<u8>) -> Result<()> {
|
||||
// Safe since we allocated `fdt_final` and previously passed in its size.
|
||||
let mut fdt_ret = unsafe { fdt_finish(from_fdt.as_mut_ptr() as *mut c_void) };
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Safe because we allocated both arrays with the correct size.
|
||||
fdt_ret = unsafe {
|
||||
fdt_open_into(
|
||||
from_fdt.as_mut_ptr() as *mut c_void,
|
||||
to_fdt.as_mut_ptr() as *mut c_void,
|
||||
FDT_MAX_SIZE as i32,
|
||||
)
|
||||
};
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Safe since we allocated `to_fdt`.
|
||||
fdt_ret = unsafe { fdt_pack(to_fdt.as_mut_ptr() as *mut c_void) };
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Following are auxiliary functions for appending nodes to FDT.
|
||||
fn append_begin_node(fdt: &mut Vec<u8>, name: &str) -> Result<()> {
|
||||
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
|
||||
|
||||
// Safe because we allocated fdt and converted name to a CString
|
||||
let fdt_ret = unsafe { fdt_begin_node(fdt.as_mut_ptr() as *mut c_void, cstr_name.as_ptr()) };
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::AppendFDTNode(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append_end_node(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
// Safe because we allocated fdt.
|
||||
let fdt_ret = unsafe { fdt_end_node(fdt.as_mut_ptr() as *mut c_void) };
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::AppendFDTNode(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Following are auxiliary functions for appending property nodes to the nodes of the FDT.
|
||||
fn append_property_u32(fdt: &mut Vec<u8>, name: &str, val: u32) -> Result<()> {
|
||||
append_property(fdt, name, &to_be32(val))
|
||||
}
|
||||
|
||||
fn append_property_u64(fdt: &mut Vec<u8>, name: &str, val: u64) -> Result<()> {
|
||||
append_property(fdt, name, &to_be64(val))
|
||||
}
|
||||
|
||||
fn append_property_string(fdt: &mut Vec<u8>, name: &str, value: &str) -> Result<()> {
|
||||
let cstr_value = CString::new(value).map_err(CstringFDTTransform)?;
|
||||
append_property_cstring(fdt, name, &cstr_value)
|
||||
}
|
||||
|
||||
fn append_property_cstring(fdt: &mut Vec<u8>, name: &str, cstr_value: &CStr) -> Result<()> {
|
||||
let value_bytes = cstr_value.to_bytes_with_nul();
|
||||
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
|
||||
// Safe because we allocated fdt, converted name and value to CStrings
|
||||
let fdt_ret = unsafe {
|
||||
fdt_property(
|
||||
fdt.as_mut_ptr() as *mut c_void,
|
||||
cstr_name.as_ptr(),
|
||||
value_bytes.as_ptr() as *mut c_void,
|
||||
value_bytes.len() as i32,
|
||||
)
|
||||
};
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::AppendFDTProperty(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append_property_null(fdt: &mut Vec<u8>, name: &str) -> Result<()> {
|
||||
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
|
||||
|
||||
// Safe because we allocated fdt, converted name to a CString
|
||||
let fdt_ret = unsafe {
|
||||
fdt_property(
|
||||
fdt.as_mut_ptr() as *mut c_void,
|
||||
cstr_name.as_ptr(),
|
||||
null(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::AppendFDTProperty(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append_property(fdt: &mut Vec<u8>, name: &str, val: &[u8]) -> Result<()> {
|
||||
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
|
||||
let val_ptr = val.as_ptr() as *const c_void;
|
||||
|
||||
// Safe because we allocated fdt and converted name to a CString
|
||||
let fdt_ret = unsafe {
|
||||
fdt_property(
|
||||
fdt.as_mut_ptr() as *mut c_void,
|
||||
cstr_name.as_ptr(),
|
||||
val_ptr,
|
||||
val.len() as i32,
|
||||
)
|
||||
};
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::AppendFDTProperty(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Auxiliary functions for writing u32/u64 numbers in big endian order.
|
||||
fn to_be32(input: u32) -> [u8; 4] {
|
||||
u32::to_be_bytes(input)
|
||||
}
|
||||
|
||||
fn to_be64(input: u64) -> [u8; 8] {
|
||||
u64::to_be_bytes(input)
|
||||
}
|
||||
|
||||
// Helper functions for generating a properly formatted byte vector using 32-bit/64-bit cells.
|
||||
fn generate_prop32(cells: &[u32]) -> Vec<u8> {
|
||||
let mut ret: Vec<u8> = Vec::new();
|
||||
for &e in cells {
|
||||
ret.extend(to_be32(e).iter());
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn generate_prop64(cells: &[u64]) -> Vec<u8> {
|
||||
let mut ret: Vec<u8> = Vec::new();
|
||||
for &e in cells {
|
||||
ret.extend(to_be64(e).iter());
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
|
||||
fn create_cpu_nodes(fdt: &mut Vec<u8>, vcpu_mpidr: &[u64]) -> Result<()> {
|
||||
fn create_cpu_nodes(fdt: &mut FdtWriter, vcpu_mpidr: &[u64]) -> FdtWriterResult<()> {
|
||||
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/arm/cpus.yaml.
|
||||
append_begin_node(fdt, "cpus")?;
|
||||
// As per documentation, on ARM v8 64-bit systems value should be set to 2.
|
||||
append_property_u32(fdt, "#address-cells", 0x02)?;
|
||||
append_property_u32(fdt, "#size-cells", 0x0)?;
|
||||
let cpus_node = fdt.begin_node("cpus")?;
|
||||
fdt.property_u32("#address-cells", 0x1)?;
|
||||
fdt.property_u32("#size-cells", 0x0)?;
|
||||
let num_cpus = vcpu_mpidr.len();
|
||||
|
||||
for (cpu_index, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
|
||||
let cpu_name = format!("cpu@{:x}", cpu_index);
|
||||
append_begin_node(fdt, &cpu_name)?;
|
||||
append_property_string(fdt, "device_type", "cpu")?;
|
||||
append_property_string(fdt, "compatible", "arm,arm-v8")?;
|
||||
for cpu_id in 0..num_cpus {
|
||||
let cpu_name = format!("cpu@{:x}", cpu_id);
|
||||
let cpu_node = fdt.begin_node(&cpu_name)?;
|
||||
fdt.property_string("device_type", "cpu")?;
|
||||
fdt.property_string("compatible", "arm,arm-v8")?;
|
||||
if num_cpus > 1 {
|
||||
// This is required on armv8 64-bit. See aforementioned documentation.
|
||||
append_property_string(fdt, "enable-method", "psci")?;
|
||||
fdt.property_string("enable-method", "psci")?;
|
||||
}
|
||||
// Set the field to first 24 bits of the MPIDR - Multiprocessor Affinity Register.
|
||||
// See http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0488c/BABHBJCI.html.
|
||||
append_property_u64(fdt, "reg", mpidr & 0x7FFFFF)?;
|
||||
append_end_node(fdt)?;
|
||||
fdt.property_u32("reg", cpu_id as u32)?;
|
||||
fdt.end_node(cpu_node)?;
|
||||
}
|
||||
append_end_node(fdt)?;
|
||||
fdt.end_node(cpus_node)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_memory_node(fdt: &mut Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
fn create_memory_node(fdt: &mut FdtWriter, guest_mem: &GuestMemoryMmap) -> FdtWriterResult<()> {
|
||||
let mem_size = guest_mem.last_addr().raw_value() - super::layout::RAM_64BIT_START + 1;
|
||||
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L960
|
||||
// for an explanation of this.
|
||||
let mem_reg_prop = generate_prop64(&[super::layout::RAM_64BIT_START as u64, mem_size as u64]);
|
||||
let mem_reg_prop = [super::layout::RAM_64BIT_START as u64, mem_size as u64];
|
||||
|
||||
let memory_node = fdt.begin_node("memory")?;
|
||||
fdt.property_string("device_type", "memory")?;
|
||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||
fdt.end_node(memory_node)?;
|
||||
|
||||
append_begin_node(fdt, "memory")?;
|
||||
append_property_string(fdt, "device_type", "memory")?;
|
||||
append_property(fdt, "reg", &mem_reg_prop)?;
|
||||
append_end_node(fdt)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_chosen_node(
|
||||
fdt: &mut Vec<u8>,
|
||||
cmdline: &CStr,
|
||||
fdt: &mut FdtWriter,
|
||||
cmdline: &str,
|
||||
initrd: &Option<InitramfsConfig>,
|
||||
) -> Result<()> {
|
||||
append_begin_node(fdt, "chosen")?;
|
||||
append_property_cstring(fdt, "bootargs", cmdline)?;
|
||||
) -> FdtWriterResult<()> {
|
||||
let chosen_node = fdt.begin_node("chosen")?;
|
||||
fdt.property_string("bootargs", cmdline)?;
|
||||
|
||||
if let Some(initrd_config) = initrd {
|
||||
append_property_u64(
|
||||
fdt,
|
||||
"linux,initrd-start",
|
||||
initrd_config.address.raw_value() as u64,
|
||||
)?;
|
||||
append_property_u64(
|
||||
fdt,
|
||||
"linux,initrd-end",
|
||||
initrd_config.address.raw_value() + initrd_config.size as u64,
|
||||
)?;
|
||||
let initrd_start = initrd_config.address.raw_value() as u64;
|
||||
let initrd_end = initrd_config.address.raw_value() + initrd_config.size as u64;
|
||||
fdt.property_u64("linux,initrd-start", initrd_start)?;
|
||||
fdt.property_u64("linux,initrd-end", initrd_end)?;
|
||||
}
|
||||
|
||||
append_end_node(fdt)?;
|
||||
fdt.end_node(chosen_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_gic_node(fdt: &mut Vec<u8>, gic_device: &dyn GICDevice) -> Result<()> {
|
||||
let gic_reg_prop = generate_prop64(gic_device.device_properties());
|
||||
fn create_gic_node(fdt: &mut FdtWriter, gic_device: &dyn GicDevice) -> FdtWriterResult<()> {
|
||||
let gic_reg_prop = gic_device.device_properties();
|
||||
|
||||
append_begin_node(fdt, "intc")?;
|
||||
append_property_string(fdt, "compatible", gic_device.fdt_compatibility())?;
|
||||
append_property_null(fdt, "interrupt-controller")?;
|
||||
let intc_node = fdt.begin_node("intc")?;
|
||||
|
||||
fdt.property_string("compatible", gic_device.fdt_compatibility())?;
|
||||
fdt.property_null("interrupt-controller")?;
|
||||
// "interrupt-cells" field specifies the number of cells needed to encode an
|
||||
// interrupt source. The type shall be a <u32> and the value shall be 3 if no PPI affinity description
|
||||
// is required.
|
||||
append_property_u32(fdt, "#interrupt-cells", 3)?;
|
||||
append_property(fdt, "reg", &gic_reg_prop)?;
|
||||
append_property_u32(fdt, "phandle", GIC_PHANDLE)?;
|
||||
append_property_u32(fdt, "#address-cells", 2)?;
|
||||
append_property_u32(fdt, "#size-cells", 2)?;
|
||||
append_property_null(fdt, "ranges")?;
|
||||
let gic_intr = [
|
||||
fdt.property_u32("#interrupt-cells", 3)?;
|
||||
fdt.property_array_u64("reg", &gic_reg_prop)?;
|
||||
fdt.property_u32("phandle", GIC_PHANDLE)?;
|
||||
fdt.property_u32("#address-cells", 2)?;
|
||||
fdt.property_u32("#size-cells", 2)?;
|
||||
fdt.property_null("ranges")?;
|
||||
|
||||
let gic_intr_prop = [
|
||||
GIC_FDT_IRQ_TYPE_PPI,
|
||||
gic_device.fdt_maint_irq(),
|
||||
IRQ_TYPE_LEVEL_HI,
|
||||
];
|
||||
let gic_intr_prop = generate_prop32(&gic_intr);
|
||||
|
||||
append_property(fdt, "interrupts", &gic_intr_prop)?;
|
||||
fdt.property_array_u32("interrupts", &gic_intr_prop)?;
|
||||
|
||||
if gic_device.msi_compatible() {
|
||||
append_begin_node(fdt, "msic")?;
|
||||
append_property_string(fdt, "compatible", gic_device.msi_compatibility())?;
|
||||
append_property_null(fdt, "msi-controller")?;
|
||||
append_property_u32(fdt, "phandle", MSI_PHANDLE)?;
|
||||
let msi_reg_prop = generate_prop64(gic_device.msi_properties());
|
||||
append_property(fdt, "reg", &msi_reg_prop)?;
|
||||
append_end_node(fdt)?;
|
||||
let msic_node = fdt.begin_node("msic")?;
|
||||
fdt.property_string("compatible", gic_device.msi_compatibility())?;
|
||||
fdt.property_null("msi-controller")?;
|
||||
fdt.property_u32("phandle", MSI_PHANDLE)?;
|
||||
let msi_reg_prop = gic_device.msi_properties();
|
||||
fdt.property_array_u64("reg", &msi_reg_prop)?;
|
||||
fdt.end_node(msic_node)?;
|
||||
}
|
||||
|
||||
append_end_node(fdt)?;
|
||||
fdt.end_node(intc_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_clock_node(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
fn create_clock_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
|
||||
// The Advanced Peripheral Bus (APB) is part of the Advanced Microcontroller Bus Architecture
|
||||
// (AMBA) protocol family. It defines a low-cost interface that is optimized for minimal power
|
||||
// consumption and reduced interface complexity.
|
||||
// PCLK is the clock source and this node defines exactly the clock for the APB.
|
||||
append_begin_node(fdt, "apb-pclk")?;
|
||||
append_property_string(fdt, "compatible", "fixed-clock")?;
|
||||
append_property_u32(fdt, "#clock-cells", 0x0)?;
|
||||
append_property_u32(fdt, "clock-frequency", 24000000)?;
|
||||
append_property_string(fdt, "clock-output-names", "clk24mhz")?;
|
||||
append_property_u32(fdt, "phandle", CLOCK_PHANDLE)?;
|
||||
append_end_node(fdt)?;
|
||||
let clock_node = fdt.begin_node("apb-pclk")?;
|
||||
fdt.property_string("compatible", "fixed-clock")?;
|
||||
fdt.property_u32("#clock-cells", 0x0)?;
|
||||
fdt.property_u32("clock-frequency", 24000000)?;
|
||||
fdt.property_string("clock-output-names", "clk24mhz")?;
|
||||
fdt.property_u32("phandle", CLOCK_PHANDLE)?;
|
||||
fdt.end_node(clock_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_timer_node(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
fn create_timer_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
|
||||
// See
|
||||
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/interrupt-controller/arch_timer.txt
|
||||
// These are fixed interrupt numbers for the timer device.
|
||||
@@ -442,93 +248,143 @@ fn create_timer_node(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
timer_reg_cells.push(irq);
|
||||
timer_reg_cells.push(IRQ_TYPE_LEVEL_HI);
|
||||
}
|
||||
let timer_reg_prop = generate_prop32(timer_reg_cells.as_slice());
|
||||
|
||||
append_begin_node(fdt, "timer")?;
|
||||
append_property_string(fdt, "compatible", compatible)?;
|
||||
append_property_null(fdt, "always-on")?;
|
||||
append_property(fdt, "interrupts", &timer_reg_prop)?;
|
||||
append_end_node(fdt)?;
|
||||
let timer_node = fdt.begin_node("timer")?;
|
||||
fdt.property_string("compatible", compatible)?;
|
||||
fdt.property_null("always-on")?;
|
||||
fdt.property_array_u32("interrupts", &timer_reg_cells)?;
|
||||
fdt.end_node(timer_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_psci_node(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
fn create_psci_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
|
||||
let compatible = "arm,psci-0.2";
|
||||
append_begin_node(fdt, "psci")?;
|
||||
append_property_string(fdt, "compatible", compatible)?;
|
||||
let psci_node = fdt.begin_node("psci")?;
|
||||
fdt.property_string("compatible", compatible)?;
|
||||
// Two methods available: hvc and smc.
|
||||
// As per documentation, PSCI calls between a guest and hypervisor may use the HVC conduit instead of SMC.
|
||||
// So, since we are using kvm, we need to use hvc.
|
||||
append_property_string(fdt, "method", "hvc")?;
|
||||
append_end_node(fdt)?;
|
||||
fdt.property_string("method", "hvc")?;
|
||||
fdt.end_node(psci_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_virtio_node<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
fdt: &mut Vec<u8>,
|
||||
fn create_virtio_node<T: DeviceInfoForFdt + Clone + Debug>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &T,
|
||||
) -> Result<()> {
|
||||
let device_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]);
|
||||
let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_EDGE_RISING]);
|
||||
) -> FdtWriterResult<()> {
|
||||
let device_reg_prop = [dev_info.addr(), dev_info.length()];
|
||||
let irq = [GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_EDGE_RISING];
|
||||
|
||||
append_begin_node(fdt, &format!("virtio_mmio@{:x}", dev_info.addr()))?;
|
||||
append_property_string(fdt, "compatible", "virtio,mmio")?;
|
||||
append_property(fdt, "reg", &device_reg_prop)?;
|
||||
append_property(fdt, "interrupts", &irq)?;
|
||||
append_property_u32(fdt, "interrupt-parent", GIC_PHANDLE)?;
|
||||
append_end_node(fdt)?;
|
||||
let virtio_node = fdt.begin_node(&format!("virtio_mmio@{:x}", dev_info.addr()))?;
|
||||
fdt.property_string("compatible", "virtio,mmio")?;
|
||||
fdt.property_array_u64("reg", &device_reg_prop)?;
|
||||
fdt.property_array_u32("interrupts", &irq)?;
|
||||
fdt.property_u32("interrupt-parent", GIC_PHANDLE)?;
|
||||
fdt.end_node(virtio_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_serial_node<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
fdt: &mut Vec<u8>,
|
||||
fn create_serial_node<T: DeviceInfoForFdt + Clone + Debug>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &T,
|
||||
) -> Result<()> {
|
||||
let serial_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]);
|
||||
let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_EDGE_RISING]);
|
||||
) -> FdtWriterResult<()> {
|
||||
let compatible = b"arm,pl011\0arm,primecell\0";
|
||||
let serial_reg_prop = [dev_info.addr(), dev_info.length()];
|
||||
let irq = [
|
||||
GIC_FDT_IRQ_TYPE_SPI,
|
||||
dev_info.irq() - IRQ_BASE,
|
||||
IRQ_TYPE_EDGE_RISING,
|
||||
];
|
||||
|
||||
append_begin_node(fdt, &format!("uart@{:x}", dev_info.addr()))?;
|
||||
append_property_string(fdt, "compatible", "ns16550a")?;
|
||||
append_property(fdt, "reg", &serial_reg_prop)?;
|
||||
append_property_u32(fdt, "clocks", CLOCK_PHANDLE)?;
|
||||
append_property_string(fdt, "clock-names", "apb_pclk")?;
|
||||
append_property(fdt, "interrupts", &irq)?;
|
||||
append_end_node(fdt)?;
|
||||
let serial_node = fdt.begin_node(&format!("pl011@{:x}", dev_info.addr()))?;
|
||||
fdt.property("compatible", compatible)?;
|
||||
fdt.property_array_u64("reg", &serial_reg_prop)?;
|
||||
fdt.property_u32("clocks", CLOCK_PHANDLE)?;
|
||||
fdt.property_string("clock-names", "apb_pclk")?;
|
||||
fdt.property_array_u32("interrupts", &irq)?;
|
||||
fdt.end_node(serial_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_rtc_node<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
fdt: &mut Vec<u8>,
|
||||
fn create_rtc_node<T: DeviceInfoForFdt + Clone + Debug>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &T,
|
||||
) -> Result<()> {
|
||||
) -> FdtWriterResult<()> {
|
||||
let compatible = b"arm,pl031\0arm,primecell\0";
|
||||
let rtc_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]);
|
||||
let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_LEVEL_HI]);
|
||||
append_begin_node(fdt, &format!("rtc@{:x}", dev_info.addr()))?;
|
||||
append_property(fdt, "compatible", compatible)?;
|
||||
append_property(fdt, "reg", &rtc_reg_prop)?;
|
||||
append_property(fdt, "interrupts", &irq)?;
|
||||
append_property_u32(fdt, "clocks", CLOCK_PHANDLE)?;
|
||||
append_property_string(fdt, "clock-names", "apb_pclk")?;
|
||||
append_end_node(fdt)?;
|
||||
let rtc_reg_prop = [dev_info.addr(), dev_info.length()];
|
||||
let irq = [
|
||||
GIC_FDT_IRQ_TYPE_SPI,
|
||||
dev_info.irq() - IRQ_BASE,
|
||||
IRQ_TYPE_LEVEL_HI,
|
||||
];
|
||||
|
||||
let rtc_node = fdt.begin_node(&format!("rtc@{:x}", dev_info.addr()))?;
|
||||
fdt.property("compatible", compatible)?;
|
||||
fdt.property_array_u64("reg", &rtc_reg_prop)?;
|
||||
fdt.property_array_u32("interrupts", &irq)?;
|
||||
fdt.property_u32("clocks", CLOCK_PHANDLE)?;
|
||||
fdt.property_string("clock-names", "apb_pclk")?;
|
||||
fdt.end_node(rtc_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_devices_node<T: DeviceInfoForFDT + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
fdt: &mut Vec<u8>,
|
||||
fn create_gpio_node<T: DeviceInfoForFdt + Clone + Debug>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &T,
|
||||
) -> FdtWriterResult<()> {
|
||||
// PL061 GPIO controller node
|
||||
let compatible = b"arm,pl061\0arm,primecell\0";
|
||||
let gpio_reg_prop = [dev_info.addr(), dev_info.length()];
|
||||
let irq = [
|
||||
GIC_FDT_IRQ_TYPE_SPI,
|
||||
dev_info.irq() - IRQ_BASE,
|
||||
IRQ_TYPE_EDGE_RISING,
|
||||
];
|
||||
|
||||
let gpio_node = fdt.begin_node(&format!("pl061@{:x}", dev_info.addr()))?;
|
||||
fdt.property("compatible", compatible)?;
|
||||
fdt.property_array_u64("reg", &gpio_reg_prop)?;
|
||||
fdt.property_array_u32("interrupts", &irq)?;
|
||||
fdt.property_null("gpio-controller")?;
|
||||
fdt.property_u32("#gpio-cells", 2)?;
|
||||
fdt.property_u32("clocks", CLOCK_PHANDLE)?;
|
||||
fdt.property_string("clock-names", "apb_pclk")?;
|
||||
fdt.property_u32("phandle", GPIO_PHANDLE)?;
|
||||
fdt.end_node(gpio_node)?;
|
||||
|
||||
// gpio-keys node
|
||||
let gpio_keys_node = fdt.begin_node("gpio-keys")?;
|
||||
fdt.property_string("compatible", "gpio-keys")?;
|
||||
fdt.property_u32("#size-cells", 0)?;
|
||||
fdt.property_u32("#address-cells", 1)?;
|
||||
let gpio_keys_poweroff_node = fdt.begin_node("button@1")?;
|
||||
fdt.property_string("label", "GPIO Key Poweroff")?;
|
||||
fdt.property_u32("linux,code", KEY_POWER)?;
|
||||
let gpios = [GPIO_PHANDLE, 3, 0];
|
||||
fdt.property_array_u32("gpios", &gpios)?;
|
||||
fdt.end_node(gpio_keys_poweroff_node)?;
|
||||
fdt.end_node(gpio_keys_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &HashMap<(DeviceType, String), T, S>,
|
||||
) -> Result<()> {
|
||||
) -> FdtWriterResult<()> {
|
||||
// Create one temp Vec to store all virtio devices
|
||||
let mut ordered_virtio_device: Vec<&T> = Vec::new();
|
||||
|
||||
for ((device_type, _device_id), info) in dev_info {
|
||||
match device_type {
|
||||
DeviceType::RTC => create_rtc_node(fdt, info)?,
|
||||
DeviceType::Gpio => create_gpio_node(fdt, info)?,
|
||||
DeviceType::Rtc => create_rtc_node(fdt, info)?,
|
||||
DeviceType::Serial => create_serial_node(fdt, info)?,
|
||||
DeviceType::Virtio(_) => {
|
||||
ordered_virtio_device.push(info);
|
||||
@@ -549,11 +405,40 @@ fn create_devices_node<T: DeviceInfoForFDT + Clone + Debug, S: ::std::hash::Buil
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_pci_nodes(fdt: &mut Vec<u8>, pci_device_base: u64, pci_device_size: u64) -> Result<()> {
|
||||
fn create_pci_nodes(
|
||||
fdt: &mut FdtWriter,
|
||||
pci_device_base: u64,
|
||||
pci_device_size: u64,
|
||||
) -> FdtWriterResult<()> {
|
||||
// Add node for PCIe controller.
|
||||
// See Documentation/devicetree/bindings/pci/host-generic-pci.txt in the kernel
|
||||
// and https://elinux.org/Device_Tree_Usage.
|
||||
let ranges = generate_prop32(&[
|
||||
|
||||
// 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
|
||||
// could fall bellow 4G.
|
||||
// Here we put it above 512G in FDT to workaround the EDK2 check.
|
||||
// But the address written in ACPI is not impacted.
|
||||
let pci_device_base_64bit: u64 = if cfg!(feature = "acpi") {
|
||||
pci_device_base + PCI_HIGH_BASE
|
||||
} else {
|
||||
pci_device_base
|
||||
};
|
||||
let pci_device_size_64bit: u64 = if cfg!(feature = "acpi") {
|
||||
pci_device_size - PCI_HIGH_BASE
|
||||
} else {
|
||||
pci_device_size
|
||||
};
|
||||
|
||||
let ranges = [
|
||||
// io addresses
|
||||
0x1000000,
|
||||
0_u32,
|
||||
0_u32,
|
||||
(MEM_PCI_IO_START.0 >> 32) as u32,
|
||||
MEM_PCI_IO_START.0 as u32,
|
||||
(MEM_PCI_IO_SIZE >> 32) as u32,
|
||||
MEM_PCI_IO_SIZE as u32,
|
||||
// mmio addresses
|
||||
0x2000000, // (ss = 10: 32-bit memory space)
|
||||
(MEM_32BIT_DEVICES_START.0 >> 32) as u32, // PCI address
|
||||
@@ -563,31 +448,31 @@ fn create_pci_nodes(fdt: &mut Vec<u8>, pci_device_base: u64, pci_device_size: u6
|
||||
(MEM_32BIT_DEVICES_SIZE >> 32) as u32, // size
|
||||
MEM_32BIT_DEVICES_SIZE as u32,
|
||||
// device addresses
|
||||
0x3000000, // (ss = 11: 64-bit memory space)
|
||||
(pci_device_base >> 32) as u32, // PCI address
|
||||
pci_device_base as u32,
|
||||
(pci_device_base >> 32) as u32, // CPU address
|
||||
pci_device_base as u32,
|
||||
(pci_device_size >> 32) as u32, // size
|
||||
pci_device_size as u32,
|
||||
]);
|
||||
let bus_range = generate_prop32(&[0, 0]); // Only bus 0
|
||||
let reg = generate_prop64(&[PCI_MMCONFIG_START.0, PCI_MMCONFIG_SIZE]);
|
||||
0x3000000, // (ss = 11: 64-bit memory space)
|
||||
(pci_device_base_64bit >> 32) as u32, // PCI address
|
||||
pci_device_base_64bit as u32,
|
||||
(pci_device_base_64bit >> 32) as u32, // CPU address
|
||||
pci_device_base_64bit as u32,
|
||||
(pci_device_size_64bit >> 32) as u32, // size
|
||||
pci_device_size_64bit as u32,
|
||||
];
|
||||
let bus_range = [0, 0]; // Only bus 0
|
||||
let reg = [PCI_MMCONFIG_START.0, PCI_MMCONFIG_SIZE];
|
||||
|
||||
append_begin_node(fdt, "pci")?;
|
||||
append_property_string(fdt, "compatible", "pci-host-ecam-generic")?;
|
||||
append_property_string(fdt, "device_type", "pci")?;
|
||||
append_property(fdt, "ranges", &ranges)?;
|
||||
append_property(fdt, "bus-range", &bus_range)?;
|
||||
append_property_u32(fdt, "#address-cells", 3)?;
|
||||
append_property_u32(fdt, "#size-cells", 2)?;
|
||||
append_property(fdt, "reg", ®)?;
|
||||
append_property_u32(fdt, "#interrupt-cells", 1)?;
|
||||
append_property_null(fdt, "interrupt-map")?;
|
||||
append_property_null(fdt, "interrupt-map-mask")?;
|
||||
append_property_null(fdt, "dma-coherent")?;
|
||||
append_property_u32(fdt, "msi-parent", MSI_PHANDLE)?;
|
||||
append_end_node(fdt)?;
|
||||
let pci_node = fdt.begin_node("pci")?;
|
||||
fdt.property_string("compatible", "pci-host-ecam-generic")?;
|
||||
fdt.property_string("device_type", "pci")?;
|
||||
fdt.property_array_u32("ranges", &ranges)?;
|
||||
fdt.property_array_u32("bus-range", &bus_range)?;
|
||||
fdt.property_u32("#address-cells", 3)?;
|
||||
fdt.property_u32("#size-cells", 2)?;
|
||||
fdt.property_array_u64("reg", ®)?;
|
||||
fdt.property_u32("#interrupt-cells", 1)?;
|
||||
fdt.property_null("interrupt-map")?;
|
||||
fdt.property_null("interrupt-map-mask")?;
|
||||
fdt.property_null("dma-coherent")?;
|
||||
fdt.property_u32("msi-parent", MSI_PHANDLE)?;
|
||||
fdt.end_node(pci_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,19 +4,24 @@
|
||||
pub mod kvm {
|
||||
use crate::aarch64::gic::dist_regs::{get_dist_regs, read_ctlr, set_dist_regs, write_ctlr};
|
||||
use crate::aarch64::gic::icc_regs::{get_icc_regs, set_icc_regs};
|
||||
use crate::aarch64::gic::kvm::{save_pending_tables, KvmGICDevice};
|
||||
use crate::aarch64::gic::redist_regs::{get_redist_regs, set_redist_regs};
|
||||
use crate::aarch64::gic::GICDevice;
|
||||
use crate::aarch64::gic::kvm::{save_pending_tables, KvmGicDevice};
|
||||
use crate::aarch64::gic::redist_regs::{
|
||||
construct_gicr_typers, get_redist_regs, set_redist_regs,
|
||||
};
|
||||
use crate::aarch64::gic::GicDevice;
|
||||
use crate::layout;
|
||||
use anyhow::anyhow;
|
||||
use hypervisor::kvm::kvm_bindings;
|
||||
use hypervisor::CpuState;
|
||||
use std::any::Any;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use std::{boxed::Box, result};
|
||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable,
|
||||
VersionMapped,
|
||||
};
|
||||
|
||||
/// Errors thrown while saving/restoring the GICv3.
|
||||
@@ -37,14 +42,14 @@ pub mod kvm {
|
||||
/// Error in restoring GIC redistributor registers.
|
||||
RestoreRedistributorRegisters(crate::aarch64::gic::Error),
|
||||
/// Error in saving GIC CPU interface registers.
|
||||
SaveICCRegisters(crate::aarch64::gic::Error),
|
||||
SaveIccRegisters(crate::aarch64::gic::Error),
|
||||
/// Error in restoring GIC CPU interface registers.
|
||||
RestoreICCRegisters(crate::aarch64::gic::Error),
|
||||
RestoreIccRegisters(crate::aarch64::gic::Error),
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
pub struct KvmGICv3 {
|
||||
pub struct KvmGicV3 {
|
||||
/// The hypervisor agnostic device
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
|
||||
@@ -58,7 +63,7 @@ pub mod kvm {
|
||||
vcpu_count: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Versionize)]
|
||||
pub struct Gicv3State {
|
||||
dist: Vec<u32>,
|
||||
rdist: Vec<u32>,
|
||||
@@ -67,34 +72,36 @@ pub mod kvm {
|
||||
gicd_ctlr: u32,
|
||||
}
|
||||
|
||||
impl KvmGICv3 {
|
||||
impl VersionMapped for Gicv3State {}
|
||||
|
||||
impl KvmGicV3 {
|
||||
// Unfortunately bindgen omits defines that are based on other defines.
|
||||
// See arch/arm64/include/uapi/asm/kvm.h file from the linux kernel.
|
||||
pub const SZ_64K: u64 = 0x0001_0000;
|
||||
const KVM_VGIC_V3_DIST_SIZE: u64 = KvmGICv3::SZ_64K;
|
||||
const KVM_VGIC_V3_REDIST_SIZE: u64 = (2 * KvmGICv3::SZ_64K);
|
||||
const KVM_VGIC_V3_DIST_SIZE: u64 = KvmGicV3::SZ_64K;
|
||||
const KVM_VGIC_V3_REDIST_SIZE: u64 = (2 * KvmGicV3::SZ_64K);
|
||||
|
||||
// Device trees specific constants
|
||||
pub const ARCH_GIC_V3_MAINT_IRQ: u32 = 9;
|
||||
|
||||
/// Get the address of the GIC distributor.
|
||||
pub fn get_dist_addr() -> u64 {
|
||||
layout::MAPPED_IO_START - KvmGICv3::KVM_VGIC_V3_DIST_SIZE
|
||||
layout::MAPPED_IO_START - KvmGicV3::KVM_VGIC_V3_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the size of the GIC distributor.
|
||||
pub fn get_dist_size() -> u64 {
|
||||
KvmGICv3::KVM_VGIC_V3_DIST_SIZE
|
||||
KvmGicV3::KVM_VGIC_V3_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the address of the GIC redistributors.
|
||||
pub fn get_redists_addr(vcpu_count: u64) -> u64 {
|
||||
KvmGICv3::get_dist_addr() - KvmGICv3::get_redists_size(vcpu_count)
|
||||
KvmGicV3::get_dist_addr() - KvmGicV3::get_redists_size(vcpu_count)
|
||||
}
|
||||
|
||||
/// Get the size of the GIC redistributors.
|
||||
pub fn get_redists_size(vcpu_count: u64) -> u64 {
|
||||
vcpu_count * KvmGICv3::KVM_VGIC_V3_REDIST_SIZE
|
||||
vcpu_count * KvmGicV3::KVM_VGIC_V3_REDIST_SIZE
|
||||
}
|
||||
|
||||
/// Save the state of GIC.
|
||||
@@ -112,7 +119,7 @@ pub mod kvm {
|
||||
.map_err(Error::SaveRedistributorRegisters)?;
|
||||
|
||||
let icc_state =
|
||||
get_icc_regs(&self.device(), &gicr_typers).map_err(Error::SaveICCRegisters)?;
|
||||
get_icc_regs(&self.device(), &gicr_typers).map_err(Error::SaveIccRegisters)?;
|
||||
|
||||
Ok(Gicv3State {
|
||||
dist: dist_state,
|
||||
@@ -134,13 +141,13 @@ pub mod kvm {
|
||||
.map_err(Error::RestoreRedistributorRegisters)?;
|
||||
|
||||
set_icc_regs(&self.device(), &gicr_typers, &state.icc)
|
||||
.map_err(Error::RestoreICCRegisters)?;
|
||||
.map_err(Error::RestoreIccRegisters)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GICDevice for KvmGICv3 {
|
||||
impl GicDevice for KvmGicV3 {
|
||||
fn device(&self) -> &Arc<dyn hypervisor::Device> {
|
||||
&self.device
|
||||
}
|
||||
@@ -150,7 +157,7 @@ pub mod kvm {
|
||||
}
|
||||
|
||||
fn fdt_maint_irq(&self) -> u32 {
|
||||
KvmGICv3::ARCH_GIC_V3_MAINT_IRQ
|
||||
KvmGicV3::ARCH_GIC_V3_MAINT_IRQ
|
||||
}
|
||||
|
||||
fn device_properties(&self) -> &[u64] {
|
||||
@@ -161,7 +168,8 @@ pub mod kvm {
|
||||
self.vcpu_count
|
||||
}
|
||||
|
||||
fn set_gicr_typers(&mut self, gicr_typers: Vec<u64>) {
|
||||
fn set_gicr_typers(&mut self, vcpu_states: &[CpuState]) {
|
||||
let gicr_typers = construct_gicr_typers(vcpu_states);
|
||||
self.gicr_typers = gicr_typers;
|
||||
}
|
||||
|
||||
@@ -170,7 +178,7 @@ pub mod kvm {
|
||||
}
|
||||
}
|
||||
|
||||
impl KvmGICDevice for KvmGICv3 {
|
||||
impl KvmGicDevice for KvmGicV3 {
|
||||
fn version() -> u32 {
|
||||
kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3
|
||||
}
|
||||
@@ -178,15 +186,15 @@ pub mod kvm {
|
||||
fn create_device(
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
vcpu_count: u64,
|
||||
) -> Box<dyn GICDevice> {
|
||||
Box::new(KvmGICv3 {
|
||||
) -> Box<dyn GicDevice> {
|
||||
Box::new(KvmGicV3 {
|
||||
device,
|
||||
gicr_typers: vec![0; vcpu_count.try_into().unwrap()],
|
||||
properties: [
|
||||
KvmGICv3::get_dist_addr(),
|
||||
KvmGICv3::get_dist_size(),
|
||||
KvmGICv3::get_redists_addr(vcpu_count),
|
||||
KvmGICv3::get_redists_size(vcpu_count),
|
||||
KvmGicV3::get_dist_addr(),
|
||||
KvmGicV3::get_dist_size(),
|
||||
KvmGicV3::get_redists_addr(vcpu_count),
|
||||
KvmGicV3::get_redists_size(vcpu_count),
|
||||
],
|
||||
vcpu_count,
|
||||
})
|
||||
@@ -194,7 +202,7 @@ pub mod kvm {
|
||||
|
||||
fn init_device_attributes(
|
||||
_vm: &Arc<dyn hypervisor::Vm>,
|
||||
gic_device: &dyn GICDevice,
|
||||
gic_device: &dyn GicDevice,
|
||||
) -> crate::aarch64::gic::Result<()> {
|
||||
/* Setting up the distributor attribute.
|
||||
We are placing the GIC below 1GB so we need to substract the size of the distributor.
|
||||
@@ -203,7 +211,7 @@ pub mod kvm {
|
||||
gic_device.device(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_DIST),
|
||||
&KvmGICv3::get_dist_addr() as *const u64 as u64,
|
||||
&KvmGicV3::get_dist_addr() as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
@@ -214,7 +222,7 @@ pub mod kvm {
|
||||
gic_device.device(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_REDIST),
|
||||
&KvmGICv3::get_redists_addr(gic_device.vcpu_count()) as *const u64 as u64,
|
||||
&KvmGicV3::get_redists_addr(gic_device.vcpu_count()) as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
@@ -223,53 +231,26 @@ pub mod kvm {
|
||||
}
|
||||
|
||||
pub const GIC_V3_SNAPSHOT_ID: &str = "gic-v3";
|
||||
impl Snapshottable for KvmGICv3 {
|
||||
impl Snapshottable for KvmGicV3 {
|
||||
fn id(&self) -> String {
|
||||
GIC_V3_SNAPSHOT_ID.to_string()
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let gicr_typers = self.gicr_typers.clone();
|
||||
let snapshot = serde_json::to_vec(&self.state(&gicr_typers).unwrap())
|
||||
.map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut gic_v3_snapshot = Snapshot::new(self.id().as_str());
|
||||
gic_v3_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id()),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(gic_v3_snapshot)
|
||||
Snapshot::new_from_versioned_state(&self.id(), &self.state(&gicr_typers).unwrap())
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(gic_v3_section) = snapshot
|
||||
.snapshot_data
|
||||
.get(&format!("{}-section", self.id()))
|
||||
{
|
||||
let gic_v3_state = match serde_json::from_slice(&gic_v3_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize GICv3 {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let gicr_typers = self.gicr_typers.clone();
|
||||
return self.set_state(&gicr_typers, &gic_v3_state).map_err(|e| {
|
||||
let gicr_typers = self.gicr_typers.clone();
|
||||
self.set_state(&gicr_typers, &snapshot.to_versioned_state(&self.id())?)
|
||||
.map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not restore GICv3 state {:?}", e))
|
||||
});
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find GICv3 snapshot section"
|
||||
)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for KvmGICv3 {}
|
||||
impl Transportable for KvmGICv3 {}
|
||||
impl Migratable for KvmGICv3 {}
|
||||
impl Pausable for KvmGicV3 {}
|
||||
impl Transportable for KvmGicV3 {}
|
||||
impl Migratable for KvmGicV3 {}
|
||||
}
|
||||
|
||||
@@ -3,22 +3,19 @@
|
||||
|
||||
pub mod kvm {
|
||||
use std::any::Any;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use std::{boxed::Box, result};
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
use crate::aarch64::gic::gicv3::kvm::KvmGICv3;
|
||||
use crate::aarch64::gic::kvm::KvmGICDevice;
|
||||
use crate::aarch64::gic::{Error, GICDevice};
|
||||
use crate::aarch64::gic::gicv3::kvm::KvmGicV3;
|
||||
use crate::aarch64::gic::kvm::KvmGicDevice;
|
||||
use crate::aarch64::gic::{Error, GicDevice};
|
||||
use hypervisor::kvm::kvm_bindings;
|
||||
use hypervisor::CpuState;
|
||||
|
||||
pub struct KvmGICv3ITS {
|
||||
pub struct KvmGicV3Its {
|
||||
/// The hypervisor agnostic device
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
|
||||
/// Vector holding values of GICR_TYPER for each vCPU
|
||||
gicr_typers: Vec<u64>,
|
||||
|
||||
/// GIC device properties, to be used for setting up the fdt entry
|
||||
gic_properties: [u64; 4],
|
||||
|
||||
@@ -29,19 +26,19 @@ pub mod kvm {
|
||||
vcpu_count: u64,
|
||||
}
|
||||
|
||||
impl KvmGICv3ITS {
|
||||
const KVM_VGIC_V3_ITS_SIZE: u64 = (2 * KvmGICv3::SZ_64K);
|
||||
impl KvmGicV3Its {
|
||||
const KVM_VGIC_V3_ITS_SIZE: u64 = (2 * KvmGicV3::SZ_64K);
|
||||
|
||||
fn get_msi_size() -> u64 {
|
||||
KvmGICv3ITS::KVM_VGIC_V3_ITS_SIZE
|
||||
KvmGicV3Its::KVM_VGIC_V3_ITS_SIZE
|
||||
}
|
||||
|
||||
fn get_msi_addr(vcpu_count: u64) -> u64 {
|
||||
KvmGICv3::get_redists_addr(vcpu_count) - KvmGICv3ITS::get_msi_size()
|
||||
KvmGicV3::get_redists_addr(vcpu_count) - KvmGicV3Its::get_msi_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl GICDevice for KvmGICv3ITS {
|
||||
impl GicDevice for KvmGicV3Its {
|
||||
fn device(&self) -> &Arc<dyn hypervisor::Device> {
|
||||
&self.device
|
||||
}
|
||||
@@ -59,7 +56,7 @@ pub mod kvm {
|
||||
}
|
||||
|
||||
fn fdt_maint_irq(&self) -> u32 {
|
||||
KvmGICv3::ARCH_GIC_V3_MAINT_IRQ
|
||||
KvmGicV3::ARCH_GIC_V3_MAINT_IRQ
|
||||
}
|
||||
|
||||
fn msi_properties(&self) -> &[u64] {
|
||||
@@ -74,36 +71,33 @@ pub mod kvm {
|
||||
self.vcpu_count
|
||||
}
|
||||
|
||||
fn set_gicr_typers(&mut self, gicr_typers: Vec<u64>) {
|
||||
self.gicr_typers = gicr_typers;
|
||||
}
|
||||
fn set_gicr_typers(&mut self, _vcpu_states: &[CpuState]) {}
|
||||
|
||||
fn as_any_concrete_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl KvmGICDevice for KvmGICv3ITS {
|
||||
impl KvmGicDevice for KvmGicV3Its {
|
||||
fn version() -> u32 {
|
||||
KvmGICv3::version()
|
||||
KvmGicV3::version()
|
||||
}
|
||||
|
||||
fn create_device(
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
vcpu_count: u64,
|
||||
) -> Box<dyn GICDevice> {
|
||||
Box::new(KvmGICv3ITS {
|
||||
) -> Box<dyn GicDevice> {
|
||||
Box::new(KvmGicV3Its {
|
||||
device,
|
||||
gicr_typers: vec![0; vcpu_count.try_into().unwrap()],
|
||||
gic_properties: [
|
||||
KvmGICv3::get_dist_addr(),
|
||||
KvmGICv3::get_dist_size(),
|
||||
KvmGICv3::get_redists_addr(vcpu_count),
|
||||
KvmGICv3::get_redists_size(vcpu_count),
|
||||
KvmGicV3::get_dist_addr(),
|
||||
KvmGicV3::get_dist_size(),
|
||||
KvmGicV3::get_redists_addr(vcpu_count),
|
||||
KvmGicV3::get_redists_size(vcpu_count),
|
||||
],
|
||||
msi_properties: [
|
||||
KvmGICv3ITS::get_msi_addr(vcpu_count),
|
||||
KvmGICv3ITS::get_msi_size(),
|
||||
KvmGicV3Its::get_msi_addr(vcpu_count),
|
||||
KvmGicV3Its::get_msi_size(),
|
||||
],
|
||||
vcpu_count,
|
||||
})
|
||||
@@ -111,9 +105,9 @@ pub mod kvm {
|
||||
|
||||
fn init_device_attributes(
|
||||
vm: &Arc<dyn hypervisor::Vm>,
|
||||
gic_device: &dyn GICDevice,
|
||||
gic_device: &dyn GicDevice,
|
||||
) -> Result<()> {
|
||||
KvmGICv3::init_device_attributes(vm, gic_device)?;
|
||||
KvmGicV3::init_device_attributes(vm, gic_device)?;
|
||||
|
||||
let mut its_device = kvm_bindings::kvm_create_device {
|
||||
type_: kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_ITS,
|
||||
@@ -123,13 +117,13 @@ pub mod kvm {
|
||||
|
||||
let its_fd = vm
|
||||
.create_device(&mut its_device)
|
||||
.map_err(Error::CreateGIC)?;
|
||||
.map_err(Error::CreateGic)?;
|
||||
|
||||
Self::set_device_attribute(
|
||||
&its_fd,
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_ITS_ADDR_TYPE),
|
||||
&KvmGICv3ITS::get_msi_addr(gic_device.vcpu_count()) as *const u64 as u64,
|
||||
&KvmGicV3Its::get_msi_addr(gic_device.vcpu_count()) as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ pub mod redist_regs;
|
||||
pub use self::dist_regs::{get_dist_regs, read_ctlr, set_dist_regs, write_ctlr};
|
||||
pub use self::icc_regs::{get_icc_regs, set_icc_regs};
|
||||
pub use self::redist_regs::{get_redist_regs, set_redist_regs};
|
||||
use hypervisor::CpuState;
|
||||
use std::any::Any;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
@@ -18,7 +19,7 @@ use std::sync::Arc;
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Error while calling KVM ioctl for setting up the global interrupt controller.
|
||||
CreateGIC(hypervisor::HypervisorVmError),
|
||||
CreateGic(hypervisor::HypervisorVmError),
|
||||
/// Error while setting device attributes for the GIC.
|
||||
SetDeviceAttribute(hypervisor::HypervisorDeviceError),
|
||||
/// Error while getting device attributes for the GIC.
|
||||
@@ -26,7 +27,7 @@ pub enum Error {
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
pub trait GICDevice: Send {
|
||||
pub trait GicDevice: Send {
|
||||
/// Returns the hypervisor agnostic Device of the GIC device
|
||||
fn device(&self) -> &Arc<dyn hypervisor::Device>;
|
||||
|
||||
@@ -58,23 +59,23 @@ pub trait GICDevice: Send {
|
||||
}
|
||||
|
||||
/// Get the values of GICR_TYPER for each vCPU.
|
||||
fn set_gicr_typers(&mut self, gicr_typers: Vec<u64>);
|
||||
fn set_gicr_typers(&mut self, vcpu_states: &[CpuState]);
|
||||
|
||||
/// Downcast the trait object to its concrete type.
|
||||
fn as_any_concrete_mut(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
|
||||
pub mod kvm {
|
||||
use super::GICDevice;
|
||||
use super::GicDevice;
|
||||
use super::Result;
|
||||
use crate::aarch64::gic::gicv3_its::kvm::KvmGICv3ITS;
|
||||
use crate::aarch64::gic::gicv3_its::kvm::KvmGicV3Its;
|
||||
use crate::layout;
|
||||
use hypervisor::kvm::kvm_bindings;
|
||||
use std::boxed::Box;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Trait for GIC devices.
|
||||
pub trait KvmGICDevice: Send + Sync + GICDevice {
|
||||
pub trait KvmGicDevice: Send + Sync + GicDevice {
|
||||
/// Returns the GIC version of the device
|
||||
fn version() -> u32;
|
||||
|
||||
@@ -82,12 +83,12 @@ pub mod kvm {
|
||||
fn create_device(
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
vcpu_count: u64,
|
||||
) -> Box<dyn GICDevice>;
|
||||
) -> Box<dyn GicDevice>;
|
||||
|
||||
/// Setup the device-specific attributes
|
||||
fn init_device_attributes(
|
||||
vm: &Arc<dyn hypervisor::Vm>,
|
||||
gic_device: &dyn GICDevice,
|
||||
gic_device: &dyn GicDevice,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Initialize a GIC device
|
||||
@@ -99,7 +100,7 @@ pub mod kvm {
|
||||
};
|
||||
|
||||
vm.create_device(&mut gic_device)
|
||||
.map_err(super::Error::CreateGIC)
|
||||
.map_err(super::Error::CreateGic)
|
||||
}
|
||||
|
||||
/// Set a GIC device attribute
|
||||
@@ -111,10 +112,10 @@ pub mod kvm {
|
||||
flags: u32,
|
||||
) -> Result<()> {
|
||||
let attr = kvm_bindings::kvm_device_attr {
|
||||
flags,
|
||||
group,
|
||||
attr,
|
||||
addr,
|
||||
flags,
|
||||
};
|
||||
device
|
||||
.set_device_attr(&attr)
|
||||
@@ -132,10 +133,10 @@ pub mod kvm {
|
||||
flags: u32,
|
||||
) -> Result<()> {
|
||||
let mut attr = kvm_bindings::kvm_device_attr {
|
||||
flags,
|
||||
group,
|
||||
attr,
|
||||
addr,
|
||||
flags,
|
||||
};
|
||||
device
|
||||
.get_device_attr(&mut attr)
|
||||
@@ -145,11 +146,11 @@ pub mod kvm {
|
||||
}
|
||||
|
||||
/// Finalize the setup of a GIC device
|
||||
fn finalize_device(gic_device: &dyn GICDevice) -> Result<()> {
|
||||
fn finalize_device(gic_device: &dyn GicDevice) -> Result<()> {
|
||||
/* We need to tell the kernel how many irqs to support with this vgic.
|
||||
* See the `layout` module for details.
|
||||
*/
|
||||
let nr_irqs: u32 = layout::IRQ_MAX - layout::IRQ_BASE + 1;
|
||||
let nr_irqs: u32 = layout::IRQ_NUM;
|
||||
let nr_irqs_ptr = &nr_irqs as *const u32;
|
||||
Self::set_device_attribute(
|
||||
gic_device.device(),
|
||||
@@ -175,7 +176,7 @@ pub mod kvm {
|
||||
|
||||
/// Method to initialize the GIC device
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
fn new(vm: &Arc<dyn hypervisor::Vm>, vcpu_count: u64) -> Result<Box<dyn GICDevice>> {
|
||||
fn new(vm: &Arc<dyn hypervisor::Vm>, vcpu_count: u64) -> Result<Box<dyn GicDevice>> {
|
||||
let vgic_fd = Self::init_device(vm)?;
|
||||
|
||||
let device = Self::create_device(vgic_fd, vcpu_count);
|
||||
@@ -190,9 +191,9 @@ pub mod kvm {
|
||||
|
||||
/// Create a GICv3-ITS device.
|
||||
///
|
||||
pub fn create_gic(vm: &Arc<dyn hypervisor::Vm>, vcpu_count: u64) -> Result<Box<dyn GICDevice>> {
|
||||
pub fn create_gic(vm: &Arc<dyn hypervisor::Vm>, vcpu_count: u64) -> Result<Box<dyn GicDevice>> {
|
||||
debug!("creating a GICv3-ITS");
|
||||
KvmGICv3ITS::new(vm, vcpu_count)
|
||||
KvmGicV3Its::new(vm, vcpu_count)
|
||||
}
|
||||
|
||||
/// Function that saves RDIST pending tables into guest RAM.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use super::{Error, Result};
|
||||
use hypervisor::kvm::kvm_bindings::{kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_REDIST_REGS};
|
||||
use hypervisor::CpuState;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Relevant redistributor registers that we want to save/restore.
|
||||
@@ -177,3 +178,34 @@ pub fn set_redist_regs(
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn construct_gicr_typers(vcpu_states: &[CpuState]) -> Vec<u64> {
|
||||
/* Pre-construct the GICR_TYPER:
|
||||
* For our implementation:
|
||||
* Top 32 bits are the affinity value of the associated CPU
|
||||
* CommonLPIAff == 01 (redistributors with same Aff3 share LPI table)
|
||||
* Processor_Number == CPU index starting from 0
|
||||
* DPGS == 0 (GICR_CTLR.DPG* not supported)
|
||||
* Last == 1 if this is the last redistributor in a series of
|
||||
* contiguous redistributor pages
|
||||
* DirectLPI == 0 (direct injection of LPIs not supported)
|
||||
* VLPIS == 0 (virtual LPIs not supported)
|
||||
* PLPIS == 0 (physical LPIs not supported)
|
||||
*/
|
||||
let mut gicr_typers: Vec<u64> = Vec::new();
|
||||
for (index, state) in vcpu_states.iter().enumerate() {
|
||||
let last = {
|
||||
if index == vcpu_states.len() - 1 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
//calculate affinity
|
||||
let mut cpu_affid = state.mpidr & 1095233437695;
|
||||
cpu_affid = ((cpu_affid & 0xFF00000000) >> 8) | (cpu_affid & 0xFFFFFF);
|
||||
gicr_typers.push((cpu_affid << 32) | (1 << 24) | (index as u64) << 8 | (last << 4));
|
||||
}
|
||||
|
||||
gicr_typers
|
||||
}
|
||||
|
||||
@@ -19,15 +19,14 @@
|
||||
// memory) | |
|
||||
// | DRAM |
|
||||
// | |
|
||||
// 2GB +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | Reserved |
|
||||
// | |
|
||||
// 1G+256M +---------------------------------------------------------------+
|
||||
// 1GB +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | PCI MMCONFIG space |
|
||||
// | |
|
||||
// 1GB +---------------------------------------------------------------+
|
||||
// 768 M +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | |
|
||||
// | PCI MMIO space |
|
||||
// | |
|
||||
@@ -36,8 +35,10 @@
|
||||
// | Legacy devices space |
|
||||
// | |
|
||||
// 144 M +---------------------------------------------------------------|
|
||||
// | |
|
||||
// | Reserved (now GIC is here) |
|
||||
// 64 M +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | UEFI space |
|
||||
// | |
|
||||
// 0GB +---------------------------------------------------------------+
|
||||
//
|
||||
@@ -45,42 +46,62 @@
|
||||
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
/// 0x0 ~ 0x400_0000 is reserved to uefi
|
||||
pub const UEFI_START: u64 = 0x0;
|
||||
pub const MEM_UEFI_START: GuestAddress = GuestAddress(0);
|
||||
pub const UEFI_SIZE: u64 = 0x0400_0000;
|
||||
|
||||
/// Below this address will reside the GIC, above this address will reside the MMIO devices.
|
||||
pub const MAPPED_IO_START: u64 = 0x0900_0000;
|
||||
|
||||
/// Space 0x0900_0000 ~ 0x1000_0000 is reserved for legacy devices.
|
||||
/// Space 0x0900_0000 ~ 0x0905_0000 is reserved for legacy devices.
|
||||
pub const LEGACY_SERIAL_MAPPED_IO_START: u64 = 0x0900_0000;
|
||||
pub const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
|
||||
pub const LEGACY_GPIO_MAPPED_IO_START: u64 = 0x0902_0000;
|
||||
|
||||
/// Legacy space will be allocated at once whiling setting up legacy devices.
|
||||
pub const LEGACY_DEVICES_MAPPED_IO_SIZE: u64 = 0x0700_0000;
|
||||
/// Space 0x0905_0000 ~ 0x0906_0000 is reserved for pcie io address
|
||||
pub const MEM_PCI_IO_START: GuestAddress = GuestAddress(0x0905_0000);
|
||||
pub const MEM_PCI_IO_SIZE: u64 = 0x10000;
|
||||
|
||||
/// Starting from 0x1000_0000 (256MiB), the 768MiB (ends at 1 GiB) is used for PCIE MMIO
|
||||
/// Starting from 0x1000_0000 (256MiB) to 0x3000_0000 (768MiB) is used for PCIE MMIO
|
||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = GuestAddress(0x1000_0000);
|
||||
pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x3000_0000;
|
||||
pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x2000_0000;
|
||||
|
||||
/// PCI MMCONFIG space (start: after the device space at 1 GiB, length: 256MiB)
|
||||
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x4000_0000);
|
||||
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x3000_0000);
|
||||
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
|
||||
|
||||
/// Start of RAM on 64 bit ARM.
|
||||
pub const RAM_64BIT_START: u64 = 0x8000_0000;
|
||||
pub const RAM_64BIT_START: u64 = 0x4000_0000;
|
||||
|
||||
/// Kernel command line maximum size.
|
||||
/// As per `arch/arm64/include/uapi/asm/setup.h`.
|
||||
pub const CMDLINE_MAX_SIZE: usize = 2048;
|
||||
|
||||
/// FDT is at the beginning of RAM.
|
||||
/// Maximum size of the device tree blob as specified in https://www.kernel.org/doc/Documentation/arm64/booting.txt.
|
||||
pub const FDT_START: u64 = RAM_64BIT_START;
|
||||
pub const FDT_MAX_SIZE: usize = 0x20_0000;
|
||||
|
||||
/// Put ACPI table above dtb
|
||||
pub const ACPI_START: u64 = RAM_64BIT_START + FDT_MAX_SIZE as u64;
|
||||
pub const ACPI_MAX_SIZE: usize = 0x20_0000;
|
||||
pub const RSDP_POINTER: GuestAddress = GuestAddress(ACPI_START);
|
||||
|
||||
/// Kernel start after FDT and ACPI
|
||||
pub const KERNEL_START: u64 = ACPI_START + ACPI_MAX_SIZE as u64;
|
||||
|
||||
/// Pci high memory base
|
||||
pub const PCI_HIGH_BASE: u64 = 0x80_0000_0000_u64;
|
||||
|
||||
// As per virt/kvm/arm/vgic/vgic-kvm-device.c we need
|
||||
// the number of interrupts our GIC will support to be:
|
||||
// * bigger than 32
|
||||
// * less than 1023 and
|
||||
// * a multiple of 32.
|
||||
// We are setting up our interrupt controller to support a maximum of 128 interrupts.
|
||||
/// First usable interrupt on aarch64.
|
||||
// We are setting up our interrupt controller to support a maximum of 256 interrupts.
|
||||
/// First usable interrupt on aarch64
|
||||
pub const IRQ_BASE: u32 = 32;
|
||||
|
||||
/// Last usable interrupt on aarch64.
|
||||
pub const IRQ_MAX: u32 = 159;
|
||||
/// Number of supported interrupts
|
||||
pub const IRQ_NUM: u32 = 256;
|
||||
|
||||
@@ -10,37 +10,43 @@ pub mod gic;
|
||||
pub mod layout;
|
||||
/// Logic for configuring aarch64 registers.
|
||||
pub mod regs;
|
||||
/// Module for loading UEFI binary.
|
||||
pub mod uefi;
|
||||
|
||||
pub use self::fdt::DeviceInfoForFDT;
|
||||
pub use self::fdt::DeviceInfoForFdt;
|
||||
use crate::DeviceType;
|
||||
use crate::GuestMemoryMmap;
|
||||
use crate::RegionType;
|
||||
use aarch64::gic::GICDevice;
|
||||
use gic::GicDevice;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
use vm_memory::{
|
||||
Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestMemoryMmap,
|
||||
GuestUsize,
|
||||
Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestUsize,
|
||||
};
|
||||
|
||||
/// Errors thrown while configuring aarch64 system.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to create a FDT.
|
||||
SetupFDT(fdt::Error),
|
||||
SetupFdt,
|
||||
|
||||
/// Failed to write FDT to memory.
|
||||
WriteFdtToMemory(fdt::Error),
|
||||
|
||||
/// Failed to create a GIC.
|
||||
SetupGIC(gic::Error),
|
||||
SetupGic(gic::Error),
|
||||
|
||||
/// Failed to compute the initramfs address.
|
||||
InitramfsAddress,
|
||||
|
||||
/// Error configuring the general purpose registers
|
||||
REGSConfiguration(regs::Error),
|
||||
RegsConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the MPIDR register
|
||||
VcpuRegMPIDR(hypervisor::HypervisorCpuError),
|
||||
VcpuRegMpidr(hypervisor::HypervisorCpuError),
|
||||
}
|
||||
|
||||
impl From<Error> for super::Error {
|
||||
@@ -63,7 +69,6 @@ pub fn configure_vcpu(
|
||||
id: u8,
|
||||
kernel_entry_point: Option<EntryPoint>,
|
||||
vm_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
_phys_bits: u8,
|
||||
) -> super::Result<u64> {
|
||||
if let Some(kernel_entry_point) = kernel_entry_point {
|
||||
regs::setup_regs(
|
||||
@@ -72,76 +77,86 @@ pub fn configure_vcpu(
|
||||
kernel_entry_point.entry_addr.raw_value(),
|
||||
&vm_memory.memory(),
|
||||
)
|
||||
.map_err(Error::REGSConfiguration)?;
|
||||
.map_err(Error::RegsConfiguration)?;
|
||||
}
|
||||
|
||||
let mpidr = fd.read_mpidr().map_err(Error::VcpuRegMPIDR)?;
|
||||
let mpidr = fd.read_mpidr().map_err(Error::VcpuRegMpidr)?;
|
||||
Ok(mpidr)
|
||||
}
|
||||
|
||||
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
|
||||
let mut regions = Vec::new();
|
||||
// 0 ~ 256 MiB: Reserved
|
||||
regions.push((
|
||||
GuestAddress(0),
|
||||
layout::MEM_32BIT_DEVICES_START.0 as usize,
|
||||
RegionType::Reserved,
|
||||
));
|
||||
// Normally UEFI should be loaded to a flash area at the beginning of memory.
|
||||
// But now flash memory type is not supported.
|
||||
// As a workaround, we take 64 MiB memory from the main RAM for UEFI.
|
||||
// As a result, the RAM that the guest can see is less than what has been
|
||||
// assigned in command line, when ACPI and UEFI is enabled.
|
||||
let ram_deduction = if cfg!(feature = "acpi") {
|
||||
layout::UEFI_SIZE
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// 256 MiB ~ 1 G: MMIO space
|
||||
regions.push((
|
||||
layout::MEM_32BIT_DEVICES_START,
|
||||
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
||||
RegionType::SubRegion,
|
||||
));
|
||||
|
||||
// 1G ~ 2G: reserved. The leading 256M for PCIe MMCONFIG space
|
||||
regions.push((
|
||||
layout::PCI_MMCONFIG_START,
|
||||
(layout::RAM_64BIT_START - layout::PCI_MMCONFIG_START.0) as usize,
|
||||
RegionType::Reserved,
|
||||
));
|
||||
|
||||
regions.push((
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
size as usize,
|
||||
RegionType::Ram,
|
||||
));
|
||||
|
||||
regions
|
||||
vec![
|
||||
// 0 ~ 64 MiB: Reserved for UEFI space
|
||||
#[cfg(feature = "acpi")]
|
||||
(GuestAddress(0), layout::UEFI_SIZE as usize, RegionType::Ram),
|
||||
#[cfg(not(feature = "acpi"))]
|
||||
(
|
||||
GuestAddress(0),
|
||||
layout::UEFI_SIZE as usize,
|
||||
RegionType::Reserved,
|
||||
),
|
||||
// 64 MiB ~ 256 MiB: Gic and legacy devices
|
||||
(
|
||||
GuestAddress(layout::UEFI_SIZE),
|
||||
(layout::MEM_32BIT_DEVICES_START.0 - layout::UEFI_SIZE) as usize,
|
||||
RegionType::Reserved,
|
||||
),
|
||||
// 256 MiB ~ 768 MiB: MMIO space
|
||||
(
|
||||
layout::MEM_32BIT_DEVICES_START,
|
||||
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
||||
RegionType::SubRegion,
|
||||
),
|
||||
// 768 MiB ~ 1 GiB: reserved. The leading 256M for PCIe MMCONFIG space
|
||||
(
|
||||
layout::PCI_MMCONFIG_START,
|
||||
layout::PCI_MMCONFIG_SIZE as usize,
|
||||
RegionType::Reserved,
|
||||
),
|
||||
// 1 GiB ~ : Ram
|
||||
(
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
(size - ram_deduction) as usize,
|
||||
RegionType::Ram,
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `guest_mem` - The memory to be used by the guest.
|
||||
/// * `num_cpus` - Number of virtual CPUs the guest will have.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn configure_system<T: DeviceInfoForFDT + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
vm: &Arc<dyn hypervisor::Vm>,
|
||||
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_cstring: &CStr,
|
||||
vcpu_count: u64,
|
||||
vcpu_mpidr: Vec<u64>,
|
||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||
initrd: &Option<super::InitramfsConfig>,
|
||||
pci_space_address: &(u64, u64),
|
||||
) -> super::Result<Box<dyn GICDevice>> {
|
||||
let gic_device = gic::kvm::create_gic(vm, vcpu_count).map_err(Error::SetupGIC)?;
|
||||
|
||||
fdt::create_fdt(
|
||||
gic_device: &dyn GicDevice,
|
||||
) -> super::Result<()> {
|
||||
let fdt_final = fdt::create_fdt(
|
||||
guest_mem,
|
||||
cmdline_cstring,
|
||||
vcpu_mpidr,
|
||||
device_info,
|
||||
&*gic_device,
|
||||
gic_device,
|
||||
initrd,
|
||||
pci_space_address,
|
||||
)
|
||||
.map_err(Error::SetupFDT)?;
|
||||
.map_err(|_| Error::SetupFdt)?;
|
||||
|
||||
Ok(gic_device)
|
||||
fdt::write_fdt_to_memory(fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the memory address where the initramfs could be loaded.
|
||||
@@ -150,8 +165,9 @@ pub fn initramfs_load_addr(
|
||||
initramfs_size: usize,
|
||||
) -> super::Result<u64> {
|
||||
let round_to_pagesize = |size| (size + (super::PAGE_SIZE - 1)) & !(super::PAGE_SIZE - 1);
|
||||
match GuestAddress(get_fdt_addr(&guest_mem))
|
||||
.checked_sub(round_to_pagesize(initramfs_size) as u64)
|
||||
match guest_mem
|
||||
.last_addr()
|
||||
.checked_sub(round_to_pagesize(initramfs_size) as u64 - 1)
|
||||
{
|
||||
Some(offset) => {
|
||||
if guest_mem.address_in_range(offset) {
|
||||
@@ -166,41 +182,31 @@ pub fn initramfs_load_addr(
|
||||
|
||||
/// Returns the memory address where the kernel could be loaded.
|
||||
pub fn get_kernel_start() -> u64 {
|
||||
layout::RAM_64BIT_START
|
||||
layout::KERNEL_START
|
||||
}
|
||||
|
||||
///Return guest memory address where the uefi should be loaded.
|
||||
pub fn get_uefi_start() -> u64 {
|
||||
layout::UEFI_START
|
||||
}
|
||||
|
||||
// Auxiliary function to get the address where the device tree blob is loaded.
|
||||
fn get_fdt_addr(mem: &GuestMemoryMmap) -> u64 {
|
||||
// If the memory allocated is smaller than the size allocated for the FDT,
|
||||
// we return the start of the DRAM so that
|
||||
// we allow the code to try and load the FDT.
|
||||
|
||||
if let Some(addr) = mem.last_addr().checked_sub(layout::FDT_MAX_SIZE as u64 - 1) {
|
||||
if mem.address_in_range(addr) {
|
||||
return addr.raw_value();
|
||||
}
|
||||
}
|
||||
|
||||
layout::RAM_64BIT_START
|
||||
fn get_fdt_addr() -> u64 {
|
||||
layout::FDT_START
|
||||
}
|
||||
|
||||
pub fn get_host_cpu_phys_bits() -> u8 {
|
||||
// The value returned here is used to determine the physical address space size
|
||||
// for a VM (IPA size).
|
||||
// In recent kernel versions, the maximum IPA size supported by the host can be
|
||||
// known by querying cap KVM_CAP_ARM_VM_IPA_SIZE. And the IPA size for a
|
||||
// guest can be configured smaller.
|
||||
// But in Cloud-Hypervisor we simply use the maximum value for the VM.
|
||||
// Reference https://lwn.net/Articles/766767/.
|
||||
//
|
||||
// The correct way to query KVM_CAP_ARM_VM_IPA_SIZE is via rust-vmm/kvm-ioctls,
|
||||
// which wraps all IOCTL's and provides easy interface to user hypervisors.
|
||||
// For now the cap hasn't been supported. A separate patch will be submitted to
|
||||
// rust-vmm to add it.
|
||||
// So a hardcoded value is used here as a temporary solution.
|
||||
// It will be replace once rust-vmm/kvm-ioctls is ready.
|
||||
//
|
||||
40
|
||||
// A dummy hypervisor created only for querying the host IPA size and will
|
||||
// be freed after the query.
|
||||
let hv = hypervisor::new().unwrap();
|
||||
let host_cpu_phys_bits = hv.get_host_ipa_limit().try_into().unwrap();
|
||||
if host_cpu_phys_bits == 0 {
|
||||
// Host kernel does not support `get_host_ipa_limit`,
|
||||
// we return the default value 40 here.
|
||||
40
|
||||
} else {
|
||||
host_cpu_phys_bits
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -210,38 +216,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_arch_memory_regions_dram() {
|
||||
let regions = arch_memory_regions((1usize << 32) as u64); //4GB
|
||||
assert_eq!(4, regions.len());
|
||||
assert_eq!(GuestAddress(layout::RAM_64BIT_START), regions[3].0);
|
||||
assert_eq!(1usize << 32, regions[3].1);
|
||||
assert_eq!(RegionType::Ram, regions[3].2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_fdt_addr() {
|
||||
let mut regions = Vec::new();
|
||||
|
||||
regions.push((
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
(layout::FDT_MAX_SIZE - 0x1000) as usize,
|
||||
));
|
||||
let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory");
|
||||
assert_eq!(get_fdt_addr(&mem), layout::RAM_64BIT_START);
|
||||
regions.clear();
|
||||
|
||||
regions.push((
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
(layout::FDT_MAX_SIZE) as usize,
|
||||
));
|
||||
let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory");
|
||||
assert_eq!(get_fdt_addr(&mem), layout::RAM_64BIT_START);
|
||||
regions.clear();
|
||||
|
||||
regions.push((
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
(layout::FDT_MAX_SIZE + 0x1000) as usize,
|
||||
));
|
||||
let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory");
|
||||
assert_eq!(get_fdt_addr(&mem), 0x1000 + layout::RAM_64BIT_START);
|
||||
regions.clear();
|
||||
assert_eq!(5, regions.len());
|
||||
assert_eq!(GuestAddress(layout::RAM_64BIT_START), regions[4].0);
|
||||
assert_eq!(1usize << 32, regions[4].1);
|
||||
assert_eq!(RegionType::Ram, regions[4].2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use super::get_fdt_addr;
|
||||
use crate::GuestMemoryMmap;
|
||||
use hypervisor::kvm::kvm_bindings::{
|
||||
kvm_regs, user_pt_regs, KVM_REG_ARM64, KVM_REG_ARM_CORE, KVM_REG_SIZE_U64,
|
||||
};
|
||||
use hypervisor::{arm64_core_reg_id, offset__of};
|
||||
use std::sync::Arc;
|
||||
use std::{mem, result};
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
|
||||
/// Errors thrown while setting aarch64 registers.
|
||||
#[derive(Debug)]
|
||||
@@ -47,7 +47,7 @@ pub fn setup_regs(
|
||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||
cpu_id: u8,
|
||||
boot_ip: u64,
|
||||
mem: &GuestMemoryMmap,
|
||||
_mem: &GuestMemoryMmap,
|
||||
) -> Result<()> {
|
||||
let kreg_off = offset__of!(kvm_regs, regs);
|
||||
|
||||
@@ -73,7 +73,7 @@ pub fn setup_regs(
|
||||
let regs0 = offset__of!(user_pt_regs, regs) + kreg_off;
|
||||
vcpu.set_reg(
|
||||
arm64_core_reg_id!(KVM_REG_SIZE_U64, regs0),
|
||||
get_fdt_addr(mem) as u64,
|
||||
get_fdt_addr() as u64,
|
||||
)
|
||||
.map_err(Error::SetCoreRegister)?;
|
||||
}
|
||||
|
||||
43
arch/src/aarch64/uefi.rs
Normal file
43
arch/src/aarch64/uefi.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::result;
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemory};
|
||||
|
||||
/// Errors thrown while loading UEFI binary
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Unable to seek to UEFI image start.
|
||||
SeekUefiStart,
|
||||
/// Unable to seek to UEFI image end.
|
||||
SeekUefiEnd,
|
||||
/// UEFI image too big.
|
||||
UefiTooBig,
|
||||
/// Unable to read UEFI image
|
||||
ReadUefiImage,
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
pub fn load_uefi<F, M: GuestMemory>(
|
||||
guest_mem: &M,
|
||||
guest_addr: GuestAddress,
|
||||
uefi_image: &mut F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: Read + Seek,
|
||||
{
|
||||
let uefi_size = uefi_image
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(|_| Error::SeekUefiEnd)? as usize;
|
||||
|
||||
// edk2 image on virtual platform is smaller than 3M
|
||||
if uefi_size > 0x300000 {
|
||||
return Err(Error::UefiTooBig);
|
||||
}
|
||||
uefi_image
|
||||
.seek(SeekFrom::Start(0))
|
||||
.map_err(|_| Error::SeekUefiStart)?;
|
||||
guest_mem
|
||||
.read_exact_from(guest_addr, uefi_image, uefi_size)
|
||||
.map_err(|_| Error::ReadUefiImage)
|
||||
}
|
||||
@@ -6,35 +6,16 @@
|
||||
|
||||
//! Implements platform specific functionality.
|
||||
//! Supported platforms: x86_64, aarch64.
|
||||
#![allow(
|
||||
clippy::unreadable_literal,
|
||||
clippy::redundant_static_lifetimes,
|
||||
clippy::cast_lossless,
|
||||
clippy::transmute_ptr_to_ptr,
|
||||
clippy::cast_ptr_alignment
|
||||
)]
|
||||
#![allow(clippy::transmute_ptr_to_ptr, clippy::redundant_static_lifetimes)]
|
||||
|
||||
extern crate anyhow;
|
||||
extern crate byteorder;
|
||||
extern crate hypervisor;
|
||||
extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg(feature = "acpi")]
|
||||
extern crate acpi_tables;
|
||||
extern crate arch_gen;
|
||||
extern crate linux_loader;
|
||||
extern crate serde;
|
||||
extern crate vm_memory;
|
||||
extern crate vm_migration;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::fmt;
|
||||
use std::result;
|
||||
|
||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||
|
||||
/// Type for returning error code.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -61,7 +42,7 @@ pub enum Error {
|
||||
/// Error writing module entry to guest memory.
|
||||
ModlistSetup(vm_memory::GuestMemoryError),
|
||||
/// RSDP Beyond Guest Memory
|
||||
RSDPPastRamEnd,
|
||||
RsdpPastRamEnd,
|
||||
}
|
||||
|
||||
/// Type for returning public functions outcome.
|
||||
@@ -92,9 +73,9 @@ pub mod aarch64;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use aarch64::{
|
||||
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFDT,
|
||||
get_host_cpu_phys_bits, get_kernel_start, initramfs_load_addr, layout,
|
||||
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, layout::IRQ_MAX, EntryPoint,
|
||||
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt,
|
||||
get_host_cpu_phys_bits, get_kernel_start, get_uefi_start, initramfs_load_addr, layout,
|
||||
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, uefi, EntryPoint,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -103,8 +84,8 @@ pub mod x86_64;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::{
|
||||
arch_memory_regions, configure_system, configure_vcpu, get_host_cpu_phys_bits,
|
||||
initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs,
|
||||
BootProtocol, CpuidPatch, CpuidReg, EntryPoint,
|
||||
initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs, CpuidPatch,
|
||||
CpuidReg, EntryPoint,
|
||||
};
|
||||
|
||||
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
||||
@@ -133,7 +114,10 @@ pub enum DeviceType {
|
||||
Serial,
|
||||
/// Device Type: RTC.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
RTC,
|
||||
Rtc,
|
||||
/// Device Type: GPIO.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
Gpio,
|
||||
}
|
||||
|
||||
/// Default (smallest) memory page size for the supported architectures.
|
||||
@@ -148,13 +132,13 @@ impl fmt::Display for DeviceType {
|
||||
/// Structure to describe MMIO device information
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub struct MMIODeviceInfo {
|
||||
pub struct MmioDeviceInfo {
|
||||
pub addr: u64,
|
||||
pub irq: u32,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
impl DeviceInfoForFDT for MMIODeviceInfo {
|
||||
impl DeviceInfoForFdt for MmioDeviceInfo {
|
||||
fn addr(&self) -> u64 {
|
||||
self.addr
|
||||
}
|
||||
@@ -162,6 +146,6 @@ impl DeviceInfoForFDT for MMIODeviceInfo {
|
||||
self.irq
|
||||
}
|
||||
fn length(&self) -> u64 {
|
||||
4096 as u64
|
||||
4096
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,21 +5,13 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use hypervisor::x86_64::LapicState;
|
||||
use std::io::Cursor;
|
||||
use std::mem;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use hypervisor::x86_64::LapicState;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
GetLapic(anyhow::Error),
|
||||
SetLapic(anyhow::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, hypervisor::HypervisorCpuError>;
|
||||
|
||||
// Defines poached from apicdef.h kernel header.
|
||||
@@ -83,10 +75,6 @@ pub fn set_lint(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
extern crate rand;
|
||||
use self::rand::Rng;
|
||||
|
||||
use super::*;
|
||||
|
||||
const KVM_APIC_REG_SIZE: usize = 0x400;
|
||||
@@ -110,8 +98,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_apic_delivery_mode() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut v: Vec<u32> = (0..20).map(|_| rng.gen::<u32>()).collect();
|
||||
let mut v: Vec<u32> = Vec::new();
|
||||
v.resize(20, 0);
|
||||
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
libc::getrandom(v.as_mut_ptr() as *mut _ as *mut libc::c_void, 80, 0),
|
||||
80
|
||||
);
|
||||
}
|
||||
|
||||
v.iter_mut()
|
||||
.for_each(|x| *x = set_apic_delivery_mode(*x, 2));
|
||||
|
||||
@@ -9,37 +9,26 @@
|
||||
use std::sync::Arc;
|
||||
pub mod interrupts;
|
||||
pub mod layout;
|
||||
mod mpspec;
|
||||
mod mptable;
|
||||
pub mod regs;
|
||||
use crate::GuestMemoryMmap;
|
||||
use crate::InitramfsConfig;
|
||||
use crate::RegionType;
|
||||
use hypervisor::{CpuId, CpuIdEntry, CPUID_FLAG_VALID_INDEX};
|
||||
use linux_loader::loader::bootparam::{boot_params, setup_header};
|
||||
use linux_loader::loader::bootparam::boot_params;
|
||||
use linux_loader::loader::elf::start_info::{
|
||||
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
|
||||
};
|
||||
use std::mem;
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
||||
GuestMemoryMmap, GuestMemoryRegion, GuestUsize,
|
||||
GuestMemoryRegion, GuestUsize,
|
||||
};
|
||||
mod smbios;
|
||||
use std::arch::x86_64;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum BootProtocol {
|
||||
LinuxBoot,
|
||||
PvhBoot,
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for BootProtocol {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
match self {
|
||||
BootProtocol::LinuxBoot => write!(f, "Linux 64-bit boot protocol"),
|
||||
BootProtocol::PvhBoot => write!(f, "PVH boot protocol"),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "tdx")]
|
||||
pub mod tdx;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
/// Specifies the entry point address where the guest must start
|
||||
@@ -48,10 +37,6 @@ impl ::std::fmt::Display for BootProtocol {
|
||||
pub struct EntryPoint {
|
||||
/// Address in guest memory where the guest must start execution
|
||||
pub entry_addr: GuestAddress,
|
||||
/// Specifies which boot protocol to use
|
||||
pub protocol: BootProtocol,
|
||||
/// This field is used for bzImage to fill zero page
|
||||
pub setup_header: Option<setup_header>,
|
||||
}
|
||||
|
||||
const E820_RAM: u32 = 1;
|
||||
@@ -138,23 +123,20 @@ unsafe impl ByteValued for BootParamsWrapper {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Invalid e820 setup params.
|
||||
E820Configuration,
|
||||
|
||||
/// Error writing MP table to memory.
|
||||
MpTableSetup(mptable::Error),
|
||||
|
||||
/// Error configuring the general purpose registers
|
||||
REGSConfiguration(regs::Error),
|
||||
RegsConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the special registers
|
||||
SREGSConfiguration(regs::Error),
|
||||
SregsConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the floating point related registers
|
||||
FPUConfiguration(regs::Error),
|
||||
FpuConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the MSR registers
|
||||
MSRSConfiguration(regs::Error),
|
||||
MsrsConfiguration(regs::Error),
|
||||
|
||||
/// Failed to set supported CPUs.
|
||||
SetSupportedCpusFailed(anyhow::Error),
|
||||
@@ -173,9 +155,6 @@ pub enum Error {
|
||||
|
||||
/// Missing SGX_LC CPU feature
|
||||
MissingSgxLaunchControlFeature,
|
||||
|
||||
// Error populating Cpuid
|
||||
PopulatingCpuid,
|
||||
}
|
||||
|
||||
impl From<Error> for super::Error {
|
||||
@@ -184,7 +163,7 @@ impl From<Error> for super::Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(dead_code, clippy::upper_case_acronyms)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum CpuidReg {
|
||||
EAX,
|
||||
@@ -336,87 +315,12 @@ pub fn configure_vcpu(
|
||||
vm_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
cpuid: CpuId,
|
||||
kvm_hyperv: bool,
|
||||
phys_bits: u8,
|
||||
) -> super::Result<()> {
|
||||
// Per vCPU CPUID changes; common are handled via CpuManager::generate_common_cpuid()
|
||||
let mut cpuid = cpuid;
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0xb, None, CpuidReg::EDX, u32::from(id));
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x1f, None, CpuidReg::EDX, u32::from(id));
|
||||
|
||||
if kvm_hyperv {
|
||||
// Remove conflicting entries
|
||||
cpuid.retain(|c| c.function != 0x4000_0000);
|
||||
cpuid.retain(|c| c.function != 0x4000_0001);
|
||||
|
||||
// See "Hypervisor Top Level Functional Specification" for details
|
||||
// Compliance with "Hv#1" requires leaves up to 0x4000_000a
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: 0x40000000,
|
||||
eax: 0x4000000a, // Maximum cpuid leaf
|
||||
ebx: 0x756e694c, // "Linu"
|
||||
ecx: 0x564b2078, // "x KV"
|
||||
edx: 0x7648204d, // "M Hv"
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: 0x40000001,
|
||||
eax: 0x31237648, // "Hv#1"
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: 0x40000002,
|
||||
eax: 0x3839, // "Build number"
|
||||
ebx: 0xa0000, // "Version"
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: 0x4000_0003,
|
||||
eax: 1 << 1 // AccessPartitionReferenceCounter
|
||||
| 1 << 2 // AccessSynicRegs
|
||||
| 1 << 3 // AccessSyntheticTimerRegs
|
||||
| 1 << 9, // AccessPartitionReferenceTsc
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
for i in 0x4000_0004..=0x4000_000a {
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: i,
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy CPU identification string
|
||||
for i in 0x8000_0002..=0x8000_0004 {
|
||||
cpuid.retain(|c| c.function != i);
|
||||
let leaf = unsafe { x86_64::__cpuid(i) };
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: i,
|
||||
eax: leaf.eax,
|
||||
ebx: leaf.ebx,
|
||||
ecx: leaf.ecx,
|
||||
edx: leaf.edx,
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
}
|
||||
|
||||
// Set CPU physical bits
|
||||
for entry in cpuid.as_mut_slice().iter_mut() {
|
||||
if entry.function == 0x8000_0008 {
|
||||
entry.eax = (entry.eax & 0xffff_ff00) | (phys_bits as u32 & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
fd.set_cpuid2(&cpuid)
|
||||
.map_err(|e| Error::SetSupportedCpusFailed(e.into()))?;
|
||||
|
||||
@@ -424,20 +328,13 @@ pub fn configure_vcpu(
|
||||
fd.enable_hyperv_synic().unwrap();
|
||||
}
|
||||
|
||||
regs::setup_msrs(fd).map_err(Error::MSRSConfiguration)?;
|
||||
regs::setup_msrs(fd).map_err(Error::MsrsConfiguration)?;
|
||||
if let Some(kernel_entry_point) = kernel_entry_point {
|
||||
// Safe to unwrap because this method is called after the VM is configured
|
||||
regs::setup_regs(
|
||||
fd,
|
||||
kernel_entry_point.entry_addr.raw_value(),
|
||||
layout::BOOT_STACK_POINTER.raw_value(),
|
||||
layout::ZERO_PAGE_START.raw_value(),
|
||||
kernel_entry_point.protocol,
|
||||
)
|
||||
.map_err(Error::REGSConfiguration)?;
|
||||
regs::setup_fpu(fd).map_err(Error::FPUConfiguration)?;
|
||||
regs::setup_sregs(&vm_memory.memory(), fd, kernel_entry_point.protocol)
|
||||
.map_err(Error::SREGSConfiguration)?;
|
||||
regs::setup_regs(fd, kernel_entry_point.entry_addr.raw_value())
|
||||
.map_err(Error::RegsConfiguration)?;
|
||||
regs::setup_fpu(fd).map_err(Error::FpuConfiguration)?;
|
||||
regs::setup_sregs(&vm_memory.memory(), fd).map_err(Error::SregsConfiguration)?;
|
||||
}
|
||||
interrupts::set_lint(fd).map_err(|e| Error::LocalIntConfiguration(e.into()))?;
|
||||
Ok(())
|
||||
@@ -502,12 +399,9 @@ pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, Region
|
||||
pub fn configure_system(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
cmdline_size: usize,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
_num_cpus: u8,
|
||||
setup_hdr: Option<setup_header>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
boot_prot: BootProtocol,
|
||||
sgx_epc_region: Option<SgxEpcRegion>,
|
||||
) -> super::Result<()> {
|
||||
let size = smbios::setup_smbios(guest_mem).map_err(Error::SmbiosSetup)?;
|
||||
@@ -520,34 +414,17 @@ pub fn configure_system(
|
||||
// Check that the RAM is not smaller than the RSDP start address
|
||||
if let Some(rsdp_addr) = rsdp_addr {
|
||||
if rsdp_addr.0 > guest_mem.last_addr().0 {
|
||||
return Err(super::Error::RSDPPastRamEnd);
|
||||
return Err(super::Error::RsdpPastRamEnd);
|
||||
}
|
||||
}
|
||||
|
||||
match boot_prot {
|
||||
BootProtocol::PvhBoot => {
|
||||
configure_pvh(
|
||||
guest_mem,
|
||||
cmdline_addr,
|
||||
initramfs,
|
||||
rsdp_addr,
|
||||
sgx_epc_region,
|
||||
)?;
|
||||
}
|
||||
BootProtocol::LinuxBoot => {
|
||||
configure_64bit_boot(
|
||||
guest_mem,
|
||||
cmdline_addr,
|
||||
cmdline_size,
|
||||
initramfs,
|
||||
setup_hdr,
|
||||
rsdp_addr,
|
||||
sgx_epc_region,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
configure_pvh(
|
||||
guest_mem,
|
||||
cmdline_addr,
|
||||
initramfs,
|
||||
rsdp_addr,
|
||||
sgx_epc_region,
|
||||
)
|
||||
}
|
||||
|
||||
fn configure_pvh(
|
||||
@@ -594,7 +471,7 @@ fn configure_pvh(
|
||||
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
||||
|
||||
// Create the memory map entries.
|
||||
add_memmap_entry(&mut memmap, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
|
||||
add_memmap_entry(&mut memmap, 0, layout::EBDA_START.raw_value(), E820_RAM);
|
||||
|
||||
let mem_end = guest_mem.last_addr();
|
||||
|
||||
@@ -604,21 +481,21 @@ fn configure_pvh(
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::HIGH_RAM_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
);
|
||||
} else {
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
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_memmap_entry(
|
||||
&mut memmap,
|
||||
layout::RAM_64BIT_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::RAM_64BIT_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,7 +504,7 @@ fn configure_pvh(
|
||||
layout::PCI_MMCONFIG_START.0,
|
||||
layout::PCI_MMCONFIG_SIZE,
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
);
|
||||
|
||||
if let Some(sgx_epc_region) = sgx_epc_region {
|
||||
add_memmap_entry(
|
||||
@@ -635,7 +512,7 @@ fn configure_pvh(
|
||||
sgx_epc_region.start().raw_value(),
|
||||
sgx_epc_region.size() as u64,
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
);
|
||||
}
|
||||
|
||||
start_info.0.memmap_entries = memmap.len() as u32;
|
||||
@@ -680,12 +557,7 @@ fn configure_pvh(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_memmap_entry(
|
||||
memmap: &mut Vec<hvm_memmap_table_entry>,
|
||||
addr: u64,
|
||||
size: u64,
|
||||
mem_type: u32,
|
||||
) -> Result<(), Error> {
|
||||
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
|
||||
memmap.push(hvm_memmap_table_entry {
|
||||
addr,
|
||||
@@ -693,123 +565,6 @@ fn add_memmap_entry(
|
||||
type_: mem_type,
|
||||
reserved: 0,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_64bit_boot(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
cmdline_size: usize,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
setup_hdr: Option<setup_header>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
sgx_epc_region: Option<SgxEpcRegion>,
|
||||
) -> super::Result<()> {
|
||||
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
|
||||
const KERNEL_HDR_MAGIC: u32 = 0x53726448;
|
||||
const KERNEL_LOADER_OTHER: u8 = 0xff;
|
||||
const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; // Must be non-zero.
|
||||
|
||||
let mut params: BootParamsWrapper = BootParamsWrapper(boot_params::default());
|
||||
|
||||
if let Some(hdr) = setup_hdr {
|
||||
// We should use the header if the loader provides one (e.g. from a bzImage).
|
||||
params.0.hdr = hdr;
|
||||
} else {
|
||||
params.0.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC;
|
||||
params.0.hdr.header = KERNEL_HDR_MAGIC;
|
||||
params.0.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
|
||||
};
|
||||
|
||||
// Common bootparams settings
|
||||
if params.0.hdr.type_of_loader == 0 {
|
||||
params.0.hdr.type_of_loader = KERNEL_LOADER_OTHER;
|
||||
}
|
||||
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||
params.0.hdr.cmdline_size = cmdline_size as u32;
|
||||
|
||||
if let Some(initramfs_config) = initramfs {
|
||||
params.0.hdr.ramdisk_image = initramfs_config.address.raw_value() as u32;
|
||||
params.0.hdr.ramdisk_size = initramfs_config.size as u32;
|
||||
}
|
||||
|
||||
add_e820_entry(&mut params.0, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
|
||||
|
||||
let mem_end = guest_mem.last_addr();
|
||||
if mem_end < layout::MEM_32BIT_RESERVED_START {
|
||||
add_e820_entry(
|
||||
&mut params.0,
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::HIGH_RAM_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
} else {
|
||||
add_e820_entry(
|
||||
&mut params.0,
|
||||
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.0,
|
||||
layout::RAM_64BIT_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::RAM_64BIT_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
add_e820_entry(
|
||||
&mut params.0,
|
||||
layout::PCI_MMCONFIG_START.0,
|
||||
layout::PCI_MMCONFIG_SIZE,
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
|
||||
if let Some(sgx_epc_region) = sgx_epc_region {
|
||||
add_e820_entry(
|
||||
&mut params.0,
|
||||
sgx_epc_region.start().raw_value(),
|
||||
sgx_epc_region.size() as u64,
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(rsdp_addr) = rsdp_addr {
|
||||
params.0.acpi_rsdp_addr = rsdp_addr.0;
|
||||
}
|
||||
|
||||
let zero_page_addr = layout::ZERO_PAGE_START;
|
||||
guest_mem
|
||||
.checked_offset(zero_page_addr, mem::size_of::<boot_params>())
|
||||
.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(())
|
||||
}
|
||||
|
||||
/// Returns the memory address where the initramfs could be loaded.
|
||||
@@ -970,11 +725,10 @@ pub fn update_cpuid_sgx(cpuid: &mut CpuId, epc_sections: Vec<SgxEpcSection>) ->
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use linux_loader::loader::bootparam::boot_e820_entry;
|
||||
|
||||
#[test]
|
||||
fn regions_lt_4gb() {
|
||||
let regions = arch_memory_regions(1 << 29 as GuestUsize);
|
||||
let regions = arch_memory_regions(1 << 29);
|
||||
assert_eq!(3, regions.len());
|
||||
assert_eq!(GuestAddress(0), regions[0].0);
|
||||
assert_eq!(1usize << 29, regions[0].1);
|
||||
@@ -982,7 +736,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn regions_gt_4gb() {
|
||||
let regions = arch_memory_regions((1 << 32 as GuestUsize) + 0x8000);
|
||||
let regions = arch_memory_regions((1 << 32) + 0x8000);
|
||||
assert_eq!(4, regions.len());
|
||||
assert_eq!(GuestAddress(0), regions[0].0);
|
||||
assert_eq!(GuestAddress(1 << 32), regions[1].0);
|
||||
@@ -995,12 +749,9 @@ mod tests {
|
||||
let config_err = configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
1,
|
||||
None,
|
||||
Some(layout::RSDP_POINTER),
|
||||
BootProtocol::LinuxBoot,
|
||||
None,
|
||||
);
|
||||
assert!(config_err.is_err());
|
||||
@@ -1014,31 +765,8 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::LinuxBoot,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::PvhBoot,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
configure_system(&gm, GuestAddress(0), &None, no_vcpus, None, None).unwrap();
|
||||
|
||||
// Now assigning some memory that is equal to the start of the 32bit memory hole.
|
||||
let mem_size = 3328 << 20;
|
||||
@@ -1049,31 +777,9 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::LinuxBoot,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
configure_system(&gm, GuestAddress(0), &None, no_vcpus, None, None).unwrap();
|
||||
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::PvhBoot,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
configure_system(&gm, GuestAddress(0), &None, no_vcpus, None, None).unwrap();
|
||||
|
||||
// Now assigning some memory that falls after the 32bit memory hole.
|
||||
let mem_size = 3330 << 20;
|
||||
@@ -1084,71 +790,9 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::LinuxBoot,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
configure_system(&gm, GuestAddress(0), &None, no_vcpus, None, None).unwrap();
|
||||
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::PvhBoot,
|
||||
None,
|
||||
)
|
||||
.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());
|
||||
configure_system(&gm, GuestAddress(0), &None, no_vcpus, None, None).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1170,8 +814,8 @@ mod tests {
|
||||
},
|
||||
];
|
||||
|
||||
add_memmap_entry(&mut memmap, 0, 0x1000, E820_RAM).unwrap();
|
||||
add_memmap_entry(&mut memmap, 0x10000, 0xa000, E820_RESERVED).unwrap();
|
||||
add_memmap_entry(&mut memmap, 0, 0x1000, E820_RAM);
|
||||
add_memmap_entry(&mut memmap, 0x10000, 0xa000, E820_RESERVED);
|
||||
|
||||
assert_eq!(format!("{:?}", memmap), format!("{:?}", expected_memmap));
|
||||
}
|
||||
|
||||
112
arch/src/x86_64/mpspec.rs
Normal file
112
arch/src/x86_64/mpspec.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
pub const MP_PROCESSOR: ::std::os::raw::c_uint = 0;
|
||||
pub const MP_BUS: ::std::os::raw::c_uint = 1;
|
||||
pub const MP_IOAPIC: ::std::os::raw::c_uint = 2;
|
||||
pub const MP_INTSRC: ::std::os::raw::c_uint = 3;
|
||||
pub const MP_LINTSRC: ::std::os::raw::c_uint = 4;
|
||||
pub const CPU_ENABLED: ::std::os::raw::c_uint = 1;
|
||||
pub const CPU_BOOTPROCESSOR: ::std::os::raw::c_uint = 2;
|
||||
pub const MPC_APIC_USABLE: ::std::os::raw::c_uint = 1;
|
||||
pub const MP_IRQDIR_DEFAULT: ::std::os::raw::c_uint = 0;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpf_intel {
|
||||
pub signature: [::std::os::raw::c_char; 4usize],
|
||||
pub physptr: ::std::os::raw::c_uint,
|
||||
pub length: ::std::os::raw::c_uchar,
|
||||
pub specification: ::std::os::raw::c_uchar,
|
||||
pub checksum: ::std::os::raw::c_uchar,
|
||||
pub feature1: ::std::os::raw::c_uchar,
|
||||
pub feature2: ::std::os::raw::c_uchar,
|
||||
pub feature3: ::std::os::raw::c_uchar,
|
||||
pub feature4: ::std::os::raw::c_uchar,
|
||||
pub feature5: ::std::os::raw::c_uchar,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpc_table {
|
||||
pub signature: [::std::os::raw::c_char; 4usize],
|
||||
pub length: ::std::os::raw::c_ushort,
|
||||
pub spec: ::std::os::raw::c_char,
|
||||
pub checksum: ::std::os::raw::c_char,
|
||||
pub oem: [::std::os::raw::c_char; 8usize],
|
||||
pub productid: [::std::os::raw::c_char; 12usize],
|
||||
pub oemptr: ::std::os::raw::c_uint,
|
||||
pub oemsize: ::std::os::raw::c_ushort,
|
||||
pub oemcount: ::std::os::raw::c_ushort,
|
||||
pub lapic: ::std::os::raw::c_uint,
|
||||
pub reserved: ::std::os::raw::c_uint,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpc_cpu {
|
||||
pub type_: ::std::os::raw::c_uchar,
|
||||
pub apicid: ::std::os::raw::c_uchar,
|
||||
pub apicver: ::std::os::raw::c_uchar,
|
||||
pub cpuflag: ::std::os::raw::c_uchar,
|
||||
pub cpufeature: ::std::os::raw::c_uint,
|
||||
pub featureflag: ::std::os::raw::c_uint,
|
||||
pub reserved: [::std::os::raw::c_uint; 2usize],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpc_bus {
|
||||
pub type_: ::std::os::raw::c_uchar,
|
||||
pub busid: ::std::os::raw::c_uchar,
|
||||
pub bustype: [::std::os::raw::c_uchar; 6usize],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpc_ioapic {
|
||||
pub type_: ::std::os::raw::c_uchar,
|
||||
pub apicid: ::std::os::raw::c_uchar,
|
||||
pub apicver: ::std::os::raw::c_uchar,
|
||||
pub flags: ::std::os::raw::c_uchar,
|
||||
pub apicaddr: ::std::os::raw::c_uint,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpc_intsrc {
|
||||
pub type_: ::std::os::raw::c_uchar,
|
||||
pub irqtype: ::std::os::raw::c_uchar,
|
||||
pub irqflag: ::std::os::raw::c_ushort,
|
||||
pub srcbus: ::std::os::raw::c_uchar,
|
||||
pub srcbusirq: ::std::os::raw::c_uchar,
|
||||
pub dstapic: ::std::os::raw::c_uchar,
|
||||
pub dstirq: ::std::os::raw::c_uchar,
|
||||
}
|
||||
|
||||
pub const MP_IRQ_SOURCE_TYPES_MP_INT: ::std::os::raw::c_uint = 0;
|
||||
pub const MP_IRQ_SOURCE_TYPES_MP_NMI: ::std::os::raw::c_uint = 1;
|
||||
pub const MP_IRQ_SOURCE_TYPES_MP_EXT_INT: ::std::os::raw::c_uint = 3;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpc_lintsrc {
|
||||
pub type_: ::std::os::raw::c_uchar,
|
||||
pub irqtype: ::std::os::raw::c_uchar,
|
||||
pub irqflag: ::std::os::raw::c_ushort,
|
||||
pub srcbusid: ::std::os::raw::c_uchar,
|
||||
pub srcbusirq: ::std::os::raw::c_uchar,
|
||||
pub destapic: ::std::os::raw::c_uchar,
|
||||
pub destapiclint: ::std::os::raw::c_uchar,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpc_oemtable {
|
||||
pub signature: [::std::os::raw::c_char; 4usize],
|
||||
pub length: ::std::os::raw::c_ushort,
|
||||
pub rev: ::std::os::raw::c_char,
|
||||
pub checksum: ::std::os::raw::c_char,
|
||||
pub mpc: [::std::os::raw::c_char; 8usize],
|
||||
}
|
||||
@@ -5,18 +5,15 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
||||
use crate::x86_64::mpspec;
|
||||
use crate::GuestMemoryMmap;
|
||||
use libc::c_char;
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::result;
|
||||
use std::slice;
|
||||
|
||||
use libc::c_char;
|
||||
|
||||
use arch_gen::x86::mpspec;
|
||||
use layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap,
|
||||
};
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `ByteValued`) where:
|
||||
@@ -222,7 +219,7 @@ pub fn setup_mptable(offset: GuestAddress, mem: &GuestMemoryMmap, num_cpus: u8)
|
||||
let size = mem::size_of::<MpcIntsrcWrapper>();
|
||||
let mut mpc_intsrc = MpcIntsrcWrapper(mpspec::mpc_intsrc::default());
|
||||
mpc_intsrc.0.type_ = mpspec::MP_INTSRC as u8;
|
||||
mpc_intsrc.0.irqtype = mpspec::mp_irq_source_types_mp_INT as u8;
|
||||
mpc_intsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_INT as u8;
|
||||
mpc_intsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
|
||||
mpc_intsrc.0.srcbus = 0;
|
||||
mpc_intsrc.0.srcbusirq = i;
|
||||
@@ -237,7 +234,7 @@ pub fn setup_mptable(offset: GuestAddress, mem: &GuestMemoryMmap, num_cpus: u8)
|
||||
let size = mem::size_of::<MpcLintsrcWrapper>();
|
||||
let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default());
|
||||
mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8;
|
||||
mpc_lintsrc.0.irqtype = mpspec::mp_irq_source_types_mp_ExtINT as u8;
|
||||
mpc_lintsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_EXT_INT as u8;
|
||||
mpc_lintsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
|
||||
mpc_lintsrc.0.srcbusid = 0;
|
||||
mpc_lintsrc.0.srcbusirq = 0;
|
||||
@@ -252,7 +249,7 @@ pub fn setup_mptable(offset: GuestAddress, mem: &GuestMemoryMmap, num_cpus: u8)
|
||||
let size = mem::size_of::<MpcLintsrcWrapper>();
|
||||
let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default());
|
||||
mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8;
|
||||
mpc_lintsrc.0.irqtype = mpspec::mp_irq_source_types_mp_NMI as u8;
|
||||
mpc_lintsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_NMI as u8;
|
||||
mpc_lintsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
|
||||
mpc_lintsrc.0.srcbusid = 0;
|
||||
mpc_lintsrc.0.srcbusirq = 0;
|
||||
@@ -287,7 +284,7 @@ pub fn setup_mptable(offset: GuestAddress, mem: &GuestMemoryMmap, num_cpus: u8)
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use layout::MPTABLE_START;
|
||||
use crate::layout::MPTABLE_START;
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
|
||||
fn table_entry_size(type_: u8) -> usize {
|
||||
|
||||
@@ -6,17 +6,14 @@
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
use std::sync::Arc;
|
||||
use std::{mem, result};
|
||||
|
||||
use super::BootProtocol;
|
||||
use crate::layout::{BOOT_GDT_START, BOOT_IDT_START, PVH_INFO_START};
|
||||
use crate::GuestMemoryMmap;
|
||||
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
||||
use hypervisor::arch::x86::regs::*;
|
||||
use hypervisor::x86_64::{FpuState, SpecialRegisters, StandardRegisters};
|
||||
use layout::{
|
||||
BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START, PML5_START, PVH_INFO_START,
|
||||
};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||
use std::sync::Arc;
|
||||
use std::{mem, result};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -25,25 +22,25 @@ pub enum Error {
|
||||
/// Failed to set base registers for this CPU.
|
||||
SetBaseRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Failed to configure the FPU.
|
||||
SetFPURegisters(hypervisor::HypervisorCpuError),
|
||||
SetFpuRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Setting up MSRs failed.
|
||||
SetModelSpecificRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Failed to set SREGs for this CPU.
|
||||
SetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Checking the GDT address failed.
|
||||
CheckGDTAddr,
|
||||
CheckGdtAddr,
|
||||
/// Writing the GDT to RAM failed.
|
||||
WriteGDT(GuestMemoryError),
|
||||
WriteGdt(GuestMemoryError),
|
||||
/// Writing the IDT to RAM failed.
|
||||
WriteIDT(GuestMemoryError),
|
||||
WriteIdt(GuestMemoryError),
|
||||
/// Writing PDPTE to RAM failed.
|
||||
WritePDPTEAddress(GuestMemoryError),
|
||||
WritePdpteAddress(GuestMemoryError),
|
||||
/// Writing PDE to RAM failed.
|
||||
WritePDEAddress(GuestMemoryError),
|
||||
WritePdeAddress(GuestMemoryError),
|
||||
/// Writing PML4 to RAM failed.
|
||||
WritePML4Address(GuestMemoryError),
|
||||
WritePml4Address(GuestMemoryError),
|
||||
/// Writing PML5 to RAM failed.
|
||||
WritePML5Address(GuestMemoryError),
|
||||
WritePml5Address(GuestMemoryError),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
@@ -60,7 +57,7 @@ pub fn setup_fpu(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
vcpu.set_fpu(&fpu).map_err(Error::SetFPURegisters)
|
||||
vcpu.set_fpu(&fpu).map_err(Error::SetFpuRegisters)
|
||||
}
|
||||
|
||||
/// Configure Model Specific Registers (MSRs) for a given CPU.
|
||||
@@ -81,32 +78,12 @@ pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
/// * `boot_ip` - Starting instruction pointer.
|
||||
/// * `boot_sp` - Starting stack pointer.
|
||||
/// * `boot_si` - Must point to zero page address per Linux ABI.
|
||||
pub fn setup_regs(
|
||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||
boot_ip: u64,
|
||||
boot_sp: u64,
|
||||
boot_si: u64,
|
||||
boot_prot: BootProtocol,
|
||||
) -> Result<()> {
|
||||
let regs: StandardRegisters = match boot_prot {
|
||||
// Configure regs as required by PVH boot protocol.
|
||||
BootProtocol::PvhBoot => StandardRegisters {
|
||||
rflags: 0x0000000000000002u64,
|
||||
rbx: PVH_INFO_START.raw_value(),
|
||||
rip: boot_ip,
|
||||
..Default::default()
|
||||
},
|
||||
// Configure regs as required by Linux 64-bit boot protocol.
|
||||
BootProtocol::LinuxBoot => StandardRegisters {
|
||||
rflags: 0x0000000000000002u64,
|
||||
rip: boot_ip,
|
||||
rsp: boot_sp,
|
||||
rbp: boot_sp,
|
||||
rsi: boot_si,
|
||||
..Default::default()
|
||||
},
|
||||
pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, boot_ip: u64) -> Result<()> {
|
||||
let regs = StandardRegisters {
|
||||
rflags: 0x0000000000000002u64,
|
||||
rbx: PVH_INFO_START.raw_value(),
|
||||
rip: boot_ip,
|
||||
..Default::default()
|
||||
};
|
||||
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
||||
}
|
||||
@@ -117,19 +94,9 @@ pub fn setup_regs(
|
||||
///
|
||||
/// * `mem` - The memory that will be passed to the guest.
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_sregs(
|
||||
mem: &GuestMemoryMmap,
|
||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||
boot_prot: BootProtocol,
|
||||
) -> Result<()> {
|
||||
pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
||||
|
||||
configure_segments_and_sregs(mem, &mut sregs, boot_prot)?;
|
||||
|
||||
if let BootProtocol::LinuxBoot = boot_prot {
|
||||
setup_page_tables(mem, &mut sregs)?; // TODO(dgreid) - Can this be done once per system instead?
|
||||
}
|
||||
|
||||
configure_segments_and_sregs(mem, &mut sregs)?;
|
||||
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
||||
}
|
||||
|
||||
@@ -140,8 +107,8 @@ fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
for (index, entry) in table.iter().enumerate() {
|
||||
let addr = guest_mem
|
||||
.checked_offset(boot_gdt_addr, index * mem::size_of::<u64>())
|
||||
.ok_or(Error::CheckGDTAddr)?;
|
||||
guest_mem.write_obj(*entry, addr).map_err(Error::WriteGDT)?;
|
||||
.ok_or(Error::CheckGdtAddr)?;
|
||||
guest_mem.write_obj(*entry, addr).map_err(Error::WriteGdt)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -150,33 +117,21 @@ fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
let boot_idt_addr = BOOT_IDT_START;
|
||||
guest_mem
|
||||
.write_obj(val, boot_idt_addr)
|
||||
.map_err(Error::WriteIDT)
|
||||
.map_err(Error::WriteIdt)
|
||||
}
|
||||
|
||||
pub fn configure_segments_and_sregs(
|
||||
mem: &GuestMemoryMmap,
|
||||
sregs: &mut SpecialRegisters,
|
||||
boot_prot: BootProtocol,
|
||||
) -> Result<()> {
|
||||
let gdt_table: [u64; BOOT_GDT_MAX as usize] = match boot_prot {
|
||||
BootProtocol::PvhBoot => {
|
||||
// Configure GDT entries as specified by PVH boot protocol
|
||||
[
|
||||
gdt_entry(0, 0, 0), // NULL
|
||||
gdt_entry(0xc09b, 0, 0xffffffff), // CODE
|
||||
gdt_entry(0xc093, 0, 0xffffffff), // DATA
|
||||
gdt_entry(0x008b, 0, 0x67), // TSS
|
||||
]
|
||||
}
|
||||
BootProtocol::LinuxBoot => {
|
||||
// Configure GDT entries as specified by Linux 64bit boot protocol
|
||||
[
|
||||
gdt_entry(0, 0, 0), // NULL
|
||||
gdt_entry(0xa09b, 0, 0xfffff), // CODE
|
||||
gdt_entry(0xc093, 0, 0xfffff), // DATA
|
||||
gdt_entry(0x808b, 0, 0xfffff), // TSS
|
||||
]
|
||||
}
|
||||
let gdt_table: [u64; BOOT_GDT_MAX as usize] = {
|
||||
// Configure GDT entries as specified by PVH boot protocol
|
||||
[
|
||||
gdt_entry(0, 0, 0), // NULL
|
||||
gdt_entry(0xc09b, 0, 0xffffffff), // CODE
|
||||
gdt_entry(0xc093, 0, 0xffffffff), // DATA
|
||||
gdt_entry(0x008b, 0, 0x67), // TSS
|
||||
]
|
||||
};
|
||||
|
||||
let code_seg = segment_from_gdt(gdt_table[1], 1);
|
||||
@@ -200,61 +155,17 @@ pub fn configure_segments_and_sregs(
|
||||
sregs.ss = data_seg;
|
||||
sregs.tr = tss_seg;
|
||||
|
||||
match boot_prot {
|
||||
BootProtocol::PvhBoot => {
|
||||
sregs.cr0 = CR0_PE;
|
||||
sregs.cr4 = 0;
|
||||
}
|
||||
BootProtocol::LinuxBoot => {
|
||||
/* 64-bit protected mode */
|
||||
sregs.cr0 |= CR0_PE;
|
||||
sregs.efer |= EFER_LME | EFER_LMA;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut SpecialRegisters) -> Result<()> {
|
||||
// Puts PML5 or PML4 right after zero page but aligned to 4k.
|
||||
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
|
||||
// Entry covering VA [0..256TB)
|
||||
mem.write_obj(PML4_START.raw_value() | 0x03, PML5_START)
|
||||
.map_err(Error::WritePML5Address)?;
|
||||
|
||||
sregs.cr3 = PML5_START.raw_value();
|
||||
sregs.cr4 |= CR4_LA57;
|
||||
} else {
|
||||
sregs.cr3 = PML4_START.raw_value();
|
||||
}
|
||||
|
||||
// Entry covering VA [0..512GB)
|
||||
mem.write_obj(PDPTE_START.raw_value() | 0x03, PML4_START)
|
||||
.map_err(Error::WritePML4Address)?;
|
||||
|
||||
// Entry covering VA [0..1GB)
|
||||
mem.write_obj(PDE_START.raw_value() | 0x03, PDPTE_START)
|
||||
.map_err(Error::WritePDPTEAddress)?;
|
||||
|
||||
// 512 2MB entries together covering VA [0..1GB). Note we are assuming
|
||||
// CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do.
|
||||
for i in 0..512 {
|
||||
mem.write_obj((i << 21) + 0x83u64, PDE_START.unchecked_add(i * 8))
|
||||
.map_err(Error::WritePDEAddress)?;
|
||||
}
|
||||
|
||||
sregs.cr4 |= CR4_PAE;
|
||||
sregs.cr0 |= CR0_PG;
|
||||
sregs.cr0 = CR0_PE;
|
||||
sregs.cr4 = 0;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate vm_memory;
|
||||
|
||||
use super::*;
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
use crate::GuestMemoryMmap;
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
fn create_guest_mem() -> GuestMemoryMmap {
|
||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
|
||||
@@ -268,36 +179,7 @@ mod tests {
|
||||
fn segments_and_sregs() {
|
||||
let mut sregs: SpecialRegisters = Default::default();
|
||||
let gm = create_guest_mem();
|
||||
configure_segments_and_sregs(&gm, &mut sregs, BootProtocol::LinuxBoot).unwrap();
|
||||
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||
assert_eq!(
|
||||
0xaf9b000000ffff,
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(8))
|
||||
);
|
||||
assert_eq!(
|
||||
0xcf93000000ffff,
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(16))
|
||||
);
|
||||
assert_eq!(
|
||||
0x8f8b000000ffff,
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(24))
|
||||
);
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_START));
|
||||
|
||||
assert_eq!(0, sregs.cs.base);
|
||||
assert_eq!(0xffffffff, sregs.ds.limit);
|
||||
assert_eq!(0x10, sregs.es.selector);
|
||||
assert_eq!(1, sregs.fs.present);
|
||||
assert_eq!(1, sregs.gs.g);
|
||||
assert_eq!(0, sregs.ss.avl);
|
||||
assert_eq!(0, sregs.tr.base);
|
||||
assert_eq!(0xffffffff, sregs.tr.limit);
|
||||
assert_eq!(0, sregs.tr.avl);
|
||||
assert_eq!(CR0_PE, sregs.cr0);
|
||||
assert_eq!(EFER_LME | EFER_LMA, sregs.efer);
|
||||
|
||||
configure_segments_and_sregs(&gm, &mut sregs, BootProtocol::PvhBoot).unwrap();
|
||||
configure_segments_and_sregs(&gm, &mut sregs).unwrap();
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||
assert_eq!(
|
||||
0xcf9b000000ffff,
|
||||
@@ -327,31 +209,4 @@ mod tests {
|
||||
assert_eq!(CR0_PE, sregs.cr0);
|
||||
assert_eq!(0, sregs.cr4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_tables() {
|
||||
let mut sregs: SpecialRegisters = Default::default();
|
||||
let gm = create_guest_mem();
|
||||
setup_page_tables(&gm, &mut sregs).unwrap();
|
||||
|
||||
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
|
||||
assert_eq!(0xa003, read_u64(&gm, PML5_START));
|
||||
}
|
||||
assert_eq!(0xb003, read_u64(&gm, PML4_START));
|
||||
assert_eq!(0xc003, read_u64(&gm, PDPTE_START));
|
||||
for i in 0..512 {
|
||||
assert_eq!(
|
||||
(i << 21) + 0x83u64,
|
||||
read_u64(&gm, PDE_START.unchecked_add(i * 8))
|
||||
);
|
||||
}
|
||||
|
||||
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
|
||||
assert_eq!(PML5_START.raw_value(), sregs.cr3);
|
||||
} else {
|
||||
assert_eq!(PML4_START.raw_value(), sregs.cr3);
|
||||
}
|
||||
assert_eq!(CR4_PAE, sregs.cr4);
|
||||
assert_eq!(CR0_PG, sregs.cr0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use layout::SMBIOS_START;
|
||||
use crate::layout::SMBIOS_START;
|
||||
use crate::GuestMemoryMmap;
|
||||
use std::fmt::{self, Display};
|
||||
use std::mem;
|
||||
use std::result;
|
||||
use std::slice;
|
||||
use vm_memory::ByteValued;
|
||||
use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryMmap};
|
||||
use vm_memory::{Address, Bytes, GuestAddress};
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[derive(Debug)]
|
||||
@@ -157,7 +158,7 @@ fn write_string(
|
||||
for c in val.as_bytes().iter() {
|
||||
curptr = write_and_incr(mem, *c, curptr)?;
|
||||
}
|
||||
curptr = write_and_incr(mem, 0 as u8, curptr)?;
|
||||
curptr = write_and_incr(mem, 0u8, curptr)?;
|
||||
Ok(curptr)
|
||||
}
|
||||
|
||||
@@ -170,55 +171,63 @@ pub fn setup_smbios(mem: &GuestMemoryMmap) -> Result<u64> {
|
||||
|
||||
{
|
||||
handle += 1;
|
||||
let mut smbios_biosinfo = SmbiosBiosInfo::default();
|
||||
smbios_biosinfo.typ = BIOS_INFORMATION;
|
||||
smbios_biosinfo.length = mem::size_of::<SmbiosBiosInfo>() as u8;
|
||||
smbios_biosinfo.handle = handle;
|
||||
smbios_biosinfo.vendor = 1; // First string written in this section
|
||||
smbios_biosinfo.version = 2; // Second string written in this section
|
||||
smbios_biosinfo.characteristics = PCI_SUPPORTED;
|
||||
smbios_biosinfo.characteristics_ext2 = IS_VIRTUAL_MACHINE;
|
||||
let smbios_biosinfo = SmbiosBiosInfo {
|
||||
typ: BIOS_INFORMATION,
|
||||
length: mem::size_of::<SmbiosBiosInfo>() as u8,
|
||||
handle,
|
||||
vendor: 1, // First string written in this section
|
||||
version: 2, // Second string written in this section
|
||||
characteristics: PCI_SUPPORTED,
|
||||
characteristics_ext2: IS_VIRTUAL_MACHINE,
|
||||
..Default::default()
|
||||
};
|
||||
curptr = write_and_incr(mem, smbios_biosinfo, curptr)?;
|
||||
curptr = write_string(mem, "cloud-hypervisor", curptr)?;
|
||||
curptr = write_string(mem, "0", curptr)?;
|
||||
curptr = write_and_incr(mem, 0 as u8, curptr)?;
|
||||
curptr = write_and_incr(mem, 0u8, curptr)?;
|
||||
}
|
||||
|
||||
{
|
||||
handle += 1;
|
||||
let mut smbios_sysinfo = SmbiosSysInfo::default();
|
||||
smbios_sysinfo.typ = SYSTEM_INFORMATION;
|
||||
smbios_sysinfo.length = mem::size_of::<SmbiosSysInfo>() as u8;
|
||||
smbios_sysinfo.handle = handle;
|
||||
smbios_sysinfo.manufacturer = 1; // First string written in this section
|
||||
smbios_sysinfo.product_name = 2; // Second string written in this section
|
||||
let smbios_sysinfo = SmbiosSysInfo {
|
||||
typ: SYSTEM_INFORMATION,
|
||||
length: mem::size_of::<SmbiosSysInfo>() as u8,
|
||||
handle,
|
||||
manufacturer: 1, // First string written in this section
|
||||
product_name: 2, // Second string written in this section
|
||||
..Default::default()
|
||||
};
|
||||
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
|
||||
curptr = write_string(mem, "Cloud Hypervisor", curptr)?;
|
||||
curptr = write_string(mem, "cloud-hypervisor", curptr)?;
|
||||
curptr = write_and_incr(mem, 0 as u8, curptr)?;
|
||||
curptr = write_and_incr(mem, 0u8, curptr)?;
|
||||
}
|
||||
|
||||
{
|
||||
handle += 1;
|
||||
let mut smbios_sysinfo = SmbiosSysInfo::default();
|
||||
smbios_sysinfo.typ = END_OF_TABLE;
|
||||
smbios_sysinfo.length = mem::size_of::<SmbiosSysInfo>() as u8;
|
||||
smbios_sysinfo.handle = handle;
|
||||
let smbios_sysinfo = SmbiosSysInfo {
|
||||
typ: END_OF_TABLE,
|
||||
length: mem::size_of::<SmbiosSysInfo>() as u8,
|
||||
handle,
|
||||
..Default::default()
|
||||
};
|
||||
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
|
||||
curptr = write_and_incr(mem, 0 as u8, curptr)?;
|
||||
curptr = write_and_incr(mem, 0u8, curptr)?;
|
||||
}
|
||||
|
||||
{
|
||||
let mut smbios_ep = Smbios30Entrypoint::default();
|
||||
smbios_ep.signature = *SM3_MAGIC_IDENT;
|
||||
smbios_ep.length = mem::size_of::<Smbios30Entrypoint>() as u8;
|
||||
// SMBIOS rev 3.2.0
|
||||
smbios_ep.majorver = 0x03;
|
||||
smbios_ep.minorver = 0x02;
|
||||
smbios_ep.docrev = 0x00;
|
||||
smbios_ep.revision = 0x01; // SMBIOS 3.0
|
||||
smbios_ep.max_size = curptr.unchecked_offset_from(physptr) as u32;
|
||||
smbios_ep.physptr = physptr.0;
|
||||
let mut smbios_ep = Smbios30Entrypoint {
|
||||
signature: *SM3_MAGIC_IDENT,
|
||||
length: mem::size_of::<Smbios30Entrypoint>() as u8,
|
||||
// SMBIOS rev 3.2.0
|
||||
majorver: 0x03,
|
||||
minorver: 0x02,
|
||||
docrev: 0x00,
|
||||
revision: 0x01, // SMBIOS 3.0
|
||||
max_size: curptr.unchecked_offset_from(physptr) as u32,
|
||||
physptr: physptr.0,
|
||||
..Default::default()
|
||||
};
|
||||
smbios_ep.checksum = compute_checksum(&smbios_ep);
|
||||
mem.write_obj(smbios_ep, GuestAddress(SMBIOS_START))
|
||||
.map_err(|_| Error::WriteSmbiosEp)?;
|
||||
|
||||
319
arch/src/x86_64/tdx/mod.rs
Normal file
319
arch/src/x86_64/tdx/mod.rs
Normal file
@@ -0,0 +1,319 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use crate::GuestMemoryMmap;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use thiserror::Error;
|
||||
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TdvfError {
|
||||
#[error("Failed read TDVF descriptor: {0}")]
|
||||
ReadDescriptor(#[source] std::io::Error),
|
||||
#[error("Failed read TDVF descriptor offset: {0}")]
|
||||
ReadDescriptorOffset(#[source] std::io::Error),
|
||||
#[error("Invalid descriptor signature")]
|
||||
InvalidDescriptorSignature,
|
||||
#[error("Invalid descriptor size")]
|
||||
InvalidDescriptorSize,
|
||||
#[error("Invalid descriptor version")]
|
||||
InvalidDescriptorVersion,
|
||||
#[error("Failed to write HOB details to guest memory: {0}")]
|
||||
GuestMemoryWriteHob(#[source] GuestMemoryError),
|
||||
}
|
||||
|
||||
// TDVF_DESCRIPTOR
|
||||
#[repr(packed)]
|
||||
pub struct TdvfDescriptor {
|
||||
signature: [u8; 4],
|
||||
length: u32,
|
||||
version: u32,
|
||||
num_sections: u32, // NumberOfSectionEntry
|
||||
}
|
||||
|
||||
// TDVF_SECTION
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct TdvfSection {
|
||||
pub data_offset: u32,
|
||||
pub data_size: u32, // RawDataSize
|
||||
pub address: u64, // MemoryAddress
|
||||
pub size: u64, // MemoryDataSize
|
||||
pub r#type: TdvfSectionType,
|
||||
pub attributes: u32,
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum TdvfSectionType {
|
||||
Bfv,
|
||||
Cfv,
|
||||
TdHob,
|
||||
TempMem,
|
||||
Reserved = 0xffffffff,
|
||||
}
|
||||
|
||||
impl Default for TdvfSectionType {
|
||||
fn default() -> Self {
|
||||
TdvfSectionType::Reserved
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_tdvf_sections(file: &mut File) -> Result<Vec<TdvfSection>, TdvfError> {
|
||||
// The 32-bit offset to the TDVF metadata is located 32 bytes from
|
||||
// the end of the file.
|
||||
// See "TDVF Metadata Pointer" in "TDX Virtual Firmware Design Guide
|
||||
file.seek(SeekFrom::End(-0x20))
|
||||
.map_err(TdvfError::ReadDescriptorOffset)?;
|
||||
|
||||
let mut descriptor_offset: [u8; 4] = [0; 4];
|
||||
file.read_exact(&mut descriptor_offset)
|
||||
.map_err(TdvfError::ReadDescriptorOffset)?;
|
||||
let descriptor_offset = u32::from_le_bytes(descriptor_offset) as u64;
|
||||
|
||||
file.seek(SeekFrom::Start(descriptor_offset))
|
||||
.map_err(TdvfError::ReadDescriptor)?;
|
||||
|
||||
let mut descriptor: TdvfDescriptor = unsafe { std::mem::zeroed() };
|
||||
// Safe as we read exactly the size of the descriptor header
|
||||
file.read_exact(unsafe {
|
||||
std::slice::from_raw_parts_mut(
|
||||
&mut descriptor as *mut _ as *mut u8,
|
||||
std::mem::size_of::<TdvfDescriptor>(),
|
||||
)
|
||||
})
|
||||
.map_err(TdvfError::ReadDescriptor)?;
|
||||
|
||||
if &descriptor.signature != b"TDVF" {
|
||||
return Err(TdvfError::InvalidDescriptorSignature);
|
||||
}
|
||||
|
||||
if descriptor.length as usize
|
||||
!= std::mem::size_of::<TdvfDescriptor>()
|
||||
+ std::mem::size_of::<TdvfSection>() * descriptor.num_sections as usize
|
||||
{
|
||||
return Err(TdvfError::InvalidDescriptorSize);
|
||||
}
|
||||
|
||||
if descriptor.version != 1 {
|
||||
return Err(TdvfError::InvalidDescriptorVersion);
|
||||
}
|
||||
|
||||
let mut sections = Vec::new();
|
||||
sections.resize_with(descriptor.num_sections as usize, TdvfSection::default);
|
||||
|
||||
// Safe as we read exactly the advertised sections
|
||||
file.read_exact(unsafe {
|
||||
std::slice::from_raw_parts_mut(
|
||||
sections.as_mut_ptr() as *mut u8,
|
||||
descriptor.num_sections as usize * std::mem::size_of::<TdvfSection>(),
|
||||
)
|
||||
})
|
||||
.map_err(TdvfError::ReadDescriptor)?;
|
||||
|
||||
Ok(sections)
|
||||
}
|
||||
|
||||
#[repr(u16)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum HobType {
|
||||
Handoff = 0x1,
|
||||
ResourceDescriptor = 0x3,
|
||||
Unused = 0xfffe,
|
||||
EndOfHobList = 0xffff,
|
||||
}
|
||||
|
||||
impl Default for HobType {
|
||||
fn default() -> Self {
|
||||
HobType::Unused
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct HobHeader {
|
||||
r#type: HobType,
|
||||
length: u16,
|
||||
reserved: u32,
|
||||
}
|
||||
unsafe impl ByteValued for HobHeader {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct HobHandoffInfoTable {
|
||||
header: HobHeader,
|
||||
version: u32,
|
||||
efi_memory_top: u64,
|
||||
efi_memory_bottom: u64,
|
||||
efi_free_memory_top: u64,
|
||||
efi_free_memory_bottom: u64,
|
||||
efi_end_of_hob_list: u64,
|
||||
}
|
||||
unsafe impl ByteValued for HobHandoffInfoTable {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct EfiGuid {
|
||||
data1: u32,
|
||||
data2: u16,
|
||||
data3: u16,
|
||||
data4: [u8; 8],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct HobResourceDescriptor {
|
||||
header: HobHeader,
|
||||
owner: EfiGuid,
|
||||
resource_type: u32,
|
||||
resource_attribute: u32,
|
||||
physical_start: u64,
|
||||
resource_length: u64,
|
||||
}
|
||||
unsafe impl ByteValued for HobResourceDescriptor {}
|
||||
|
||||
pub struct TdHob {
|
||||
start_offset: u64,
|
||||
current_offset: u64,
|
||||
}
|
||||
|
||||
fn align_hob(v: u64) -> u64 {
|
||||
(v + 7) / 8 * 8
|
||||
}
|
||||
|
||||
impl TdHob {
|
||||
fn update_offset<T>(&mut self) {
|
||||
self.current_offset = align_hob(self.current_offset + std::mem::size_of::<T>() as u64)
|
||||
}
|
||||
|
||||
pub fn start(offset: u64) -> TdHob {
|
||||
// Leave a gap to place the HandoffTable at the start as it can only be filled in later
|
||||
let mut hob = TdHob {
|
||||
start_offset: offset,
|
||||
current_offset: offset,
|
||||
};
|
||||
hob.update_offset::<HobHandoffInfoTable>();
|
||||
hob
|
||||
}
|
||||
|
||||
pub fn finish(&mut self, mem: &GuestMemoryMmap) -> Result<(), TdvfError> {
|
||||
// Write end
|
||||
let end = HobHeader {
|
||||
r#type: HobType::EndOfHobList,
|
||||
length: std::mem::size_of::<HobHeader>() as u16,
|
||||
reserved: 0,
|
||||
};
|
||||
info!("Writing HOB end {:x} {:x?}", self.current_offset, end);
|
||||
mem.write_obj(end, GuestAddress(self.current_offset))
|
||||
.map_err(TdvfError::GuestMemoryWriteHob)?;
|
||||
self.update_offset::<HobHeader>();
|
||||
|
||||
// Write handoff, delayed as it needs end of HOB list
|
||||
let efi_end_of_hob_list = self.current_offset;
|
||||
let handoff = HobHandoffInfoTable {
|
||||
header: HobHeader {
|
||||
r#type: HobType::Handoff,
|
||||
length: std::mem::size_of::<HobHandoffInfoTable>() as u16,
|
||||
reserved: 0,
|
||||
},
|
||||
version: 0x9,
|
||||
efi_memory_top: 0,
|
||||
efi_memory_bottom: 0,
|
||||
efi_free_memory_top: 0,
|
||||
efi_free_memory_bottom: 0,
|
||||
efi_end_of_hob_list,
|
||||
};
|
||||
info!("Writing HOB start {:x} {:x?}", self.start_offset, handoff);
|
||||
mem.write_obj(handoff, GuestAddress(self.start_offset))
|
||||
.map_err(TdvfError::GuestMemoryWriteHob)
|
||||
}
|
||||
|
||||
pub fn add_resource(
|
||||
&mut self,
|
||||
mem: &GuestMemoryMmap,
|
||||
physical_start: u64,
|
||||
resource_length: u64,
|
||||
resource_type: u32,
|
||||
resource_attribute: u32,
|
||||
) -> Result<(), TdvfError> {
|
||||
let resource_descriptor = HobResourceDescriptor {
|
||||
header: HobHeader {
|
||||
r#type: HobType::ResourceDescriptor,
|
||||
length: std::mem::size_of::<HobResourceDescriptor>() as u16,
|
||||
reserved: 0,
|
||||
},
|
||||
owner: EfiGuid::default(),
|
||||
resource_type,
|
||||
resource_attribute,
|
||||
physical_start,
|
||||
resource_length,
|
||||
};
|
||||
info!(
|
||||
"Writing HOB resource {:x} {:x?}",
|
||||
self.current_offset, resource_descriptor
|
||||
);
|
||||
mem.write_obj(resource_descriptor, GuestAddress(self.current_offset))
|
||||
.map_err(TdvfError::GuestMemoryWriteHob)?;
|
||||
self.update_offset::<HobResourceDescriptor>();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_memory_resource(
|
||||
&mut self,
|
||||
mem: &GuestMemoryMmap,
|
||||
physical_start: u64,
|
||||
resource_length: u64,
|
||||
ram: bool,
|
||||
) -> Result<(), TdvfError> {
|
||||
self.add_resource(
|
||||
mem,
|
||||
physical_start,
|
||||
resource_length,
|
||||
if ram {
|
||||
0 /* EFI_RESOURCE_SYSTEM_MEMORY */
|
||||
} else {
|
||||
0x5 /*EFI_RESOURCE_MEMORY_RESERVED */
|
||||
},
|
||||
/* TODO:
|
||||
* QEMU currently fills it in like this:
|
||||
* EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_TESTED
|
||||
* which differs from the spec (due to TDVF implementation issue?)
|
||||
*/
|
||||
0x7,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn add_mmio_resource(
|
||||
&mut self,
|
||||
mem: &GuestMemoryMmap,
|
||||
physical_start: u64,
|
||||
resource_length: u64,
|
||||
) -> Result<(), TdvfError> {
|
||||
self.add_resource(
|
||||
mem,
|
||||
physical_start,
|
||||
resource_length,
|
||||
0x1, /* EFI_RESOURCE_MEMORY_MAPPED_IO */
|
||||
/*
|
||||
* EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_UNCACHEABLE
|
||||
*/
|
||||
0x403,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_parse_tdvf_sections() {
|
||||
let mut f = std::fs::File::open("tdvf.fd").unwrap();
|
||||
let sections = parse_tdvf_sections(&mut f).unwrap();
|
||||
for section in sections {
|
||||
eprintln!("{:x?}", section)
|
||||
}
|
||||
}
|
||||
}
|
||||
6
arch_gen/Cargo.lock
generated
6
arch_gen/Cargo.lock
generated
@@ -1,6 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "arch_gen"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
[package]
|
||||
name = "arch_gen"
|
||||
version = "0.1.0"
|
||||
authors = ["Amazon firecracker team <firecracker-devel@amazon.com>"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
pub mod x86;
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(
|
||||
clippy::unreadable_literal,
|
||||
clippy::redundant_static_lifetimes,
|
||||
clippy::trivially_copy_pass_by_ref,
|
||||
clippy::useless_transmute,
|
||||
clippy::should_implement_trait,
|
||||
clippy::transmute_ptr_to_ptr
|
||||
)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]
|
||||
pub mod mpspec;
|
||||
@@ -1,832 +0,0 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
/* automatically generated by rust-bindgen */
|
||||
|
||||
pub const MPC_SIGNATURE: &'static [u8; 5usize] = b"PCMP\x00";
|
||||
pub const MP_PROCESSOR: ::std::os::raw::c_uint = 0;
|
||||
pub const MP_BUS: ::std::os::raw::c_uint = 1;
|
||||
pub const MP_IOAPIC: ::std::os::raw::c_uint = 2;
|
||||
pub const MP_INTSRC: ::std::os::raw::c_uint = 3;
|
||||
pub const MP_LINTSRC: ::std::os::raw::c_uint = 4;
|
||||
pub const MP_TRANSLATION: ::std::os::raw::c_uint = 192;
|
||||
pub const CPU_ENABLED: ::std::os::raw::c_uint = 1;
|
||||
pub const CPU_BOOTPROCESSOR: ::std::os::raw::c_uint = 2;
|
||||
pub const CPU_STEPPING_MASK: ::std::os::raw::c_uint = 15;
|
||||
pub const CPU_MODEL_MASK: ::std::os::raw::c_uint = 240;
|
||||
pub const CPU_FAMILY_MASK: ::std::os::raw::c_uint = 3840;
|
||||
pub const BUSTYPE_EISA: &'static [u8; 5usize] = b"EISA\x00";
|
||||
pub const BUSTYPE_ISA: &'static [u8; 4usize] = b"ISA\x00";
|
||||
pub const BUSTYPE_INTERN: &'static [u8; 7usize] = b"INTERN\x00";
|
||||
pub const BUSTYPE_MCA: &'static [u8; 4usize] = b"MCA\x00";
|
||||
pub const BUSTYPE_VL: &'static [u8; 3usize] = b"VL\x00";
|
||||
pub const BUSTYPE_PCI: &'static [u8; 4usize] = b"PCI\x00";
|
||||
pub const BUSTYPE_PCMCIA: &'static [u8; 7usize] = b"PCMCIA\x00";
|
||||
pub const BUSTYPE_CBUS: &'static [u8; 5usize] = b"CBUS\x00";
|
||||
pub const BUSTYPE_CBUSII: &'static [u8; 7usize] = b"CBUSII\x00";
|
||||
pub const BUSTYPE_FUTURE: &'static [u8; 7usize] = b"FUTURE\x00";
|
||||
pub const BUSTYPE_MBI: &'static [u8; 4usize] = b"MBI\x00";
|
||||
pub const BUSTYPE_MBII: &'static [u8; 5usize] = b"MBII\x00";
|
||||
pub const BUSTYPE_MPI: &'static [u8; 4usize] = b"MPI\x00";
|
||||
pub const BUSTYPE_MPSA: &'static [u8; 5usize] = b"MPSA\x00";
|
||||
pub const BUSTYPE_NUBUS: &'static [u8; 6usize] = b"NUBUS\x00";
|
||||
pub const BUSTYPE_TC: &'static [u8; 3usize] = b"TC\x00";
|
||||
pub const BUSTYPE_VME: &'static [u8; 4usize] = b"VME\x00";
|
||||
pub const BUSTYPE_XPRESS: &'static [u8; 7usize] = b"XPRESS\x00";
|
||||
pub const MPC_APIC_USABLE: ::std::os::raw::c_uint = 1;
|
||||
pub const MP_IRQDIR_DEFAULT: ::std::os::raw::c_uint = 0;
|
||||
pub const MP_IRQDIR_HIGH: ::std::os::raw::c_uint = 1;
|
||||
pub const MP_IRQDIR_LOW: ::std::os::raw::c_uint = 3;
|
||||
pub const MP_APIC_ALL: ::std::os::raw::c_uint = 255;
|
||||
pub const MPC_OEM_SIGNATURE: &'static [u8; 5usize] = b"_OEM\x00";
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpf_intel {
|
||||
pub signature: [::std::os::raw::c_char; 4usize],
|
||||
pub physptr: ::std::os::raw::c_uint,
|
||||
pub length: ::std::os::raw::c_uchar,
|
||||
pub specification: ::std::os::raw::c_uchar,
|
||||
pub checksum: ::std::os::raw::c_uchar,
|
||||
pub feature1: ::std::os::raw::c_uchar,
|
||||
pub feature2: ::std::os::raw::c_uchar,
|
||||
pub feature3: ::std::os::raw::c_uchar,
|
||||
pub feature4: ::std::os::raw::c_uchar,
|
||||
pub feature5: ::std::os::raw::c_uchar,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpf_intel() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpf_intel>(),
|
||||
16usize,
|
||||
concat!("Size of: ", stringify!(mpf_intel))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpf_intel>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(mpf_intel))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).signature as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(signature)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).physptr as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(physptr)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).length as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(length)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).specification as *const _ as usize },
|
||||
9usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(specification)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).checksum as *const _ as usize },
|
||||
10usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(checksum)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).feature1 as *const _ as usize },
|
||||
11usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(feature1)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).feature2 as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(feature2)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).feature3 as *const _ as usize },
|
||||
13usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(feature3)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).feature4 as *const _ as usize },
|
||||
14usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(feature4)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).feature5 as *const _ as usize },
|
||||
15usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(feature5)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Clone for mpf_intel {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpc_table {
|
||||
pub signature: [::std::os::raw::c_char; 4usize],
|
||||
pub length: ::std::os::raw::c_ushort,
|
||||
pub spec: ::std::os::raw::c_char,
|
||||
pub checksum: ::std::os::raw::c_char,
|
||||
pub oem: [::std::os::raw::c_char; 8usize],
|
||||
pub productid: [::std::os::raw::c_char; 12usize],
|
||||
pub oemptr: ::std::os::raw::c_uint,
|
||||
pub oemsize: ::std::os::raw::c_ushort,
|
||||
pub oemcount: ::std::os::raw::c_ushort,
|
||||
pub lapic: ::std::os::raw::c_uint,
|
||||
pub reserved: ::std::os::raw::c_uint,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpc_table() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpc_table>(),
|
||||
44usize,
|
||||
concat!("Size of: ", stringify!(mpc_table))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpc_table>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(mpc_table))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).signature as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(signature)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).length as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(length)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).spec as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(spec)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).checksum as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(checksum)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).oem as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(oem)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).productid as *const _ as usize },
|
||||
16usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(productid)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).oemptr as *const _ as usize },
|
||||
28usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(oemptr)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).oemsize as *const _ as usize },
|
||||
32usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(oemsize)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).oemcount as *const _ as usize },
|
||||
34usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(oemcount)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).lapic as *const _ as usize },
|
||||
36usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(lapic)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).reserved as *const _ as usize },
|
||||
40usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(reserved)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Clone for mpc_table {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpc_cpu {
|
||||
pub type_: ::std::os::raw::c_uchar,
|
||||
pub apicid: ::std::os::raw::c_uchar,
|
||||
pub apicver: ::std::os::raw::c_uchar,
|
||||
pub cpuflag: ::std::os::raw::c_uchar,
|
||||
pub cpufeature: ::std::os::raw::c_uint,
|
||||
pub featureflag: ::std::os::raw::c_uint,
|
||||
pub reserved: [::std::os::raw::c_uint; 2usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpc_cpu() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpc_cpu>(),
|
||||
20usize,
|
||||
concat!("Size of: ", stringify!(mpc_cpu))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpc_cpu>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(mpc_cpu))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_cpu),
|
||||
"::",
|
||||
stringify!(type_)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).apicid as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_cpu),
|
||||
"::",
|
||||
stringify!(apicid)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).apicver as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_cpu),
|
||||
"::",
|
||||
stringify!(apicver)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).cpuflag as *const _ as usize },
|
||||
3usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_cpu),
|
||||
"::",
|
||||
stringify!(cpuflag)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).cpufeature as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_cpu),
|
||||
"::",
|
||||
stringify!(cpufeature)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).featureflag as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_cpu),
|
||||
"::",
|
||||
stringify!(featureflag)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).reserved as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_cpu),
|
||||
"::",
|
||||
stringify!(reserved)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Clone for mpc_cpu {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpc_bus {
|
||||
pub type_: ::std::os::raw::c_uchar,
|
||||
pub busid: ::std::os::raw::c_uchar,
|
||||
pub bustype: [::std::os::raw::c_uchar; 6usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpc_bus() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpc_bus>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(mpc_bus))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpc_bus>(),
|
||||
1usize,
|
||||
concat!("Alignment of ", stringify!(mpc_bus))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_bus>()).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_bus),
|
||||
"::",
|
||||
stringify!(type_)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_bus>()).busid as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_bus),
|
||||
"::",
|
||||
stringify!(busid)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_bus>()).bustype as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_bus),
|
||||
"::",
|
||||
stringify!(bustype)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Clone for mpc_bus {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpc_ioapic {
|
||||
pub type_: ::std::os::raw::c_uchar,
|
||||
pub apicid: ::std::os::raw::c_uchar,
|
||||
pub apicver: ::std::os::raw::c_uchar,
|
||||
pub flags: ::std::os::raw::c_uchar,
|
||||
pub apicaddr: ::std::os::raw::c_uint,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpc_ioapic() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpc_ioapic>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(mpc_ioapic))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpc_ioapic>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(mpc_ioapic))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_ioapic>()).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_ioapic),
|
||||
"::",
|
||||
stringify!(type_)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_ioapic>()).apicid as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_ioapic),
|
||||
"::",
|
||||
stringify!(apicid)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_ioapic>()).apicver as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_ioapic),
|
||||
"::",
|
||||
stringify!(apicver)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_ioapic>()).flags as *const _ as usize },
|
||||
3usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_ioapic),
|
||||
"::",
|
||||
stringify!(flags)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_ioapic>()).apicaddr as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_ioapic),
|
||||
"::",
|
||||
stringify!(apicaddr)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Clone for mpc_ioapic {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpc_intsrc {
|
||||
pub type_: ::std::os::raw::c_uchar,
|
||||
pub irqtype: ::std::os::raw::c_uchar,
|
||||
pub irqflag: ::std::os::raw::c_ushort,
|
||||
pub srcbus: ::std::os::raw::c_uchar,
|
||||
pub srcbusirq: ::std::os::raw::c_uchar,
|
||||
pub dstapic: ::std::os::raw::c_uchar,
|
||||
pub dstirq: ::std::os::raw::c_uchar,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpc_intsrc() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpc_intsrc>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(mpc_intsrc))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpc_intsrc>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(mpc_intsrc))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_intsrc),
|
||||
"::",
|
||||
stringify!(type_)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).irqtype as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_intsrc),
|
||||
"::",
|
||||
stringify!(irqtype)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).irqflag as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_intsrc),
|
||||
"::",
|
||||
stringify!(irqflag)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).srcbus as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_intsrc),
|
||||
"::",
|
||||
stringify!(srcbus)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).srcbusirq as *const _ as usize },
|
||||
5usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_intsrc),
|
||||
"::",
|
||||
stringify!(srcbusirq)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).dstapic as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_intsrc),
|
||||
"::",
|
||||
stringify!(dstapic)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).dstirq as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_intsrc),
|
||||
"::",
|
||||
stringify!(dstirq)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Clone for mpc_intsrc {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
pub const mp_irq_source_types_mp_INT: mp_irq_source_types = 0;
|
||||
pub const mp_irq_source_types_mp_NMI: mp_irq_source_types = 1;
|
||||
pub const mp_irq_source_types_mp_SMI: mp_irq_source_types = 2;
|
||||
pub const mp_irq_source_types_mp_ExtINT: mp_irq_source_types = 3;
|
||||
pub type mp_irq_source_types = ::std::os::raw::c_uint;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpc_lintsrc {
|
||||
pub type_: ::std::os::raw::c_uchar,
|
||||
pub irqtype: ::std::os::raw::c_uchar,
|
||||
pub irqflag: ::std::os::raw::c_ushort,
|
||||
pub srcbusid: ::std::os::raw::c_uchar,
|
||||
pub srcbusirq: ::std::os::raw::c_uchar,
|
||||
pub destapic: ::std::os::raw::c_uchar,
|
||||
pub destapiclint: ::std::os::raw::c_uchar,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpc_lintsrc() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpc_lintsrc>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(mpc_lintsrc))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpc_lintsrc>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(mpc_lintsrc))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_lintsrc),
|
||||
"::",
|
||||
stringify!(type_)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).irqtype as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_lintsrc),
|
||||
"::",
|
||||
stringify!(irqtype)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).irqflag as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_lintsrc),
|
||||
"::",
|
||||
stringify!(irqflag)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).srcbusid as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_lintsrc),
|
||||
"::",
|
||||
stringify!(srcbusid)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).srcbusirq as *const _ as usize },
|
||||
5usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_lintsrc),
|
||||
"::",
|
||||
stringify!(srcbusirq)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).destapic as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_lintsrc),
|
||||
"::",
|
||||
stringify!(destapic)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).destapiclint as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_lintsrc),
|
||||
"::",
|
||||
stringify!(destapiclint)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Clone for mpc_lintsrc {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpc_oemtable {
|
||||
pub signature: [::std::os::raw::c_char; 4usize],
|
||||
pub length: ::std::os::raw::c_ushort,
|
||||
pub rev: ::std::os::raw::c_char,
|
||||
pub checksum: ::std::os::raw::c_char,
|
||||
pub mpc: [::std::os::raw::c_char; 8usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpc_oemtable() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpc_oemtable>(),
|
||||
16usize,
|
||||
concat!("Size of: ", stringify!(mpc_oemtable))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpc_oemtable>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(mpc_oemtable))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_oemtable>()).signature as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_oemtable),
|
||||
"::",
|
||||
stringify!(signature)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_oemtable>()).length as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_oemtable),
|
||||
"::",
|
||||
stringify!(length)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_oemtable>()).rev as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_oemtable),
|
||||
"::",
|
||||
stringify!(rev)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_oemtable>()).checksum as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_oemtable),
|
||||
"::",
|
||||
stringify!(checksum)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*std::ptr::null::<mpc_oemtable>()).mpc as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_oemtable),
|
||||
"::",
|
||||
stringify!(mpc)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Clone for mpc_oemtable {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
pub const mp_bustype_MP_BUS_ISA: mp_bustype = 1;
|
||||
pub const mp_bustype_MP_BUS_EISA: mp_bustype = 2;
|
||||
pub const mp_bustype_MP_BUS_PCI: mp_bustype = 3;
|
||||
pub type mp_bustype = ::std::os::raw::c_uint;
|
||||
@@ -10,12 +10,14 @@ io_uring = []
|
||||
|
||||
[dependencies]
|
||||
io-uring = ">=0.4.0"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
serde = ">=1.0.27"
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
libc = "0.2.96"
|
||||
log = "0.4.14"
|
||||
qcow = { path = "../qcow" }
|
||||
thiserror = "1.0"
|
||||
versionize = "0.1.6"
|
||||
versionize_derive = "0.1.4"
|
||||
virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
|
||||
vm-memory = { version = "0.4.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-memory = { version = "0.5.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
|
||||
56
block_util/src/async_io.rs
Normal file
56
block_util/src/async_io.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use thiserror::Error;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DiskFileError {
|
||||
/// Failed getting disk file size.
|
||||
#[error("Failed getting disk file size: {0}")]
|
||||
Size(#[source] std::io::Error),
|
||||
/// Failed creating a new AsyncIo.
|
||||
#[error("Failed creating a new AsyncIo: {0}")]
|
||||
NewAsyncIo(#[source] std::io::Error),
|
||||
}
|
||||
|
||||
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
|
||||
|
||||
pub trait DiskFile: Send + Sync {
|
||||
fn size(&mut self) -> DiskFileResult<u64>;
|
||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AsyncIoError {
|
||||
/// Failed vectored reading from file.
|
||||
#[error("Failed vectored reading from file: {0}")]
|
||||
ReadVectored(#[source] std::io::Error),
|
||||
/// Failed vectored writing to file.
|
||||
#[error("Failed vectored writing to file: {0}")]
|
||||
WriteVectored(#[source] std::io::Error),
|
||||
/// Failed synchronizing file.
|
||||
#[error("Failed synchronizing file: {0}")]
|
||||
Fsync(#[source] std::io::Error),
|
||||
}
|
||||
|
||||
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
|
||||
|
||||
pub trait AsyncIo: Send + Sync {
|
||||
fn notifier(&self) -> &EventFd;
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()>;
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()>;
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
|
||||
fn complete(&mut self) -> Vec<(u64, i32)>;
|
||||
}
|
||||
110
block_util/src/fixed_vhd_async.rs
Normal file
110
block_util/src/fixed_vhd_async.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::raw_async::RawFileAsync;
|
||||
use crate::vhd::VhdFooter;
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct FixedVhdDiskAsync {
|
||||
file: File,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl FixedVhdDiskAsync {
|
||||
pub fn new(mut file: File) -> std::io::Result<Self> {
|
||||
let footer = VhdFooter::new(&mut file)?;
|
||||
|
||||
Ok(FixedVhdDiskAsync {
|
||||
file,
|
||||
size: footer.current_size(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for FixedVhdDiskAsync {
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self.size)
|
||||
}
|
||||
|
||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(
|
||||
FixedVhdAsync::new(self.file.as_raw_fd(), ring_depth, self.size)
|
||||
.map_err(DiskFileError::NewAsyncIo)?,
|
||||
) as Box<dyn AsyncIo>)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FixedVhdAsync {
|
||||
raw_file_async: RawFileAsync,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl FixedVhdAsync {
|
||||
pub fn new(fd: RawFd, ring_depth: u32, size: u64) -> std::io::Result<Self> {
|
||||
let raw_file_async = RawFileAsync::new(fd, ring_depth)?;
|
||||
|
||||
Ok(FixedVhdAsync {
|
||||
raw_file_async,
|
||||
size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for FixedVhdAsync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
self.raw_file_async.notifier()
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
if offset as u64 >= self.size {
|
||||
return Err(AsyncIoError::ReadVectored(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"Invalid offset {}, can't be larger than file size {}",
|
||||
offset, self.size
|
||||
),
|
||||
)));
|
||||
}
|
||||
|
||||
self.raw_file_async.read_vectored(offset, iovecs, user_data)
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
if offset as u64 >= self.size {
|
||||
return Err(AsyncIoError::WriteVectored(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"Invalid offset {}, can't be larger than file size {}",
|
||||
offset, self.size
|
||||
),
|
||||
)));
|
||||
}
|
||||
|
||||
self.raw_file_async
|
||||
.write_vectored(offset, iovecs, user_data)
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
self.raw_file_async.fsync(user_data)
|
||||
}
|
||||
|
||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||
self.raw_file_async.complete()
|
||||
}
|
||||
}
|
||||
107
block_util/src/fixed_vhd_sync.rs
Normal file
107
block_util/src/fixed_vhd_sync.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::raw_sync::RawFileSync;
|
||||
use crate::vhd::VhdFooter;
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct FixedVhdDiskSync {
|
||||
file: File,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl FixedVhdDiskSync {
|
||||
pub fn new(mut file: File) -> std::io::Result<Self> {
|
||||
let footer = VhdFooter::new(&mut file)?;
|
||||
|
||||
Ok(FixedVhdDiskSync {
|
||||
file,
|
||||
size: footer.current_size(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for FixedVhdDiskSync {
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self.size)
|
||||
}
|
||||
|
||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(
|
||||
FixedVhdSync::new(self.file.as_raw_fd(), self.size)
|
||||
.map_err(DiskFileError::NewAsyncIo)?,
|
||||
) as Box<dyn AsyncIo>)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FixedVhdSync {
|
||||
raw_file_sync: RawFileSync,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl FixedVhdSync {
|
||||
pub fn new(fd: RawFd, size: u64) -> std::io::Result<Self> {
|
||||
Ok(FixedVhdSync {
|
||||
raw_file_sync: RawFileSync::new(fd),
|
||||
size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for FixedVhdSync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
self.raw_file_sync.notifier()
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
if offset as u64 >= self.size {
|
||||
return Err(AsyncIoError::ReadVectored(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"Invalid offset {}, can't be larger than file size {}",
|
||||
offset, self.size
|
||||
),
|
||||
)));
|
||||
}
|
||||
|
||||
self.raw_file_sync.read_vectored(offset, iovecs, user_data)
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
if offset as u64 >= self.size {
|
||||
return Err(AsyncIoError::WriteVectored(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"Invalid offset {}, can't be larger than file size {}",
|
||||
offset, self.size
|
||||
),
|
||||
)));
|
||||
}
|
||||
|
||||
self.raw_file_sync.write_vectored(offset, iovecs, user_data)
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
self.raw_file_sync.fsync(user_data)
|
||||
}
|
||||
|
||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||
self.raw_file_sync.complete()
|
||||
}
|
||||
}
|
||||
@@ -10,29 +10,42 @@
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
pub mod async_io;
|
||||
pub mod fixed_vhd_async;
|
||||
pub mod fixed_vhd_sync;
|
||||
pub mod qcow_sync;
|
||||
pub mod raw_async;
|
||||
pub mod raw_sync;
|
||||
pub mod vhd;
|
||||
|
||||
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, DiskFileError, DiskFileResult};
|
||||
#[cfg(feature = "io_uring")]
|
||||
use io_uring::Probe;
|
||||
use io_uring::{opcode, squeue, IoUring};
|
||||
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||
use io_uring::{opcode, IoUring, Probe};
|
||||
use std::cmp;
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
||||
use std::os::linux::fs::MetadataExt;
|
||||
#[cfg(feature = "io_uring")]
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::path::PathBuf;
|
||||
use std::path::Path;
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use virtio_bindings::bindings::virtio_blk::*;
|
||||
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||
use vm_memory::{
|
||||
bitmap::AtomicBitmap, bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory,
|
||||
GuestMemoryError,
|
||||
};
|
||||
use vm_virtio::DescriptorChain;
|
||||
#[cfg(feature = "io_uring")]
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
||||
|
||||
const SECTOR_SHIFT: u8 = 9;
|
||||
pub const SECTOR_SIZE: u64 = (0x01 as u64) << SECTOR_SHIFT;
|
||||
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -56,7 +69,7 @@ pub enum Error {
|
||||
TooManyDescriptors,
|
||||
}
|
||||
|
||||
fn build_device_id(disk_path: &PathBuf) -> result::Result<String, Error> {
|
||||
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
||||
let blk_metadata = match disk_path.metadata() {
|
||||
Err(_) => return Err(Error::GetFileMetadata),
|
||||
Ok(m) => m,
|
||||
@@ -71,7 +84,7 @@ fn build_device_id(disk_path: &PathBuf) -> result::Result<String, Error> {
|
||||
Ok(device_id)
|
||||
}
|
||||
|
||||
pub fn build_disk_image_id(disk_path: &PathBuf) -> Vec<u8> {
|
||||
pub fn build_disk_image_id(disk_path: &Path) -> Vec<u8> {
|
||||
let mut default_disk_image_id = vec![0; VIRTIO_BLK_ID_BYTES as usize];
|
||||
match build_device_id(disk_path) {
|
||||
Err(_) => {
|
||||
@@ -98,6 +111,9 @@ pub enum ExecuteError {
|
||||
Unsupported(u32),
|
||||
SubmitIoUring(io::Error),
|
||||
GetHostAddress(GuestMemoryError),
|
||||
AsyncRead(AsyncIoError),
|
||||
AsyncWrite(AsyncIoError),
|
||||
AsyncFlush(AsyncIoError),
|
||||
}
|
||||
|
||||
impl ExecuteError {
|
||||
@@ -111,6 +127,9 @@ impl ExecuteError {
|
||||
ExecuteError::Unsupported(_) => VIRTIO_BLK_S_UNSUPP,
|
||||
ExecuteError::SubmitIoUring(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::GetHostAddress(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncRead(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,7 +139,7 @@ pub enum RequestType {
|
||||
In,
|
||||
Out,
|
||||
Flush,
|
||||
GetDeviceID,
|
||||
GetDeviceId,
|
||||
Unsupported(u32),
|
||||
}
|
||||
|
||||
@@ -133,7 +152,7 @@ pub fn request_type(
|
||||
VIRTIO_BLK_T_IN => Ok(RequestType::In),
|
||||
VIRTIO_BLK_T_OUT => Ok(RequestType::Out),
|
||||
VIRTIO_BLK_T_FLUSH => Ok(RequestType::Flush),
|
||||
VIRTIO_BLK_T_GET_ID => Ok(RequestType::GetDeviceID),
|
||||
VIRTIO_BLK_T_GET_ID => Ok(RequestType::GetDeviceId),
|
||||
t => Ok(RequestType::Unsupported(t)),
|
||||
}
|
||||
}
|
||||
@@ -148,6 +167,7 @@ fn sector(mem: &GuestMemoryMmap, desc_addr: GuestAddress) -> result::Result<u64,
|
||||
mem.read_obj(addr).map_err(Error::GuestMemory)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Request {
|
||||
pub request_type: RequestType,
|
||||
pub sector: u64,
|
||||
@@ -177,12 +197,17 @@ impl Request {
|
||||
let status_desc;
|
||||
let mut desc = avail_desc
|
||||
.next_descriptor()
|
||||
.ok_or(Error::DescriptorChainTooShort)?;
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.map_err(|e| {
|
||||
error!("Only head descriptor present: request = {:?}", req);
|
||||
e
|
||||
})?;
|
||||
|
||||
if !desc.has_next() {
|
||||
status_desc = desc;
|
||||
// Only flush requests are allowed to skip the data descriptor.
|
||||
if req.request_type != RequestType::Flush {
|
||||
error!("Need a data descriptor: request = {:?}", req);
|
||||
return Err(Error::DescriptorChainTooShort);
|
||||
}
|
||||
} else {
|
||||
@@ -193,13 +218,17 @@ impl Request {
|
||||
if !desc.is_write_only() && req.request_type == RequestType::In {
|
||||
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||
}
|
||||
if !desc.is_write_only() && req.request_type == RequestType::GetDeviceID {
|
||||
if !desc.is_write_only() && req.request_type == RequestType::GetDeviceId {
|
||||
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||
}
|
||||
req.data_descriptors.push((desc.addr, desc.len));
|
||||
desc = desc
|
||||
.next_descriptor()
|
||||
.ok_or(Error::DescriptorChainTooShort)?;
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.map_err(|e| {
|
||||
error!("DescriptorChain corrupted: request = {:?}", req);
|
||||
e
|
||||
})?;
|
||||
}
|
||||
status_desc = desc;
|
||||
}
|
||||
@@ -255,7 +284,7 @@ impl Request {
|
||||
}
|
||||
}
|
||||
RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?,
|
||||
RequestType::GetDeviceID => {
|
||||
RequestType::GetDeviceId => {
|
||||
if (*data_len as usize) < disk_id.len() {
|
||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||
}
|
||||
@@ -268,12 +297,11 @@ impl Request {
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
pub fn execute_io_uring(
|
||||
pub fn execute_async(
|
||||
&self,
|
||||
mem: &GuestMemoryMmap,
|
||||
io_uring: &mut IoUring,
|
||||
disk_nsectors: u64,
|
||||
disk_image_fd: RawFd,
|
||||
disk_image: &mut dyn AsyncIo,
|
||||
disk_id: &[u8],
|
||||
user_data: u64,
|
||||
) -> result::Result<bool, ExecuteError> {
|
||||
@@ -281,9 +309,6 @@ impl Request {
|
||||
let request_type = self.request_type;
|
||||
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
|
||||
|
||||
let (submitter, sq, _) = io_uring.split();
|
||||
let mut avail_sq = sq.available();
|
||||
|
||||
let mut iovecs = Vec::new();
|
||||
for (data_addr, data_len) in &self.data_descriptors {
|
||||
let mut top: u64 = u64::from(*data_len) / SECTOR_SIZE;
|
||||
@@ -311,51 +336,27 @@ impl Request {
|
||||
// Queue operations expected to be submitted.
|
||||
match request_type {
|
||||
RequestType::In => {
|
||||
// Safe because we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
let _ = unsafe {
|
||||
avail_sq.push(
|
||||
opcode::Readv::new(
|
||||
opcode::types::Fd(disk_image_fd),
|
||||
iovecs.as_ptr(),
|
||||
iovecs.len() as u32,
|
||||
)
|
||||
.offset(offset)
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
for (data_addr, data_len) in &self.data_descriptors {
|
||||
mem.get_slice(*data_addr, *data_len as usize)
|
||||
.map_err(ExecuteError::GetHostAddress)?
|
||||
.bitmap()
|
||||
.mark_dirty(0, *data_len as usize);
|
||||
}
|
||||
disk_image
|
||||
.read_vectored(offset, iovecs, user_data)
|
||||
.map_err(ExecuteError::AsyncRead)?;
|
||||
}
|
||||
RequestType::Out => {
|
||||
// Safe because we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
let _ = unsafe {
|
||||
avail_sq.push(
|
||||
opcode::Writev::new(
|
||||
opcode::types::Fd(disk_image_fd),
|
||||
iovecs.as_ptr(),
|
||||
iovecs.len() as u32,
|
||||
)
|
||||
.offset(offset)
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
disk_image
|
||||
.write_vectored(offset, iovecs, user_data)
|
||||
.map_err(ExecuteError::AsyncWrite)?;
|
||||
}
|
||||
RequestType::Flush => {
|
||||
// Safe because we know the file descriptor is valid.
|
||||
let _ = unsafe {
|
||||
avail_sq.push(
|
||||
opcode::Fsync::new(opcode::types::Fd(disk_image_fd))
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
disk_image
|
||||
.fsync(Some(user_data))
|
||||
.map_err(ExecuteError::AsyncFlush)?;
|
||||
}
|
||||
RequestType::GetDeviceID => {
|
||||
RequestType::GetDeviceId => {
|
||||
let (data_addr, data_len) = if self.data_descriptors.len() == 1 {
|
||||
(self.data_descriptors[0].0, self.data_descriptors[0].1)
|
||||
} else {
|
||||
@@ -371,11 +372,6 @@ impl Request {
|
||||
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
||||
}
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
avail_sq.sync();
|
||||
submitter.submit().map_err(ExecuteError::SubmitIoUring)?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@@ -384,7 +380,7 @@ impl Request {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Deserialize)]
|
||||
#[derive(Copy, Clone, Debug, Default, Versionize)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VirtioBlockConfig {
|
||||
pub capacity: u64,
|
||||
@@ -407,95 +403,15 @@ pub struct VirtioBlockConfig {
|
||||
pub write_zeroes_may_unmap: u8,
|
||||
pub unused1: [u8; 3],
|
||||
}
|
||||
|
||||
// We must explicitly implement Serialize since the structure is packed and
|
||||
// it's unsafe to borrow from a packed structure. And by default, if we derive
|
||||
// Serialize from serde, it will borrow the values from the structure.
|
||||
// That's why this implementation copies each field separately before it
|
||||
// serializes the entire structure field by field.
|
||||
impl Serialize for VirtioBlockConfig {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let capacity = self.capacity;
|
||||
let size_max = self.size_max;
|
||||
let seg_max = self.seg_max;
|
||||
let geometry = self.geometry;
|
||||
let blk_size = self.blk_size;
|
||||
let physical_block_exp = self.physical_block_exp;
|
||||
let alignment_offset = self.alignment_offset;
|
||||
let min_io_size = self.min_io_size;
|
||||
let opt_io_size = self.opt_io_size;
|
||||
let writeback = self.writeback;
|
||||
let unused = self.unused;
|
||||
let num_queues = self.num_queues;
|
||||
let max_discard_sectors = self.max_discard_sectors;
|
||||
let max_discard_seg = self.max_discard_seg;
|
||||
let discard_sector_alignment = self.discard_sector_alignment;
|
||||
let max_write_zeroes_sectors = self.max_write_zeroes_sectors;
|
||||
let max_write_zeroes_seg = self.max_write_zeroes_seg;
|
||||
let write_zeroes_may_unmap = self.write_zeroes_may_unmap;
|
||||
let unused1 = self.unused1;
|
||||
|
||||
let mut virtio_block_config = serializer.serialize_struct("VirtioBlockConfig", 60)?;
|
||||
virtio_block_config.serialize_field("capacity", &capacity)?;
|
||||
virtio_block_config.serialize_field("size_max", &size_max)?;
|
||||
virtio_block_config.serialize_field("seg_max", &seg_max)?;
|
||||
virtio_block_config.serialize_field("geometry", &geometry)?;
|
||||
virtio_block_config.serialize_field("blk_size", &blk_size)?;
|
||||
virtio_block_config.serialize_field("physical_block_exp", &physical_block_exp)?;
|
||||
virtio_block_config.serialize_field("alignment_offset", &alignment_offset)?;
|
||||
virtio_block_config.serialize_field("min_io_size", &min_io_size)?;
|
||||
virtio_block_config.serialize_field("opt_io_size", &opt_io_size)?;
|
||||
virtio_block_config.serialize_field("writeback", &writeback)?;
|
||||
virtio_block_config.serialize_field("unused", &unused)?;
|
||||
virtio_block_config.serialize_field("num_queues", &num_queues)?;
|
||||
virtio_block_config.serialize_field("max_discard_sectors", &max_discard_sectors)?;
|
||||
virtio_block_config.serialize_field("max_discard_seg", &max_discard_seg)?;
|
||||
virtio_block_config
|
||||
.serialize_field("discard_sector_alignment", &discard_sector_alignment)?;
|
||||
virtio_block_config
|
||||
.serialize_field("max_write_zeroes_sectors", &max_write_zeroes_sectors)?;
|
||||
virtio_block_config.serialize_field("max_write_zeroes_seg", &max_write_zeroes_seg)?;
|
||||
virtio_block_config.serialize_field("write_zeroes_may_unmap", &write_zeroes_may_unmap)?;
|
||||
virtio_block_config.serialize_field("unused1", &unused1)?;
|
||||
virtio_block_config.end()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioBlockConfig {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Deserialize)]
|
||||
#[derive(Copy, Clone, Debug, Default, Versionize)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VirtioBlockGeometry {
|
||||
pub cylinders: u16,
|
||||
pub heads: u8,
|
||||
pub sectors: u8,
|
||||
}
|
||||
|
||||
// We must explicitly implement Serialize since the structure is packed and
|
||||
// it's unsafe to borrow from a packed structure. And by default, if we derive
|
||||
// Serialize from serde, it will borrow the values from the structure.
|
||||
// That's why this implementation copies each field separately before it
|
||||
// serializes the entire structure field by field.
|
||||
impl Serialize for VirtioBlockGeometry {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let cylinders = self.cylinders;
|
||||
let heads = self.heads;
|
||||
let sectors = self.sectors;
|
||||
|
||||
let mut virtio_block_geometry = serializer.serialize_struct("VirtioBlockGeometry", 4)?;
|
||||
virtio_block_geometry.serialize_field("cylinders", &cylinders)?;
|
||||
virtio_block_geometry.serialize_field("heads", &heads)?;
|
||||
virtio_block_geometry.serialize_field("sectors", §ors)?;
|
||||
virtio_block_geometry.end()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioBlockGeometry {}
|
||||
|
||||
/// Check if io_uring for block device can be used on the current system, as
|
||||
@@ -571,3 +487,145 @@ pub fn block_io_uring_is_supported() -> bool {
|
||||
pub fn block_io_uring_is_supported() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn disk_size(file: &mut dyn Seek, semaphore: &mut Arc<Mutex<()>>) -> DiskFileResult<u64> {
|
||||
// Take the semaphore to ensure other threads are not interacting with
|
||||
// the underlying file.
|
||||
let _lock = semaphore.lock().unwrap();
|
||||
|
||||
Ok(file.seek(SeekFrom::End(0)).map_err(DiskFileError::Size)? as u64)
|
||||
}
|
||||
|
||||
pub trait ReadSeekFile: Read + Seek {}
|
||||
impl<F: Read + Seek> ReadSeekFile for F {}
|
||||
|
||||
pub fn read_vectored_sync(
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
file: &mut dyn ReadSeekFile,
|
||||
eventfd: &EventFd,
|
||||
completion_list: &mut Vec<(u64, i32)>,
|
||||
semaphore: &mut Arc<Mutex<()>>,
|
||||
) -> AsyncIoResult<()> {
|
||||
// Convert libc::iovec into IoSliceMut
|
||||
let mut slices = Vec::new();
|
||||
for iovec in iovecs.iter() {
|
||||
slices.push(IoSliceMut::new(unsafe { std::mem::transmute(*iovec) }));
|
||||
}
|
||||
|
||||
let result = {
|
||||
// Take the semaphore to ensure other threads are not interacting
|
||||
// with the underlying file.
|
||||
let _lock = semaphore.lock().unwrap();
|
||||
|
||||
// Move the cursor to the right offset
|
||||
file.seek(SeekFrom::Start(offset as u64))
|
||||
.map_err(AsyncIoError::ReadVectored)?;
|
||||
|
||||
// Read vectored
|
||||
file.read_vectored(slices.as_mut_slice())
|
||||
.map_err(AsyncIoError::ReadVectored)?
|
||||
};
|
||||
|
||||
completion_list.push((user_data, result as i32));
|
||||
eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub trait WriteSeekFile: Write + Seek {}
|
||||
impl<F: Write + Seek> WriteSeekFile for F {}
|
||||
|
||||
pub fn write_vectored_sync(
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
file: &mut dyn WriteSeekFile,
|
||||
eventfd: &EventFd,
|
||||
completion_list: &mut Vec<(u64, i32)>,
|
||||
semaphore: &mut Arc<Mutex<()>>,
|
||||
) -> AsyncIoResult<()> {
|
||||
// Convert libc::iovec into IoSlice
|
||||
let mut slices = Vec::new();
|
||||
for iovec in iovecs.iter() {
|
||||
slices.push(IoSlice::new(unsafe { std::mem::transmute(*iovec) }));
|
||||
}
|
||||
|
||||
let result = {
|
||||
// Take the semaphore to ensure other threads are not interacting
|
||||
// with the underlying file.
|
||||
let _lock = semaphore.lock().unwrap();
|
||||
|
||||
// Move the cursor to the right offset
|
||||
file.seek(SeekFrom::Start(offset as u64))
|
||||
.map_err(AsyncIoError::WriteVectored)?;
|
||||
|
||||
// Write vectored
|
||||
file.write_vectored(slices.as_slice())
|
||||
.map_err(AsyncIoError::WriteVectored)?
|
||||
};
|
||||
|
||||
completion_list.push((user_data, result as i32));
|
||||
eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn fsync_sync(
|
||||
user_data: Option<u64>,
|
||||
file: &mut dyn Write,
|
||||
eventfd: &EventFd,
|
||||
completion_list: &mut Vec<(u64, i32)>,
|
||||
semaphore: &mut Arc<Mutex<()>>,
|
||||
) -> AsyncIoResult<()> {
|
||||
let result: i32 = {
|
||||
// Take the semaphore to ensure other threads are not interacting
|
||||
// with the underlying file.
|
||||
let _lock = semaphore.lock().unwrap();
|
||||
|
||||
// Flush
|
||||
file.flush().map_err(AsyncIoError::Fsync)?;
|
||||
|
||||
0
|
||||
};
|
||||
|
||||
if let Some(user_data) = user_data {
|
||||
completion_list.push((user_data, result));
|
||||
eventfd.write(1).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub enum ImageType {
|
||||
FixedVhd,
|
||||
Qcow2,
|
||||
Raw,
|
||||
}
|
||||
|
||||
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
||||
|
||||
/// Determine image type through file parsing.
|
||||
pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
|
||||
// We must create a buffer aligned on 512 bytes with a size being a
|
||||
// multiple of 512 bytes as the file might be opened with O_DIRECT flag.
|
||||
#[repr(align(512))]
|
||||
struct Sector {
|
||||
data: [u8; 512],
|
||||
}
|
||||
let mut s = Sector { data: [0; 512] };
|
||||
|
||||
f.read_exact(&mut s.data)?;
|
||||
|
||||
// Check 4 first bytes to get the header value and determine the image type
|
||||
let image_type = if u32::from_be_bytes(s.data[0..4].try_into().unwrap()) == QCOW_MAGIC {
|
||||
ImageType::Qcow2
|
||||
} else if vhd::is_fixed_vhd(f)? {
|
||||
ImageType::FixedVhd
|
||||
} else {
|
||||
ImageType::Raw
|
||||
};
|
||||
|
||||
Ok(image_type)
|
||||
}
|
||||
|
||||
111
block_util/src/qcow_sync.rs
Normal file
111
block_util/src/qcow_sync.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileResult};
|
||||
use crate::{disk_size, fsync_sync, read_vectored_sync, write_vectored_sync};
|
||||
use qcow::{QcowFile, RawFile};
|
||||
use std::fs::File;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct QcowDiskSync {
|
||||
qcow_file: QcowFile,
|
||||
semaphore: Arc<Mutex<()>>,
|
||||
}
|
||||
|
||||
impl QcowDiskSync {
|
||||
pub fn new(file: File, direct_io: bool) -> Self {
|
||||
QcowDiskSync {
|
||||
qcow_file: QcowFile::from(RawFile::new(file, direct_io))
|
||||
.expect("Failed creating QcowFile"),
|
||||
semaphore: Arc::new(Mutex::new(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for QcowDiskSync {
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
disk_size(&mut self.qcow_file, &mut self.semaphore)
|
||||
}
|
||||
|
||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(QcowSync::new(
|
||||
self.qcow_file.clone(),
|
||||
self.semaphore.clone(),
|
||||
)) as Box<dyn AsyncIo>)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QcowSync {
|
||||
qcow_file: QcowFile,
|
||||
eventfd: EventFd,
|
||||
completion_list: Vec<(u64, i32)>,
|
||||
semaphore: Arc<Mutex<()>>,
|
||||
}
|
||||
|
||||
impl QcowSync {
|
||||
pub fn new(qcow_file: QcowFile, semaphore: Arc<Mutex<()>>) -> Self {
|
||||
QcowSync {
|
||||
qcow_file,
|
||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
||||
.expect("Failed creating EventFd for QcowSync"),
|
||||
completion_list: Vec::new(),
|
||||
semaphore,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for QcowSync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
&self.eventfd
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
read_vectored_sync(
|
||||
offset,
|
||||
iovecs,
|
||||
user_data,
|
||||
&mut self.qcow_file,
|
||||
&self.eventfd,
|
||||
&mut self.completion_list,
|
||||
&mut self.semaphore,
|
||||
)
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
write_vectored_sync(
|
||||
offset,
|
||||
iovecs,
|
||||
user_data,
|
||||
&mut self.qcow_file,
|
||||
&self.eventfd,
|
||||
&mut self.completion_list,
|
||||
&mut self.semaphore,
|
||||
)
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
fsync_sync(
|
||||
user_data,
|
||||
&mut self.qcow_file,
|
||||
&self.eventfd,
|
||||
&mut self.completion_list,
|
||||
&mut self.semaphore,
|
||||
)
|
||||
}
|
||||
|
||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||
self.completion_list.drain(..).collect()
|
||||
}
|
||||
}
|
||||
159
block_util/src/raw_async.rs
Normal file
159
block_util/src/raw_async.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use io_uring::{opcode, squeue, types, IoUring};
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct RawFileDisk {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl RawFileDisk {
|
||||
pub fn new(file: File) -> Self {
|
||||
RawFileDisk { file }
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for RawFileDisk {
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self
|
||||
.file
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(DiskFileError::Size)? as u64)
|
||||
}
|
||||
|
||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(
|
||||
RawFileAsync::new(self.file.as_raw_fd(), ring_depth)
|
||||
.map_err(DiskFileError::NewAsyncIo)?,
|
||||
) as Box<dyn AsyncIo>)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawFileAsync {
|
||||
fd: RawFd,
|
||||
io_uring: IoUring,
|
||||
eventfd: EventFd,
|
||||
}
|
||||
|
||||
impl RawFileAsync {
|
||||
pub fn new(fd: RawFd, ring_depth: u32) -> std::io::Result<Self> {
|
||||
let io_uring = IoUring::new(ring_depth)?;
|
||||
let eventfd = EventFd::new(libc::EFD_NONBLOCK)?;
|
||||
|
||||
// Register the io_uring eventfd that will notify when something in
|
||||
// the completion queue is ready.
|
||||
io_uring.submitter().register_eventfd(eventfd.as_raw_fd())?;
|
||||
|
||||
Ok(RawFileAsync {
|
||||
fd,
|
||||
io_uring,
|
||||
eventfd,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for RawFileAsync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
&self.eventfd
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
// Safe because we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
let _ = unsafe {
|
||||
sq.push(
|
||||
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||
.offset(offset)
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::ReadVectored)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
// Safe because we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
let _ = unsafe {
|
||||
sq.push(
|
||||
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||
.offset(offset)
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::WriteVectored)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
if let Some(user_data) = user_data {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
// Safe because we know the file descriptor is valid.
|
||||
let _ = unsafe {
|
||||
sq.push(
|
||||
&opcode::Fsync::new(types::Fd(self.fd))
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::Fsync)?;
|
||||
} else {
|
||||
unsafe { libc::fsync(self.fd) };
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||
let mut completion_list = Vec::new();
|
||||
|
||||
let cq = self.io_uring.completion();
|
||||
for cq_entry in cq {
|
||||
completion_list.push((cq_entry.user_data(), cq_entry.result()));
|
||||
}
|
||||
|
||||
completion_list
|
||||
}
|
||||
}
|
||||
122
block_util/src/raw_sync.rs
Normal file
122
block_util/src/raw_sync.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct RawFileDiskSync {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl RawFileDiskSync {
|
||||
pub fn new(file: File) -> Self {
|
||||
RawFileDiskSync { file }
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for RawFileDiskSync {
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self
|
||||
.file
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(DiskFileError::Size)? as u64)
|
||||
}
|
||||
|
||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(RawFileSync::new(self.file.as_raw_fd())) as Box<dyn AsyncIo>)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawFileSync {
|
||||
fd: RawFd,
|
||||
eventfd: EventFd,
|
||||
completion_list: Vec<(u64, i32)>,
|
||||
}
|
||||
|
||||
impl RawFileSync {
|
||||
pub fn new(fd: RawFd) -> Self {
|
||||
RawFileSync {
|
||||
fd,
|
||||
eventfd: EventFd::new(libc::EFD_NONBLOCK).expect("Failed creating EventFd for RawFile"),
|
||||
completion_list: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for RawFileSync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
&self.eventfd
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
let result = unsafe {
|
||||
libc::preadv(
|
||||
self.fd as libc::c_int,
|
||||
iovecs.as_ptr() as *const libc::iovec,
|
||||
iovecs.len() as libc::c_int,
|
||||
offset,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::ReadVectored(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
self.completion_list.push((user_data, result as i32));
|
||||
self.eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
let result = unsafe {
|
||||
libc::pwritev(
|
||||
self.fd as libc::c_int,
|
||||
iovecs.as_ptr() as *const libc::iovec,
|
||||
iovecs.len() as libc::c_int,
|
||||
offset,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::WriteVectored(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
self.completion_list.push((user_data, result as i32));
|
||||
self.eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
let result = unsafe { libc::fsync(self.fd as libc::c_int) };
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::Fsync(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
if let Some(user_data) = user_data {
|
||||
self.completion_list.push((user_data, result as i32));
|
||||
self.eventfd.write(1).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||
self.completion_list.drain(..).collect()
|
||||
}
|
||||
}
|
||||
221
block_util/src/vhd.rs
Normal file
221
block_util/src/vhd.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct VhdFooter {
|
||||
cookie: u64,
|
||||
features: u32,
|
||||
file_format_version: u32,
|
||||
data_offset: u64,
|
||||
time_stamp: u32,
|
||||
creator_application: u32,
|
||||
creator_version: u32,
|
||||
creator_host_os: u32,
|
||||
original_size: u64,
|
||||
current_size: u64,
|
||||
disk_geometry: u32,
|
||||
disk_type: u32,
|
||||
checksum: u32,
|
||||
unique_id: u128,
|
||||
saved_state: u8,
|
||||
}
|
||||
|
||||
impl VhdFooter {
|
||||
pub fn new(file: &mut File) -> std::io::Result<VhdFooter> {
|
||||
// We must create a buffer aligned on 512 bytes with a size being a
|
||||
// multiple of 512 bytes as the file might be opened with O_DIRECT flag.
|
||||
#[repr(align(512))]
|
||||
struct Sector {
|
||||
data: [u8; 512],
|
||||
}
|
||||
let mut s = Sector { data: [0; 512] };
|
||||
|
||||
// Place the cursor 512 bytes before the end of the file, as this is
|
||||
// where the footer starts.
|
||||
file.seek(SeekFrom::End(-512))?;
|
||||
|
||||
// Fill in the VhdFooter structure
|
||||
file.read_exact(&mut s.data)?;
|
||||
|
||||
Ok(VhdFooter {
|
||||
cookie: u64::from_be_bytes(s.data[0..8].try_into().unwrap()),
|
||||
features: u32::from_be_bytes(s.data[8..12].try_into().unwrap()),
|
||||
file_format_version: u32::from_be_bytes(s.data[12..16].try_into().unwrap()),
|
||||
data_offset: u64::from_be_bytes(s.data[16..24].try_into().unwrap()),
|
||||
time_stamp: u32::from_be_bytes(s.data[24..28].try_into().unwrap()),
|
||||
creator_application: u32::from_be_bytes(s.data[28..32].try_into().unwrap()),
|
||||
creator_version: u32::from_be_bytes(s.data[32..36].try_into().unwrap()),
|
||||
creator_host_os: u32::from_be_bytes(s.data[36..40].try_into().unwrap()),
|
||||
original_size: u64::from_be_bytes(s.data[40..48].try_into().unwrap()),
|
||||
current_size: u64::from_be_bytes(s.data[48..56].try_into().unwrap()),
|
||||
disk_geometry: u32::from_be_bytes(s.data[56..60].try_into().unwrap()),
|
||||
disk_type: u32::from_be_bytes(s.data[60..64].try_into().unwrap()),
|
||||
checksum: u32::from_be_bytes(s.data[64..68].try_into().unwrap()),
|
||||
unique_id: u128::from_be_bytes(s.data[68..84].try_into().unwrap()),
|
||||
saved_state: u8::from_be_bytes(s.data[84..85].try_into().unwrap()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cookie(&self) -> u64 {
|
||||
self.cookie
|
||||
}
|
||||
pub fn features(&self) -> u32 {
|
||||
self.features
|
||||
}
|
||||
pub fn file_format_version(&self) -> u32 {
|
||||
self.file_format_version
|
||||
}
|
||||
pub fn data_offset(&self) -> u64 {
|
||||
self.data_offset
|
||||
}
|
||||
pub fn time_stamp(&self) -> u32 {
|
||||
self.time_stamp
|
||||
}
|
||||
pub fn creator_application(&self) -> u32 {
|
||||
self.creator_application
|
||||
}
|
||||
pub fn creator_version(&self) -> u32 {
|
||||
self.creator_version
|
||||
}
|
||||
pub fn creator_host_os(&self) -> u32 {
|
||||
self.creator_host_os
|
||||
}
|
||||
pub fn original_size(&self) -> u64 {
|
||||
self.original_size
|
||||
}
|
||||
pub fn current_size(&self) -> u64 {
|
||||
self.current_size
|
||||
}
|
||||
pub fn disk_geometry(&self) -> u32 {
|
||||
self.disk_geometry
|
||||
}
|
||||
pub fn disk_type(&self) -> u32 {
|
||||
self.disk_type
|
||||
}
|
||||
pub fn checksum(&self) -> u32 {
|
||||
self.checksum
|
||||
}
|
||||
pub fn unique_id(&self) -> u128 {
|
||||
self.unique_id
|
||||
}
|
||||
pub fn saved_state(&self) -> u8 {
|
||||
self.saved_state
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine image type through file parsing.
|
||||
pub fn is_fixed_vhd(f: &mut File) -> std::io::Result<bool> {
|
||||
let footer = VhdFooter::new(f)?;
|
||||
|
||||
// "conectix" => 0x636f6e6563746978
|
||||
Ok(footer.cookie() == 0x636f6e6563746978
|
||||
&& footer.file_format_version() == 0x0001_0000
|
||||
&& footer.data_offset() == 0xffff_ffff_ffff_ffff
|
||||
&& footer.disk_type() == 0x2)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{is_fixed_vhd, VhdFooter};
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom, Write};
|
||||
use vmm_sys_util::tempfile::TempFile;
|
||||
|
||||
fn valid_fixed_vhd_footer() -> Vec<u8> {
|
||||
vec![
|
||||
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
|
||||
0x00, 0x00, 0x00, 0x02, // features
|
||||
0x00, 0x01, 0x00, 0x00, // file format version
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // data offset
|
||||
0x27, 0xa6, 0xa6, 0x5d, // time stamp
|
||||
0x71, 0x65, 0x6d, 0x75, // creator application
|
||||
0x00, 0x05, 0x00, 0x03, // creator version
|
||||
0x57, 0x69, 0x32, 0x6b, // creator host os
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // original size
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // current size
|
||||
0x11, 0xe0, 0x10, 0x3f, // disk geometry
|
||||
0x00, 0x00, 0x00, 0x02, // disk type
|
||||
0x00, 0x00, 0x00, 0x00, // checksum
|
||||
0x98, 0x7b, 0xb1, 0xcd, 0x84, 0x14, 0x41, 0xfc, 0xa4, 0xab, 0xd0, 0x69, 0x45, 0x2b,
|
||||
0xf2, 0x23, // unique id
|
||||
0x00, // saved state
|
||||
]
|
||||
}
|
||||
|
||||
fn valid_dynamic_vhd_footer() -> Vec<u8> {
|
||||
vec![
|
||||
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
|
||||
0x00, 0x00, 0x00, 0x02, // features
|
||||
0x00, 0x01, 0x00, 0x00, // file format version
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // data offset
|
||||
0x27, 0xa6, 0xa6, 0x5d, // time stamp
|
||||
0x71, 0x65, 0x6d, 0x75, // creator application
|
||||
0x00, 0x05, 0x00, 0x03, // creator version
|
||||
0x57, 0x69, 0x32, 0x6b, // creator host os
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // original size
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // current size
|
||||
0x11, 0xe0, 0x10, 0x3f, // disk geometry
|
||||
0x00, 0x00, 0x00, 0x03, // disk type
|
||||
0x00, 0x00, 0x00, 0x00, // checksum
|
||||
0x98, 0x7b, 0xb1, 0xcd, 0x84, 0x14, 0x41, 0xfc, 0xa4, 0xab, 0xd0, 0x69, 0x45, 0x2b,
|
||||
0xf2, 0x23, // unique id
|
||||
0x00, // saved state
|
||||
]
|
||||
}
|
||||
|
||||
fn with_file<F>(footer: &[u8], mut testfn: F)
|
||||
where
|
||||
F: FnMut(File),
|
||||
{
|
||||
let mut disk_file: File = TempFile::new().unwrap().into_file();
|
||||
disk_file.set_len(0x1000_0200).unwrap();
|
||||
disk_file.seek(SeekFrom::Start(0x1000_0000)).unwrap();
|
||||
disk_file.write_all(&footer).unwrap();
|
||||
|
||||
testfn(disk_file); // File closed when the function exits.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_vhd_footer() {
|
||||
with_file(&valid_fixed_vhd_footer(), |mut file: File| {
|
||||
let vhd_footer = VhdFooter::new(&mut file).expect("Failed to create VHD footer");
|
||||
assert_eq!(vhd_footer.cookie(), 0x636f_6e65_6374_6978);
|
||||
assert_eq!(vhd_footer.features(), 0x0000_0002);
|
||||
assert_eq!(vhd_footer.file_format_version(), 0x0001_0000);
|
||||
assert_eq!(vhd_footer.data_offset(), 0xffff_ffff_ffff_ffff);
|
||||
assert_eq!(vhd_footer.time_stamp(), 0x27a6_a65d);
|
||||
assert_eq!(vhd_footer.creator_application(), 0x7165_6d75);
|
||||
assert_eq!(vhd_footer.creator_version(), 0x0005_0003);
|
||||
assert_eq!(vhd_footer.creator_host_os(), 0x5769_326b);
|
||||
assert_eq!(vhd_footer.original_size(), 0x0000_0000_1000_0000);
|
||||
assert_eq!(vhd_footer.current_size(), 0x0000_0000_1000_0000);
|
||||
assert_eq!(vhd_footer.disk_geometry(), 0x11e0_103f);
|
||||
assert_eq!(vhd_footer.disk_type(), 0x0000_0002);
|
||||
assert_eq!(vhd_footer.checksum(), 0x0000_0000);
|
||||
assert_eq!(
|
||||
vhd_footer.unique_id(),
|
||||
0x987b_b1cd_8414_41fc_a4ab_d069_452b_f223
|
||||
);
|
||||
assert_eq!(vhd_footer.saved_state(), 0x00);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_fixed_vhd() {
|
||||
with_file(&valid_fixed_vhd_footer(), |mut file: File| {
|
||||
assert!(is_fixed_vhd(&mut file).unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_not_fixed_vhd() {
|
||||
with_file(&valid_dynamic_vhd_footer(), |mut file: File| {
|
||||
assert!(!(is_fixed_vhd(&mut file).unwrap()));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,26 +2,24 @@
|
||||
name = "devices"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bitflags = ">=1.2.1"
|
||||
byteorder = "1.3.4"
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-device = { path = "../vm-device" }
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
vm-memory = "0.4.0"
|
||||
anyhow = "1.0"
|
||||
arch = { path = "../arch" }
|
||||
bitflags = ">=1.2.1"
|
||||
byteorder = "1.4.3"
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.96"
|
||||
log = "0.4.14"
|
||||
versionize = "0.1.6"
|
||||
versionize_derive = "0.1.4"
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = "0.5.0"
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
acpi = ["acpi_tables"]
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use super::AcpiNotificationFlags;
|
||||
use acpi_tables::{aml, aml::Aml};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::time::Instant;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
use vm_memory::GuestAddress;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use HotPlugNotificationFlags;
|
||||
|
||||
pub const GED_DEVICE_ACPI_SIZE: usize = 0x1;
|
||||
|
||||
/// A device for handling ACPI shutdown and reboot
|
||||
pub struct AcpiShutdownDevice {
|
||||
@@ -36,7 +39,7 @@ impl BusDevice for AcpiShutdownDevice {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if data[0] == 1 {
|
||||
debug!("ACPI Reboot signalled");
|
||||
if let Err(e) = self.reset_evt.write(1) {
|
||||
@@ -49,33 +52,39 @@ impl BusDevice for AcpiShutdownDevice {
|
||||
const SLEEP_VALUE_BIT: u8 = 2;
|
||||
if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) {
|
||||
debug!("ACPI Shutdown signalled");
|
||||
extern crate bitflags;
|
||||
if let Err(e) = self.exit_evt.write(1) {
|
||||
error!("Error triggering ACPI shutdown event: {}", e);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A device for handling ACPI GED event generation
|
||||
pub struct AcpiGEDDevice {
|
||||
pub struct AcpiGedDevice {
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
notification_type: HotPlugNotificationFlags,
|
||||
notification_type: AcpiNotificationFlags,
|
||||
ged_irq: u32,
|
||||
address: GuestAddress,
|
||||
}
|
||||
|
||||
impl AcpiGEDDevice {
|
||||
pub fn new(interrupt: Arc<Box<dyn InterruptSourceGroup>>, ged_irq: u32) -> AcpiGEDDevice {
|
||||
AcpiGEDDevice {
|
||||
impl AcpiGedDevice {
|
||||
pub fn new(
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
ged_irq: u32,
|
||||
address: GuestAddress,
|
||||
) -> AcpiGedDevice {
|
||||
AcpiGedDevice {
|
||||
interrupt,
|
||||
notification_type: HotPlugNotificationFlags::NO_DEVICES_CHANGED,
|
||||
notification_type: AcpiNotificationFlags::NO_DEVICES_CHANGED,
|
||||
ged_irq,
|
||||
address,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify(
|
||||
&mut self,
|
||||
notification_type: HotPlugNotificationFlags,
|
||||
notification_type: AcpiNotificationFlags,
|
||||
) -> Result<(), std::io::Error> {
|
||||
self.notification_type |= notification_type;
|
||||
self.interrupt.trigger(0)
|
||||
@@ -87,18 +96,16 @@ impl AcpiGEDDevice {
|
||||
}
|
||||
|
||||
// I/O port reports what type of notification was made
|
||||
impl BusDevice for AcpiGEDDevice {
|
||||
impl BusDevice for AcpiGedDevice {
|
||||
// Spec has all fields as zero
|
||||
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||
data[0] = self.notification_type.bits();
|
||||
self.notification_type = HotPlugNotificationFlags::NO_DEVICES_CHANGED;
|
||||
self.notification_type = AcpiNotificationFlags::NO_DEVICES_CHANGED;
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
|
||||
}
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
impl Aml for AcpiGEDDevice {
|
||||
impl Aml for AcpiGedDevice {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
aml::Device::new(
|
||||
"_SB_.GED_".into(),
|
||||
@@ -115,7 +122,12 @@ impl Aml for AcpiGEDDevice {
|
||||
self.ged_irq,
|
||||
)]),
|
||||
),
|
||||
&aml::OpRegion::new("GDST".into(), aml::OpRegionSpace::SystemIO, 0xb000, 0x1),
|
||||
&aml::OpRegion::new(
|
||||
"GDST".into(),
|
||||
aml::OpRegionSpace::SystemMemory,
|
||||
self.address.0 as usize,
|
||||
GED_DEVICE_ACPI_SIZE,
|
||||
),
|
||||
&aml::Field::new(
|
||||
"GDST".into(),
|
||||
aml::FieldAccessType::Byte,
|
||||
@@ -143,6 +155,14 @@ impl Aml for AcpiGEDDevice {
|
||||
&aml::Equal::new(&aml::Local(1), &4usize),
|
||||
vec![&aml::MethodCall::new("\\_SB_.PCI0.PCNT".into(), vec![])],
|
||||
),
|
||||
&aml::And::new(&aml::Local(1), &aml::Local(0), &8usize),
|
||||
&aml::If::new(
|
||||
&aml::Equal::new(&aml::Local(1), &8usize),
|
||||
vec![&aml::Notify::new(
|
||||
&aml::Path::new("\\_SB_.PWRB"),
|
||||
&0x80usize,
|
||||
)],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -151,11 +171,11 @@ impl Aml for AcpiGEDDevice {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AcpiPMTimerDevice {
|
||||
pub struct AcpiPmTimerDevice {
|
||||
start: Instant,
|
||||
}
|
||||
|
||||
impl AcpiPMTimerDevice {
|
||||
impl AcpiPmTimerDevice {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
start: Instant::now(),
|
||||
@@ -163,13 +183,13 @@ impl AcpiPMTimerDevice {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AcpiPMTimerDevice {
|
||||
impl Default for AcpiPmTimerDevice {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for AcpiPMTimerDevice {
|
||||
impl BusDevice for AcpiPmTimerDevice {
|
||||
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||
let now = Instant::now();
|
||||
let since = now.duration_since(self.start);
|
||||
@@ -183,6 +203,4 @@ impl BusDevice for AcpiPMTimerDevice {
|
||||
|
||||
data.copy_from_slice(&counter.to_le_bytes());
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use super::interrupt_controller::{Error, InterruptController};
|
||||
extern crate arch;
|
||||
use arch::aarch64::gic::GicDevice;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
|
||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
||||
};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Reserve 32 IRQs (GSI 32 ~ 64) for legacy device.
|
||||
// GsiAllocator should allocate beyond this: from 64 on
|
||||
// Reserve 32 IRQs for legacy device.
|
||||
pub const IRQ_LEGACY_BASE: usize = arch::layout::IRQ_BASE as usize;
|
||||
pub const IRQ_LEGACY_COUNT: usize = 32;
|
||||
pub const IRQ_SPI_OFFSET: usize = 32;
|
||||
|
||||
// This Gic struct implements InterruptController to provide interrupt delivery service.
|
||||
// The Gic source files in arch/ folder maintain the Aarch64 specific Gic device.
|
||||
@@ -24,6 +27,7 @@ pub const IRQ_SPI_OFFSET: usize = 32;
|
||||
// 2. Move this file and ioapic.rs to arch/, as they are architecture specific.
|
||||
pub struct Gic {
|
||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
gic_device: Option<Arc<Mutex<Box<dyn GicDevice>>>>,
|
||||
}
|
||||
|
||||
impl Gic {
|
||||
@@ -33,22 +37,48 @@ impl Gic {
|
||||
) -> Result<Gic> {
|
||||
let interrupt_source_group = interrupt_manager
|
||||
.create_group(MsiIrqGroupConfig {
|
||||
base: IRQ_SPI_OFFSET as InterruptIndex,
|
||||
base: IRQ_LEGACY_BASE as InterruptIndex,
|
||||
count: IRQ_LEGACY_COUNT as InterruptIndex,
|
||||
})
|
||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||
|
||||
Ok(Gic {
|
||||
interrupt_source_group,
|
||||
gic_device: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_gic_device(&mut self, gic_device: Arc<Mutex<Box<dyn GicDevice>>>) {
|
||||
self.gic_device = Some(gic_device);
|
||||
}
|
||||
|
||||
pub fn get_gic_device(&self) -> Option<&Arc<Mutex<Box<dyn GicDevice>>>> {
|
||||
self.gic_device.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptController for Gic {
|
||||
fn enable(&self) -> Result<()> {
|
||||
// Set irqfd for legacy interrupts
|
||||
self.interrupt_source_group
|
||||
.enable()
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
|
||||
// Set irq_routing for legacy interrupts.
|
||||
// irqchip: Hardcode to 0 as we support only 1 GIC
|
||||
// pin: Use irq number as pin
|
||||
for i in IRQ_LEGACY_BASE..(IRQ_LEGACY_BASE + IRQ_LEGACY_COUNT) {
|
||||
let config = LegacyIrqSourceConfig {
|
||||
irqchip: 0,
|
||||
pin: (i - IRQ_LEGACY_BASE) as u32,
|
||||
};
|
||||
self.interrupt_source_group
|
||||
.update(
|
||||
i as InterruptIndex,
|
||||
InterruptSourceConfig::LegacyIrq(config),
|
||||
)
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -61,4 +91,8 @@ impl InterruptController for Gic {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notifier(&self, irq: usize) -> Option<EventFd> {
|
||||
self.interrupt_source_group.notifier(irq as InterruptIndex)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
use std::io;
|
||||
use std::result;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -56,4 +57,5 @@ pub trait InterruptController: Send {
|
||||
fn enable(&self) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn end_of_interrupt(&mut self, vec: u8);
|
||||
fn notifier(&self, irq: usize) -> Option<EventFd>;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,9 @@ use super::interrupt_controller::{Error, InterruptController};
|
||||
use anyhow::anyhow;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||
@@ -21,13 +23,9 @@ use vm_device::interrupt::{
|
||||
use vm_device::BusDevice;
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "GuestAddress")]
|
||||
pub struct GuestAddressDef(pub u64);
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
@@ -112,9 +110,9 @@ enum TriggerMode {
|
||||
enum DeliveryMode {
|
||||
Fixed = 0b000,
|
||||
Lowest = 0b001,
|
||||
SMI = 0b010, // System management interrupt
|
||||
Smi = 0b010, // System management interrupt
|
||||
RemoteRead = 0b011, // This is no longer supported by intel.
|
||||
NMI = 0b100, // Non maskable interrupt
|
||||
Nmi = 0b100, // Non maskable interrupt
|
||||
Init = 0b101,
|
||||
Startup = 0b110,
|
||||
External = 0b111,
|
||||
@@ -139,15 +137,15 @@ pub struct Ioapic {
|
||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Versionize)]
|
||||
pub struct IoapicState {
|
||||
id_reg: u32,
|
||||
reg_sel: u32,
|
||||
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
|
||||
used_entries: [bool; NUM_IOAPIC_PINS],
|
||||
#[serde(with = "GuestAddressDef")]
|
||||
apic_address: GuestAddress,
|
||||
apic_address: u64,
|
||||
}
|
||||
impl VersionMapped for IoapicState {}
|
||||
|
||||
impl BusDevice for Ioapic {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
@@ -167,7 +165,7 @@ impl BusDevice for Ioapic {
|
||||
LittleEndian::write_u32(data, value);
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
assert!(data.len() == 4);
|
||||
|
||||
debug!("IOAPIC_W @ offset 0x{:x}", offset);
|
||||
@@ -181,6 +179,7 @@ impl BusDevice for Ioapic {
|
||||
error!("IOAPIC: failed writing at offset {}", offset);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +191,7 @@ impl Ioapic {
|
||||
) -> Result<Ioapic> {
|
||||
let interrupt_source_group = interrupt_manager
|
||||
.create_group(MsiIrqGroupConfig {
|
||||
base: 0 as InterruptIndex,
|
||||
base: 0,
|
||||
count: NUM_IOAPIC_PINS as InterruptIndex,
|
||||
})
|
||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||
@@ -282,7 +281,7 @@ impl Ioapic {
|
||||
reg_sel: self.reg_sel,
|
||||
reg_entries: self.reg_entries,
|
||||
used_entries: self.used_entries,
|
||||
apic_address: self.apic_address,
|
||||
apic_address: self.apic_address.0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,7 +290,7 @@ impl Ioapic {
|
||||
self.reg_sel = state.reg_sel;
|
||||
self.reg_entries = state.reg_entries;
|
||||
self.used_entries = state.used_entries;
|
||||
self.apic_address = state.apic_address;
|
||||
self.apic_address = GuestAddress(state.apic_address);
|
||||
for (irq, entry) in self.used_entries.iter().enumerate() {
|
||||
if *entry {
|
||||
self.update_entry(irq)?;
|
||||
@@ -331,9 +330,9 @@ impl Ioapic {
|
||||
match delivery_mode {
|
||||
x if (x == DeliveryMode::Fixed as u8)
|
||||
|| (x == DeliveryMode::Lowest as u8)
|
||||
|| (x == DeliveryMode::SMI as u8)
|
||||
|| (x == DeliveryMode::Smi as u8)
|
||||
|| (x == DeliveryMode::RemoteRead as u8)
|
||||
|| (x == DeliveryMode::NMI as u8)
|
||||
|| (x == DeliveryMode::Nmi as u8)
|
||||
|| (x == DeliveryMode::Init as u8)
|
||||
|| (x == DeliveryMode::Startup as u8)
|
||||
|| (x == DeliveryMode::External as u8) => {}
|
||||
@@ -404,6 +403,10 @@ impl InterruptController for Ioapic {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notifier(&self, irq: usize) -> Option<EventFd> {
|
||||
self.interrupt_source_group.notifier(irq as InterruptIndex)
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for Ioapic {
|
||||
@@ -412,38 +415,18 @@ impl Snapshottable for Ioapic {
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut ioapic_snapshot = Snapshot::new(self.id.as_str());
|
||||
ioapic_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(ioapic_snapshot)
|
||||
Snapshot::new_from_versioned_state(&self.id, &self.state())
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(ioapic_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||
let ioapic_state = match serde_json::from_slice(&ioapic_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize IOAPIC {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
return self.set_state(&ioapic_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not restore IOAPIC state {:?}", e))
|
||||
});
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find IOAPIC snapshot section"
|
||||
)))
|
||||
self.set_state(&snapshot.to_versioned_state(&self.id)?)
|
||||
.map_err(|e| {
|
||||
MigratableError::Restore(anyhow!(
|
||||
"Could not restore state for {}: {:?}",
|
||||
self.id,
|
||||
e
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
use libc::{clock_gettime, gmtime_r, time_t, timespec, tm, CLOCK_REALTIME};
|
||||
use std::cmp::min;
|
||||
use std::mem;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use vm_device::BusDevice;
|
||||
|
||||
const INDEX_MASK: u8 = 0x7f;
|
||||
@@ -44,16 +45,17 @@ impl Cmos {
|
||||
}
|
||||
|
||||
impl BusDevice for Cmos {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if data.len() != 1 {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
|
||||
match offset {
|
||||
INDEX_OFFSET => self.index = data[0] & INDEX_MASK,
|
||||
DATA_OFFSET => self.data[self.index as usize] = data[0],
|
||||
o => panic!("bad write offset on CMOS device: {}", o),
|
||||
}
|
||||
};
|
||||
None
|
||||
}
|
||||
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
//
|
||||
|
||||
use std::sync::{Arc, Barrier};
|
||||
use vm_device::BusDevice;
|
||||
|
||||
/// Provides firmware debug output via I/O port controls
|
||||
@@ -30,11 +31,13 @@ impl BusDevice for FwDebugDevice {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if data.len() == 1 {
|
||||
print!("{}", data[0] as char);
|
||||
} else {
|
||||
error!("Invalid write size on debug port: {}", data.len())
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
460
devices/src/legacy/gpio_pl061.rs
Normal file
460
devices/src/legacy/gpio_pl061.rs
Normal file
@@ -0,0 +1,460 @@
|
||||
// Copyright 2021 Arm Limited (or its affiliates). All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! ARM PrimeCell General Purpose Input/Output(PL061)
|
||||
//!
|
||||
//! This module implements an ARM PrimeCell General Purpose Input/Output(PL061) to support gracefully poweroff microvm from external.
|
||||
//!
|
||||
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
use std::result;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::{fmt, io};
|
||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||
};
|
||||
|
||||
const OFS_DATA: u64 = 0x400; // Data Register
|
||||
const GPIODIR: u64 = 0x400; // Direction Register
|
||||
const GPIOIS: u64 = 0x404; // Interrupt Sense Register
|
||||
const GPIOIBE: u64 = 0x408; // Interrupt Both Edges Register
|
||||
const GPIOIEV: u64 = 0x40c; // Interrupt Event Register
|
||||
const GPIOIE: u64 = 0x410; // Interrupt Mask Register
|
||||
const GPIORIE: u64 = 0x414; // Raw Interrupt Status Register
|
||||
const GPIOMIS: u64 = 0x418; // Masked Interrupt Status Register
|
||||
const GPIOIC: u64 = 0x41c; // Interrupt Clear Register
|
||||
const GPIOAFSEL: u64 = 0x420; // Mode Control Select Register
|
||||
// From 0x424 to 0xFDC => reserved space.
|
||||
// From 0xFE0 to 0xFFC => Peripheral and PrimeCell Identification Registers which are Read Only registers.
|
||||
// Thses registers can conceptually be treated as a 32-bit register, and PartNumber[11:0] is used to identify the peripheral.
|
||||
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
|
||||
const GPIO_ID: [u8; 8] = [0x61, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
||||
// ID Margins
|
||||
const GPIO_ID_LOW: u64 = 0xfe0;
|
||||
const GPIO_ID_HIGH: u64 = 0x1000;
|
||||
|
||||
const N_GPIOS: u32 = 8;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
BadWriteOffset(u64),
|
||||
GpioInterruptDisabled,
|
||||
GpioInterruptFailure(io::Error),
|
||||
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 triggerd: {}.", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// A GPIO device following the PL061 specification.
|
||||
pub struct Gpio {
|
||||
id: String,
|
||||
// Data Register
|
||||
data: u32,
|
||||
old_in_data: u32,
|
||||
// Direction Register
|
||||
dir: u32,
|
||||
// Interrupt Sense Register
|
||||
isense: u32,
|
||||
// Interrupt Both Edges Register
|
||||
ibe: u32,
|
||||
// Interrupt Event Register
|
||||
iev: u32,
|
||||
// Interrupt Mask Register
|
||||
im: u32,
|
||||
// Raw Interrupt Status Register
|
||||
istate: u32,
|
||||
// Mode Control Select Register
|
||||
afsel: u32,
|
||||
// GPIO irq_field
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
}
|
||||
|
||||
#[derive(Versionize)]
|
||||
pub struct GpioState {
|
||||
data: u32,
|
||||
old_in_data: u32,
|
||||
dir: u32,
|
||||
isense: u32,
|
||||
ibe: u32,
|
||||
iev: u32,
|
||||
im: u32,
|
||||
istate: u32,
|
||||
afsel: u32,
|
||||
}
|
||||
|
||||
impl VersionMapped for GpioState {}
|
||||
|
||||
impl Gpio {
|
||||
/// Constructs an PL061 GPIO device.
|
||||
pub fn new(id: String, interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Self {
|
||||
Self {
|
||||
id,
|
||||
data: 0,
|
||||
old_in_data: 0,
|
||||
dir: 0,
|
||||
isense: 0,
|
||||
ibe: 0,
|
||||
iev: 0,
|
||||
im: 0,
|
||||
istate: 0,
|
||||
afsel: 0,
|
||||
interrupt,
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> GpioState {
|
||||
GpioState {
|
||||
data: self.data,
|
||||
old_in_data: self.old_in_data,
|
||||
dir: self.dir,
|
||||
isense: self.isense,
|
||||
ibe: self.ibe,
|
||||
iev: self.iev,
|
||||
im: self.im,
|
||||
istate: self.istate,
|
||||
afsel: self.afsel,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &GpioState) {
|
||||
self.data = state.data;
|
||||
self.old_in_data = state.old_in_data;
|
||||
self.dir = state.dir;
|
||||
self.isense = state.isense;
|
||||
self.ibe = state.ibe;
|
||||
self.iev = state.iev;
|
||||
self.im = state.im;
|
||||
self.istate = state.istate;
|
||||
self.afsel = state.afsel;
|
||||
}
|
||||
|
||||
fn pl061_internal_update(&mut self) {
|
||||
// FIXME:
|
||||
// Missing Output Interrupt Emulation.
|
||||
|
||||
// Input Edging Interrupt Emulation.
|
||||
let changed = ((self.old_in_data ^ self.data) & !self.dir) as u32;
|
||||
if changed > 0 {
|
||||
self.old_in_data = self.data;
|
||||
for i in 0..N_GPIOS {
|
||||
let mask = (1 << i) as u32;
|
||||
if (changed & mask) > 0 {
|
||||
// Bits set high in GPIOIS(Interrupt sense register) configure the corresponding
|
||||
// pins to detect levels, otherwise, detect edges.
|
||||
if (self.isense & mask) == 0 {
|
||||
if (self.ibe & mask) > 0 {
|
||||
// Bits set high in GPIOIBE(Interrupt both-edges register) configure the corresponding
|
||||
// pins to detect both falling and rising edges.
|
||||
// Clearing a bit configures the pin to be controlled by GPIOIEV.
|
||||
self.istate |= mask;
|
||||
} else {
|
||||
// Bits set to high in GPIOIEV(Interrupt event register) configure the
|
||||
// corresponding pin to detect rising edges, otherwise, detect falling edges.
|
||||
self.istate |= !(self.data ^ self.iev) & mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input Level Interrupt Emulation.
|
||||
self.istate |= !(self.data ^ self.iev) & self.isense;
|
||||
}
|
||||
|
||||
fn handle_write(&mut self, offset: u64, val: u32) -> Result<()> {
|
||||
if offset < OFS_DATA {
|
||||
// In order to write to data register, the corresponding bits in the mask, resulting
|
||||
// from the offsite[9:2], must be HIGH. otherwise the bit values remain unchanged.
|
||||
let mask = (offset >> 2) as u32 & self.dir;
|
||||
self.data = (self.data & !mask) | (val & mask);
|
||||
} else {
|
||||
match offset {
|
||||
GPIODIR => {
|
||||
/* Direction Register */
|
||||
self.dir = val & 0xff;
|
||||
}
|
||||
GPIOIS => {
|
||||
/* Interrupt Sense Register */
|
||||
self.isense = val & 0xff;
|
||||
}
|
||||
GPIOIBE => {
|
||||
/* Interrupt Both Edges Register */
|
||||
self.ibe = val & 0xff;
|
||||
}
|
||||
GPIOIEV => {
|
||||
/* Interrupt Event Register */
|
||||
self.iev = val & 0xff;
|
||||
}
|
||||
GPIOIE => {
|
||||
/* Interrupt Mask Register */
|
||||
self.im = val & 0xff;
|
||||
}
|
||||
GPIOIC => {
|
||||
/* Interrupt Clear Register */
|
||||
self.istate &= !val;
|
||||
}
|
||||
GPIOAFSEL => {
|
||||
/* Mode Control Select Register */
|
||||
self.afsel = val & 0xff;
|
||||
}
|
||||
o => {
|
||||
return Err(Error::BadWriteOffset(o));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn trigger_key(&mut self, key: u32) -> Result<()> {
|
||||
let mask = (1 << key) as u32;
|
||||
if (!self.dir & mask) > 0 {
|
||||
// emulate key event
|
||||
// By default, Input Pin is configured to detect both rising and falling edges.
|
||||
// So reverse the input pin data to generate a pulse.
|
||||
self.data |= !(self.data & mask) & mask;
|
||||
self.pl061_internal_update();
|
||||
|
||||
match self.trigger_gpio_interrupt() {
|
||||
Ok(_) | Err(Error::GpioInterruptDisabled) => return Ok(()),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::GpioTriggerKeyFailure(key))
|
||||
}
|
||||
|
||||
fn trigger_gpio_interrupt(&self) -> Result<()> {
|
||||
// Bits set to high in GPIOIE(Interrupt mask register) allow the corresponding pins to
|
||||
// trigger their individual interrupts and then the combined GPIOINTR line.
|
||||
if (self.istate & self.im) == 0 {
|
||||
warn!("Failed to trigger GPIO input interrupt (disabled by guest OS)");
|
||||
return Err(Error::GpioInterruptDisabled);
|
||||
}
|
||||
self.interrupt
|
||||
.trigger(0)
|
||||
.map_err(Error::GpioInterruptFailure)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for Gpio {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
let value;
|
||||
let mut read_ok = true;
|
||||
|
||||
if (GPIO_ID_LOW..GPIO_ID_HIGH).contains(&offset) {
|
||||
let index = ((offset - GPIO_ID_LOW) >> 2) as usize;
|
||||
value = u32::from(GPIO_ID[index]);
|
||||
} else if offset < OFS_DATA {
|
||||
value = self.data & ((offset >> 2) as u32)
|
||||
} else {
|
||||
value = match offset {
|
||||
GPIODIR => self.dir,
|
||||
GPIOIS => self.isense,
|
||||
GPIOIBE => self.ibe,
|
||||
GPIOIEV => self.iev,
|
||||
GPIOIE => self.im,
|
||||
GPIORIE => self.istate,
|
||||
GPIOMIS => self.istate & self.im,
|
||||
GPIOAFSEL => self.afsel,
|
||||
_ => {
|
||||
read_ok = false;
|
||||
0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if read_ok && data.len() <= 4 {
|
||||
write_le_u32(data, value);
|
||||
} else {
|
||||
warn!(
|
||||
"Invalid GPIO PL061 read: offset {}, data length {}",
|
||||
offset,
|
||||
data.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if data.len() <= 4 {
|
||||
let value = read_le_u32(&data);
|
||||
if let Err(e) = self.handle_write(offset, value) {
|
||||
warn!("Failed to write to GPIO PL061 device: {}", e);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Invalid GPIO PL061 write: offset {}, data length {}",
|
||||
offset,
|
||||
data.len()
|
||||
);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for Gpio {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
Snapshot::new_from_versioned_state(&self.id, &self.state())
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
self.set_state(&snapshot.to_versioned_state(&self.id)?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Gpio {}
|
||||
impl Transportable for Gpio {}
|
||||
impl Migratable for Gpio {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
use std::sync::Arc;
|
||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const GPIO_NAME: &str = "gpio";
|
||||
const LEGACY_GPIO_MAPPED_IO_START: u64 = 0x0902_0000;
|
||||
|
||||
struct TestInterrupt {
|
||||
event_fd: EventFd,
|
||||
}
|
||||
|
||||
impl InterruptSourceGroup for TestInterrupt {
|
||||
fn trigger(&self, _index: InterruptIndex) -> result::Result<(), std::io::Error> {
|
||||
self.event_fd.write(1)
|
||||
}
|
||||
|
||||
fn update(
|
||||
&self,
|
||||
_index: InterruptIndex,
|
||||
_config: InterruptSourceConfig,
|
||||
) -> result::Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||
Some(self.event_fd.try_clone().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl TestInterrupt {
|
||||
fn new(event_fd: EventFd) -> Self {
|
||||
TestInterrupt { event_fd }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gpio_read_write_and_event() {
|
||||
let intr_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();
|
||||
let mut gpio = Gpio::new(
|
||||
String::from(GPIO_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
let mut data = [0; 4];
|
||||
|
||||
// Read and write to the GPIODIR register.
|
||||
// Set pin 0 output pin.
|
||||
write_le_u32(&mut data, 1);
|
||||
gpio.write(LEGACY_GPIO_MAPPED_IO_START, GPIODIR, &mut data);
|
||||
gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIODIR, &mut data);
|
||||
let v = read_le_u32(&data);
|
||||
assert_eq!(v, 1);
|
||||
|
||||
// Read and write to the GPIODATA register.
|
||||
write_le_u32(&mut data, 1);
|
||||
// Set pin 0 high.
|
||||
let offset = 0x00000004 as u64;
|
||||
gpio.write(LEGACY_GPIO_MAPPED_IO_START, offset, &mut data);
|
||||
gpio.read(LEGACY_GPIO_MAPPED_IO_START, offset, &mut data);
|
||||
let v = read_le_u32(&data);
|
||||
assert_eq!(v, 1);
|
||||
|
||||
// Read and write to the GPIOIS register.
|
||||
// Configure pin 0 detecting level interrupt.
|
||||
write_le_u32(&mut data, 1);
|
||||
gpio.write(LEGACY_GPIO_MAPPED_IO_START, GPIOIS, &mut data);
|
||||
gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIOIS, &mut data);
|
||||
let v = read_le_u32(&data);
|
||||
assert_eq!(v, 1);
|
||||
|
||||
// Read and write to the GPIOIBE register.
|
||||
// Configure pin 1 detecting both falling and rising edges.
|
||||
write_le_u32(&mut data, 2);
|
||||
gpio.write(LEGACY_GPIO_MAPPED_IO_START, GPIOIBE, &mut data);
|
||||
gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIOIBE, &mut data);
|
||||
let v = read_le_u32(&data);
|
||||
assert_eq!(v, 2);
|
||||
|
||||
// Read and write to the GPIOIEV register.
|
||||
// Configure pin 2 detecting both falling and rising edges.
|
||||
write_le_u32(&mut data, 4);
|
||||
gpio.write(LEGACY_GPIO_MAPPED_IO_START, GPIOIEV, &mut data);
|
||||
gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIOIEV, &mut data);
|
||||
let v = read_le_u32(&data);
|
||||
assert_eq!(v, 4);
|
||||
|
||||
// Read and write to the GPIOIE register.
|
||||
// Configure pin 0...2 capable of triggering their individual interrupts
|
||||
// and then the combined GPIOINTR line.
|
||||
write_le_u32(&mut data, 7);
|
||||
gpio.write(LEGACY_GPIO_MAPPED_IO_START, GPIOIE, &mut data);
|
||||
gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIOIE, &mut data);
|
||||
let v = read_le_u32(&data);
|
||||
assert_eq!(v, 7);
|
||||
|
||||
let mask = 0x00000002 as u32;
|
||||
// emulate an rising pulse in pin 1.
|
||||
gpio.data |= !(gpio.data & mask) & mask;
|
||||
gpio.pl061_internal_update();
|
||||
// The interrupt line on pin 1 should be on.
|
||||
// Read the GPIOMIS register.
|
||||
gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIOMIS, &mut data);
|
||||
let v = read_le_u32(&data);
|
||||
assert_eq!(v, 2);
|
||||
|
||||
// Read and Write to the GPIOIC register.
|
||||
// clear interrupt in pin 1.
|
||||
write_le_u32(&mut data, 2);
|
||||
gpio.write(LEGACY_GPIO_MAPPED_IO_START, GPIOIC, &mut data);
|
||||
gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIOIC, &mut data);
|
||||
let v = read_le_u32(&data);
|
||||
assert_eq!(v, 2);
|
||||
|
||||
// Attempts to write beyond the writable space.
|
||||
write_le_u32(&mut data, 0);
|
||||
gpio.write(LEGACY_GPIO_MAPPED_IO_START, GPIO_ID_LOW, &mut data);
|
||||
|
||||
let mut data = [0; 4];
|
||||
gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIO_ID_LOW, &mut data);
|
||||
let index = GPIO_ID_LOW + 3;
|
||||
assert_eq!(data[0], GPIO_ID[((index - GPIO_ID_LOW) >> 2) as usize]);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use std::sync::{Arc, Barrier};
|
||||
use vm_device::BusDevice;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// A i8042 PS/2 controller that emulates just enough to shutdown the machine.
|
||||
pub struct I8042Device {
|
||||
@@ -32,12 +32,14 @@ impl BusDevice for I8042Device {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if data.len() == 1 && data[0] == 0xfe && offset == 3 {
|
||||
debug!("i8042 reset signalled");
|
||||
if let Err(e) = self.reset_evt.write(1) {
|
||||
error!("Error triggering i8042 reset event: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,14 @@
|
||||
mod cmos;
|
||||
#[cfg(feature = "fwdebug")]
|
||||
mod fwdebug;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mod gpio_pl061;
|
||||
mod i8042;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mod rtc_pl031;
|
||||
mod serial;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mod uart_pl011;
|
||||
|
||||
#[cfg(feature = "cmos")]
|
||||
pub use self::cmos::Cmos;
|
||||
@@ -22,4 +26,10 @@ pub use self::i8042::I8042Device;
|
||||
pub use self::serial::Serial;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use self::rtc_pl031::RTC;
|
||||
pub use self::gpio_pl061::Error as GpioDeviceError;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use self::gpio_pl061::Gpio;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use self::rtc_pl031::Rtc;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use self::uart_pl011::Pl011;
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
//! This is achieved by generating an interrupt signal after counting for a programmed number of cycles of
|
||||
//! a real-time clock input.
|
||||
//!
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::time::Instant;
|
||||
use std::{io, result};
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
@@ -38,52 +39,6 @@ const AMBA_ID_HIGH: u64 = 0x1000;
|
||||
/// Constant to convert seconds to nanoseconds.
|
||||
pub const NANOS_PER_SECOND: u64 = 1_000_000_000;
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! generate_read_fn {
|
||||
($fn_name: ident, $data_type: ty, $byte_type: ty, $type_size: expr, $endian_type: ident) => {
|
||||
#[allow(dead_code)]
|
||||
pub fn $fn_name(input: &[$byte_type]) -> $data_type {
|
||||
assert!($type_size == std::mem::size_of::<$data_type>());
|
||||
let mut array = [0u8; $type_size];
|
||||
for (byte, read) in array.iter_mut().zip(input.iter().cloned()) {
|
||||
*byte = read as u8;
|
||||
}
|
||||
<$data_type>::$endian_type(array)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! generate_write_fn {
|
||||
($fn_name: ident, $data_type: ty, $byte_type: ty, $endian_type: ident) => {
|
||||
#[allow(dead_code)]
|
||||
pub fn $fn_name(buf: &mut [$byte_type], n: $data_type) {
|
||||
for (byte, read) in buf
|
||||
.iter_mut()
|
||||
.zip(<$data_type>::$endian_type(n).iter().cloned())
|
||||
{
|
||||
*byte = read as $byte_type;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
generate_read_fn!(read_le_u16, u16, u8, 2, from_le_bytes);
|
||||
generate_read_fn!(read_le_u32, u32, u8, 4, from_le_bytes);
|
||||
generate_read_fn!(read_le_u64, u64, u8, 8, from_le_bytes);
|
||||
generate_read_fn!(read_le_i32, i32, i8, 4, from_le_bytes);
|
||||
|
||||
generate_read_fn!(read_be_u16, u16, u8, 2, from_be_bytes);
|
||||
generate_read_fn!(read_be_u32, u32, u8, 4, from_be_bytes);
|
||||
|
||||
generate_write_fn!(write_le_u16, u16, u8, to_le_bytes);
|
||||
generate_write_fn!(write_le_u32, u32, u8, to_le_bytes);
|
||||
generate_write_fn!(write_le_u64, u64, u8, to_le_bytes);
|
||||
generate_write_fn!(write_le_i32, i32, i8, to_le_bytes);
|
||||
|
||||
generate_write_fn!(write_be_u16, u16, u8, to_be_bytes);
|
||||
generate_write_fn!(write_be_u32, u32, u8, to_be_bytes);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
BadWriteOffset(u64),
|
||||
@@ -115,9 +70,9 @@ pub enum ClockType {
|
||||
ThreadCpu,
|
||||
}
|
||||
|
||||
impl Into<libc::clockid_t> for ClockType {
|
||||
fn into(self) -> libc::clockid_t {
|
||||
match self {
|
||||
impl From<ClockType> for libc::clockid_t {
|
||||
fn from(ct: ClockType) -> libc::clockid_t {
|
||||
match ct {
|
||||
ClockType::Monotonic => libc::CLOCK_MONOTONIC,
|
||||
ClockType::Real => libc::CLOCK_REALTIME,
|
||||
ClockType::ProcessCpu => libc::CLOCK_PROCESS_CPUTIME_ID,
|
||||
@@ -260,7 +215,7 @@ pub fn seconds_to_nanoseconds(value: i64) -> Option<i64> {
|
||||
}
|
||||
|
||||
/// A RTC device following the PL031 specification..
|
||||
pub struct RTC {
|
||||
pub struct Rtc {
|
||||
previous_now: Instant,
|
||||
tick_offset: i64,
|
||||
// This is used for implementing the RTC alarm. However, in Firecracker we do not need it.
|
||||
@@ -272,10 +227,10 @@ pub struct RTC {
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
}
|
||||
|
||||
impl RTC {
|
||||
impl Rtc {
|
||||
/// Constructs an AMBA PL031 RTC device.
|
||||
pub fn new(interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> RTC {
|
||||
RTC {
|
||||
pub fn new(interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Self {
|
||||
Self {
|
||||
// This is used only for duration measuring purposes.
|
||||
previous_now: Instant::now(),
|
||||
tick_offset: get_time(ClockType::Real) as i64,
|
||||
@@ -334,12 +289,12 @@ impl RTC {
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for RTC {
|
||||
impl BusDevice for Rtc {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
let v;
|
||||
let mut read_ok = true;
|
||||
|
||||
if offset < AMBA_ID_HIGH && offset >= AMBA_ID_LOW {
|
||||
if (AMBA_ID_LOW..AMBA_ID_HIGH).contains(&offset) {
|
||||
let index = ((offset - AMBA_ID_LOW) >> 2) as usize;
|
||||
v = u32::from(PL031_ID[index]);
|
||||
} else {
|
||||
@@ -371,9 +326,9 @@ impl BusDevice for RTC {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if data.len() <= 4 {
|
||||
let v = read_le_u32(&data[..]);
|
||||
let v = read_le_u32(&data);
|
||||
if let Err(e) = self.handle_write(offset, v) {
|
||||
warn!("Failed to write to RTC PL031 device: {}", e);
|
||||
}
|
||||
@@ -384,12 +339,18 @@ impl BusDevice for RTC {
|
||||
data.len()
|
||||
);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u32, read_le_u64, write_be_u16,
|
||||
write_be_u32, write_le_i32, write_le_u16, write_le_u32, write_le_u64,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
@@ -474,8 +435,8 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notifier(&self, _index: InterruptIndex) -> Option<&EventFd> {
|
||||
Some(&self.event_fd)
|
||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||
Some(self.event_fd.try_clone().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,7 +450,7 @@ mod tests {
|
||||
fn test_rtc_read_write_and_event() {
|
||||
let intr_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();
|
||||
|
||||
let mut rtc = RTC::new(Arc::new(Box::new(TestInterrupt::new(
|
||||
let mut rtc = Rtc::new(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
let mut data = [0; 4];
|
||||
@@ -498,7 +459,7 @@ mod tests {
|
||||
write_le_u32(&mut data, 123);
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCMR, &mut data);
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCMR, &mut data);
|
||||
let v = read_le_u32(&data[..]);
|
||||
let v = read_le_u32(&data);
|
||||
assert_eq!(v, 123);
|
||||
|
||||
// Read and write to the LR register.
|
||||
@@ -510,7 +471,7 @@ mod tests {
|
||||
assert!(rtc.previous_now > previous_now_before);
|
||||
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCLR, &mut data);
|
||||
let v_read = read_le_u32(&data[..]);
|
||||
let v_read = read_le_u32(&data);
|
||||
assert_eq!((v / NANOS_PER_SECOND) as u32, v_read);
|
||||
|
||||
// Read and write to IMSC register.
|
||||
@@ -521,14 +482,14 @@ mod tests {
|
||||
// The interrupt line should be on.
|
||||
assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() == 1);
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
|
||||
let v = read_le_u32(&data[..]);
|
||||
let v = read_le_u32(&data);
|
||||
assert_eq!(non_zero & 1, v);
|
||||
|
||||
// Now test with 0.
|
||||
write_le_u32(&mut data, 0);
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
|
||||
let v = read_le_u32(&data[..]);
|
||||
let v = read_le_u32(&data);
|
||||
assert_eq!(0, v);
|
||||
|
||||
// Read and write to the ICR register.
|
||||
@@ -536,10 +497,10 @@ mod tests {
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCICR, &mut data);
|
||||
// The interrupt line should be on.
|
||||
assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() > 1);
|
||||
let v_before = read_le_u32(&data[..]);
|
||||
let v_before = read_le_u32(&data);
|
||||
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCICR, &mut data);
|
||||
let v = read_le_u32(&data[..]);
|
||||
let v = read_le_u32(&data);
|
||||
// ICR is a write only register. Data received should stay equal to data sent.
|
||||
assert_eq!(v, v_before);
|
||||
|
||||
@@ -547,7 +508,7 @@ mod tests {
|
||||
write_le_u32(&mut data, 0);
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCCR, &mut data);
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCCR, &mut data);
|
||||
let v = read_le_u32(&data[..]);
|
||||
let v = read_le_u32(&data);
|
||||
assert_eq!(v, 1);
|
||||
|
||||
// Attempts to write beyond the writable space. Using here the space used to read
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use anyhow::anyhow;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::{io, result};
|
||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||
};
|
||||
use vmm_sys_util::errno::Result;
|
||||
|
||||
@@ -74,7 +74,7 @@ pub struct Serial {
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Versionize)]
|
||||
pub struct SerialState {
|
||||
interrupt_enable: u8,
|
||||
interrupt_identification: u8,
|
||||
@@ -84,8 +84,9 @@ pub struct SerialState {
|
||||
modem_status: u8,
|
||||
scratch: u8,
|
||||
baud_divisor: u16,
|
||||
in_buffer: VecDeque<u8>,
|
||||
in_buffer: Vec<u8>,
|
||||
}
|
||||
impl VersionMapped for SerialState {}
|
||||
|
||||
impl Serial {
|
||||
pub fn new(
|
||||
@@ -227,7 +228,7 @@ impl Serial {
|
||||
modem_status: self.modem_status,
|
||||
scratch: self.scratch,
|
||||
baud_divisor: self.baud_divisor,
|
||||
in_buffer: self.in_buffer.clone(),
|
||||
in_buffer: self.in_buffer.clone().into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +241,7 @@ impl Serial {
|
||||
self.modem_status = state.modem_status;
|
||||
self.scratch = state.scratch;
|
||||
self.baud_divisor = state.baud_divisor;
|
||||
self.in_buffer = state.in_buffer.clone();
|
||||
self.in_buffer = state.in_buffer.clone().into();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,12 +276,14 @@ impl BusDevice for Serial {
|
||||
};
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if data.len() != 1 {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Err(_e) = self.handle_write(offset as u8, data[0]) {}
|
||||
self.handle_write(offset as u8, data[0]).ok();
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,38 +293,12 @@ impl Snapshottable for Serial {
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut serial_snapshot = Snapshot::new(self.id.as_str());
|
||||
serial_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(serial_snapshot)
|
||||
Snapshot::new_from_versioned_state(&self.id, &self.state())
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(serial_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||
let serial_state = match serde_json::from_slice(&serial_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize SERIAL {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
self.set_state(&serial_state);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find the serial snapshot section"
|
||||
)))
|
||||
self.set_state(&snapshot.to_versioned_state(&self.id)?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,6 +331,9 @@ mod tests {
|
||||
) -> result::Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||
Some(self.event_fd.try_clone().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl TestInterrupt {
|
||||
@@ -472,13 +452,13 @@ mod tests {
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
|
||||
serial.write(0, LCR as u64, &[LCR_DLAB_BIT as u8]);
|
||||
serial.write(0, DLAB_LOW as u64, &[0x12 as u8]);
|
||||
serial.write(0, DLAB_HIGH as u64, &[0x34 as u8]);
|
||||
serial.write(0, LCR as u64, &[LCR_DLAB_BIT]);
|
||||
serial.write(0, DLAB_LOW as u64, &[0x12]);
|
||||
serial.write(0, DLAB_HIGH as u64, &[0x34]);
|
||||
|
||||
let mut data = [0u8];
|
||||
serial.read(0, LCR as u64, &mut data[..]);
|
||||
assert_eq!(data[0], LCR_DLAB_BIT as u8);
|
||||
assert_eq!(data[0], LCR_DLAB_BIT);
|
||||
serial.read(0, DLAB_LOW as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 0x12);
|
||||
serial.read(0, DLAB_HIGH as u64, &mut data[..]);
|
||||
@@ -493,16 +473,16 @@ mod tests {
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
|
||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT as u8]);
|
||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT]);
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
serial.write(0, DATA as u64, &[b'b']);
|
||||
serial.write(0, DATA as u64, &[b'c']);
|
||||
|
||||
let mut data = [0u8];
|
||||
serial.read(0, MSR as u64, &mut data[..]);
|
||||
assert_eq!(data[0], DEFAULT_MODEM_STATUS as u8);
|
||||
assert_eq!(data[0], DEFAULT_MODEM_STATUS);
|
||||
serial.read(0, MCR as u64, &mut data[..]);
|
||||
assert_eq!(data[0], MCR_LOOP_BIT as u8);
|
||||
assert_eq!(data[0], MCR_LOOP_BIT);
|
||||
serial.read(0, DATA as u64, &mut data[..]);
|
||||
assert_eq!(data[0], b'a');
|
||||
serial.read(0, DATA as u64, &mut data[..]);
|
||||
@@ -519,10 +499,10 @@ mod tests {
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
|
||||
serial.write(0, SCR as u64, &[0x12 as u8]);
|
||||
serial.write(0, SCR as u64, &[0x12]);
|
||||
|
||||
let mut data = [0u8];
|
||||
serial.read(0, SCR as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 0x12 as u8);
|
||||
assert_eq!(data[0], 0x12);
|
||||
}
|
||||
}
|
||||
|
||||
482
devices/src/legacy/uart_pl011.rs
Normal file
482
devices/src/legacy/uart_pl011.rs
Normal file
@@ -0,0 +1,482 @@
|
||||
// Copyright 2021 Arm Limited (or its affiliates). All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! ARM PrimeCell UART(PL011)
|
||||
//!
|
||||
//! This module implements an ARM PrimeCell UART(PL011).
|
||||
//!
|
||||
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::{io, result};
|
||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||
};
|
||||
|
||||
/* Registers */
|
||||
const UARTDR: u64 = 0;
|
||||
const UARTRSR_UARTECR: u64 = 1;
|
||||
const UARTFR: u64 = 6;
|
||||
const UARTILPR: u64 = 8;
|
||||
const UARTIBRD: u64 = 9;
|
||||
const UARTFBRD: u64 = 10;
|
||||
const UARTLCR_H: u64 = 11;
|
||||
const UARTCR: u64 = 12;
|
||||
const UARTIFLS: u64 = 13;
|
||||
const UARTIMSC: u64 = 14;
|
||||
const UARTRIS: u64 = 15;
|
||||
const UARTMIS: u64 = 16;
|
||||
const UARTICR: u64 = 17;
|
||||
const UARTDMACR: u64 = 18;
|
||||
|
||||
const PL011_INT_TX: u32 = 0x20;
|
||||
const PL011_INT_RX: u32 = 0x10;
|
||||
|
||||
const PL011_FLAG_RXFF: u32 = 0x40;
|
||||
const PL011_FLAG_RXFE: u32 = 0x10;
|
||||
|
||||
const PL011_ID: [u8; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
||||
// We are only interested in the margins.
|
||||
const AMBA_ID_LOW: u64 = 0x3f8;
|
||||
const AMBA_ID_HIGH: u64 = 0x401;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
BadWriteOffset(u64),
|
||||
DmaNotImplemented,
|
||||
InterruptFailure(io::Error),
|
||||
WriteAllFailure(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>;
|
||||
|
||||
/// A PL011 device following the PL011 specification.
|
||||
pub struct Pl011 {
|
||||
id: String,
|
||||
flags: u32,
|
||||
lcr: u32,
|
||||
rsr: u32,
|
||||
cr: u32,
|
||||
dmacr: u32,
|
||||
int_enabled: u32,
|
||||
int_level: u32,
|
||||
read_fifo: VecDeque<u8>,
|
||||
ilpr: u32,
|
||||
ibrd: u32,
|
||||
fbrd: u32,
|
||||
ifl: u32,
|
||||
read_count: u32,
|
||||
read_trigger: u32,
|
||||
irq: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
}
|
||||
|
||||
#[derive(Versionize)]
|
||||
pub struct Pl011State {
|
||||
flags: u32,
|
||||
lcr: u32,
|
||||
rsr: u32,
|
||||
cr: u32,
|
||||
dmacr: u32,
|
||||
int_enabled: u32,
|
||||
int_level: u32,
|
||||
read_fifo: Vec<u8>,
|
||||
ilpr: u32,
|
||||
ibrd: u32,
|
||||
fbrd: u32,
|
||||
ifl: u32,
|
||||
read_count: u32,
|
||||
read_trigger: u32,
|
||||
}
|
||||
|
||||
impl VersionMapped for Pl011State {}
|
||||
|
||||
impl Pl011 {
|
||||
/// Constructs an AMBA PL011 UART device.
|
||||
pub fn new(
|
||||
id: String,
|
||||
irq: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
flags: 0x90u32,
|
||||
lcr: 0u32,
|
||||
rsr: 0u32,
|
||||
cr: 0x300u32,
|
||||
dmacr: 0u32,
|
||||
int_enabled: 0u32,
|
||||
int_level: 0u32,
|
||||
read_fifo: VecDeque::new(),
|
||||
ilpr: 0u32,
|
||||
ibrd: 0u32,
|
||||
fbrd: 0u32,
|
||||
ifl: 0x12u32,
|
||||
read_count: 0u32,
|
||||
read_trigger: 1u32,
|
||||
irq,
|
||||
out,
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> Pl011State {
|
||||
Pl011State {
|
||||
flags: self.flags,
|
||||
lcr: self.lcr,
|
||||
rsr: self.rsr,
|
||||
cr: self.cr,
|
||||
dmacr: self.dmacr,
|
||||
int_enabled: self.int_enabled,
|
||||
int_level: self.int_level,
|
||||
read_fifo: self.read_fifo.clone().into(),
|
||||
ilpr: self.ilpr,
|
||||
ibrd: self.ibrd,
|
||||
fbrd: self.fbrd,
|
||||
ifl: self.ifl,
|
||||
read_count: self.read_count,
|
||||
read_trigger: self.read_trigger,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &Pl011State) {
|
||||
self.flags = state.flags;
|
||||
self.lcr = state.lcr;
|
||||
self.rsr = state.rsr;
|
||||
self.cr = state.cr;
|
||||
self.dmacr = state.dmacr;
|
||||
self.int_enabled = state.int_enabled;
|
||||
self.int_level = state.int_level;
|
||||
self.read_fifo = state.read_fifo.clone().into();
|
||||
self.ilpr = state.ilpr;
|
||||
self.ibrd = state.ibrd;
|
||||
self.fbrd = state.fbrd;
|
||||
self.ifl = state.ifl;
|
||||
self.read_count = state.read_count;
|
||||
self.read_trigger = state.read_trigger;
|
||||
}
|
||||
|
||||
/// Queues raw bytes for the guest to read and signals the interrupt
|
||||
pub fn queue_input_bytes(&mut self, c: &[u8]) -> vmm_sys_util::errno::Result<()> {
|
||||
self.read_fifo.extend(c);
|
||||
self.read_count += c.len() as u32;
|
||||
self.flags &= !PL011_FLAG_RXFE;
|
||||
|
||||
if ((self.lcr & 0x10) == 0) || (self.read_count == 16) {
|
||||
self.flags |= PL011_FLAG_RXFF;
|
||||
}
|
||||
|
||||
if self.read_count >= self.read_trigger {
|
||||
self.int_level |= PL011_INT_RX;
|
||||
self.trigger_interrupt()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pl011_get_baudrate(&self) -> u32 {
|
||||
if self.fbrd == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let clk = 24_000_000; // We set the APB_PLCK to 24M in device tree
|
||||
(clk / ((self.ibrd << 6) + self.fbrd)) << 2
|
||||
}
|
||||
|
||||
fn pl011_trace_baudrate_change(&self) {
|
||||
debug!(
|
||||
"=== New baudrate: {:#?} (clk: {:#?}Hz, ibrd: {:#?}, fbrd: {:#?}) ===",
|
||||
self.pl011_get_baudrate(),
|
||||
24_000_000, // We set the APB_PLCK to 24M in device tree
|
||||
self.ibrd,
|
||||
self.fbrd
|
||||
);
|
||||
}
|
||||
|
||||
fn pl011_set_read_trigger(&mut self) {
|
||||
self.read_trigger = 1;
|
||||
}
|
||||
|
||||
fn handle_write(&mut self, offset: u64, val: u32) -> Result<()> {
|
||||
match offset >> 2 {
|
||||
UARTDR => {
|
||||
self.int_level |= PL011_INT_TX;
|
||||
if let Some(out) = self.out.as_mut() {
|
||||
out.write_all(&[val.to_le_bytes()[0]])
|
||||
.map_err(Error::WriteAllFailure)?;
|
||||
out.flush().map_err(Error::FlushFailure)?;
|
||||
}
|
||||
}
|
||||
UARTRSR_UARTECR => {
|
||||
self.rsr = 0;
|
||||
}
|
||||
UARTFR => { /* Writes to Flag register are ignored.*/ }
|
||||
UARTILPR => {
|
||||
self.ilpr = val;
|
||||
}
|
||||
UARTIBRD => {
|
||||
self.ibrd = val;
|
||||
self.pl011_trace_baudrate_change();
|
||||
}
|
||||
UARTFBRD => {
|
||||
self.fbrd = val;
|
||||
self.pl011_trace_baudrate_change();
|
||||
}
|
||||
UARTLCR_H => {
|
||||
/* Reset the FIFO state on FIFO enable or disable */
|
||||
if ((self.lcr ^ val) & 0x10) != 0 {
|
||||
self.read_count = 0;
|
||||
}
|
||||
self.lcr = val;
|
||||
self.pl011_set_read_trigger();
|
||||
}
|
||||
UARTCR => {
|
||||
self.cr = val;
|
||||
}
|
||||
UARTIFLS => {
|
||||
self.ifl = val;
|
||||
self.pl011_set_read_trigger();
|
||||
}
|
||||
UARTIMSC => {
|
||||
self.int_enabled = val;
|
||||
self.trigger_interrupt().map_err(Error::InterruptFailure)?;
|
||||
}
|
||||
UARTICR => {
|
||||
self.int_level &= !val;
|
||||
self.trigger_interrupt().map_err(Error::InterruptFailure)?;
|
||||
}
|
||||
UARTDMACR => {
|
||||
self.dmacr = val;
|
||||
if (val & 3) != 0 {
|
||||
return Err(Error::DmaNotImplemented);
|
||||
}
|
||||
}
|
||||
off => {
|
||||
return Err(Error::BadWriteOffset(off));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn trigger_interrupt(&mut self) -> result::Result<(), io::Error> {
|
||||
self.irq.trigger(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for Pl011 {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
let v;
|
||||
let mut read_ok = true;
|
||||
if (AMBA_ID_LOW..AMBA_ID_HIGH).contains(&(offset >> 2)) {
|
||||
let index = ((offset - 0xfe0) >> 2) as usize;
|
||||
v = u32::from(PL011_ID[index]);
|
||||
} else {
|
||||
v = match offset >> 2 {
|
||||
UARTDR => {
|
||||
let c: u32;
|
||||
let r: u32;
|
||||
|
||||
self.flags &= !PL011_FLAG_RXFF;
|
||||
c = self.read_fifo.pop_front().unwrap_or_default().into();
|
||||
if self.read_count > 0 {
|
||||
self.read_count -= 1;
|
||||
}
|
||||
if self.read_count == 0 {
|
||||
self.flags |= PL011_FLAG_RXFE;
|
||||
}
|
||||
if self.read_count == (self.read_trigger - 1) {
|
||||
self.int_level &= !PL011_INT_RX;
|
||||
}
|
||||
self.rsr = c >> 8;
|
||||
r = c;
|
||||
r
|
||||
}
|
||||
UARTRSR_UARTECR => self.rsr,
|
||||
UARTFR => self.flags,
|
||||
UARTILPR => self.ilpr,
|
||||
UARTIBRD => self.ibrd,
|
||||
UARTFBRD => self.fbrd,
|
||||
UARTLCR_H => self.lcr,
|
||||
UARTCR => self.cr,
|
||||
UARTIFLS => self.ifl,
|
||||
UARTIMSC => self.int_enabled,
|
||||
UARTRIS => self.int_level,
|
||||
UARTMIS => (self.int_level & self.int_enabled),
|
||||
UARTDMACR => self.dmacr,
|
||||
_ => {
|
||||
read_ok = false;
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if read_ok && data.len() <= 4 {
|
||||
write_le_u32(data, v);
|
||||
} else {
|
||||
warn!(
|
||||
"Invalid PL011 read: offset {}, data length {}",
|
||||
offset,
|
||||
data.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if data.len() <= 4 {
|
||||
let v = read_le_u32(&data);
|
||||
if let Err(e) = self.handle_write(offset, v) {
|
||||
warn!("Failed to write to PL011 device: {}", e);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Invalid PL011 write: offset {}, data length {}",
|
||||
offset,
|
||||
data.len()
|
||||
);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for Pl011 {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
Snapshot::new_from_versioned_state(&self.id, &self.state())
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
self.set_state(&snapshot.to_versioned_state(&self.id)?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Pl011 {}
|
||||
impl Transportable for Pl011 {}
|
||||
impl Migratable for Pl011 {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const SERIAL_NAME: &str = "serial";
|
||||
|
||||
struct TestInterrupt {
|
||||
event_fd: EventFd,
|
||||
}
|
||||
|
||||
impl InterruptSourceGroup for TestInterrupt {
|
||||
fn trigger(&self, _index: InterruptIndex) -> result::Result<(), std::io::Error> {
|
||||
self.event_fd.write(1)
|
||||
}
|
||||
fn update(
|
||||
&self,
|
||||
_index: InterruptIndex,
|
||||
_config: InterruptSourceConfig,
|
||||
) -> result::Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||
Some(self.event_fd.try_clone().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl TestInterrupt {
|
||||
fn new(event_fd: EventFd) -> Self {
|
||||
TestInterrupt { event_fd }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SharedBuffer {
|
||||
buf: Arc<Mutex<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl SharedBuffer {
|
||||
fn new() -> SharedBuffer {
|
||||
SharedBuffer {
|
||||
buf: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for SharedBuffer {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.buf.lock().unwrap().write(buf)
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.buf.lock().unwrap().flush()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pl011_output() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let pl011_out = SharedBuffer::new();
|
||||
let mut pl011 = Pl011::new(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
Some(Box::new(pl011_out.clone())),
|
||||
);
|
||||
|
||||
pl011.write(0, UARTDR as u64, &[b'x', b'y']);
|
||||
pl011.write(0, UARTDR as u64, &[b'a']);
|
||||
pl011.write(0, UARTDR as u64, &[b'b']);
|
||||
pl011.write(0, UARTDR as u64, &[b'c']);
|
||||
assert_eq!(
|
||||
pl011_out.buf.lock().unwrap().as_slice(),
|
||||
&[b'x', b'a', b'b', b'c']
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pl011_input() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let pl011_out = SharedBuffer::new();
|
||||
let mut pl011 = Pl011::new(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
Some(Box::new(pl011_out)),
|
||||
);
|
||||
|
||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
assert!(intr_evt.write(1).is_ok());
|
||||
pl011.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
|
||||
|
||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||
|
||||
let mut data = [0u8];
|
||||
pl011.read(0, UARTDR as u64, &mut data);
|
||||
assert_eq!(data[0], b'a');
|
||||
pl011.read(0, UARTDR as u64, &mut data);
|
||||
assert_eq!(data[0], b'b');
|
||||
pl011.read(0, UARTDR as u64, &mut data);
|
||||
assert_eq!(data[0], b'c');
|
||||
}
|
||||
}
|
||||
@@ -6,27 +6,14 @@
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
//! Emulates virtual and hardware devices.
|
||||
extern crate anyhow;
|
||||
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
extern crate byteorder;
|
||||
extern crate epoll;
|
||||
extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg(feature = "acpi")]
|
||||
extern crate acpi_tables;
|
||||
extern crate serde;
|
||||
extern crate vm_device;
|
||||
extern crate vm_memory;
|
||||
extern crate vm_migration;
|
||||
extern crate vmm_sys_util;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
mod acpi;
|
||||
pub mod acpi;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod gic;
|
||||
pub mod interrupt_controller;
|
||||
@@ -35,13 +22,74 @@ pub mod ioapic;
|
||||
pub mod legacy;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
pub use self::acpi::{AcpiGEDDevice, AcpiPMTimerDevice, AcpiShutdownDevice};
|
||||
pub use self::acpi::{AcpiGedDevice, AcpiPmTimerDevice, AcpiShutdownDevice};
|
||||
|
||||
bitflags! {
|
||||
pub struct HotPlugNotificationFlags: u8 {
|
||||
pub struct AcpiNotificationFlags: u8 {
|
||||
const NO_DEVICES_CHANGED = 0;
|
||||
const CPU_DEVICES_CHANGED = 0b1;
|
||||
const MEMORY_DEVICES_CHANGED = 0b10;
|
||||
const PCI_DEVICES_CHANGED = 0b100;
|
||||
const POWER_BUTTON_CHANGED = 0b1000;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
macro_rules! generate_read_fn {
|
||||
($fn_name: ident, $data_type: ty, $byte_type: ty, $type_size: expr, $endian_type: ident) => {
|
||||
#[allow(dead_code)]
|
||||
pub fn $fn_name(input: &[$byte_type]) -> $data_type {
|
||||
assert!($type_size == std::mem::size_of::<$data_type>());
|
||||
let mut array = [0u8; $type_size];
|
||||
for (byte, read) in array.iter_mut().zip(input.iter().cloned()) {
|
||||
*byte = read as u8;
|
||||
}
|
||||
<$data_type>::$endian_type(array)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
macro_rules! generate_write_fn {
|
||||
($fn_name: ident, $data_type: ty, $byte_type: ty, $endian_type: ident) => {
|
||||
#[allow(dead_code)]
|
||||
pub fn $fn_name(buf: &mut [$byte_type], n: $data_type) {
|
||||
for (byte, read) in buf
|
||||
.iter_mut()
|
||||
.zip(<$data_type>::$endian_type(n).iter().cloned())
|
||||
{
|
||||
*byte = read as $byte_type;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
generate_read_fn!(read_le_u16, u16, u8, 2, from_le_bytes);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
generate_read_fn!(read_le_u32, u32, u8, 4, from_le_bytes);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
generate_read_fn!(read_le_u64, u64, u8, 8, from_le_bytes);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
generate_read_fn!(read_le_i32, i32, i8, 4, from_le_bytes);
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
generate_read_fn!(read_be_u16, u16, u8, 2, from_be_bytes);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
generate_read_fn!(read_be_u32, u32, u8, 4, from_be_bytes);
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
generate_write_fn!(write_le_u16, u16, u8, to_le_bytes);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
generate_write_fn!(write_le_u32, u32, u8, to_le_bytes);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
generate_write_fn!(write_le_u64, u64, u8, to_le_bytes);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
generate_write_fn!(write_le_i32, i32, i8, to_le_bytes);
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
generate_write_fn!(write_be_u16, u16, u8, to_be_bytes);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
generate_write_fn!(write_be_u32, u32, u8, to_be_bytes);
|
||||
|
||||
@@ -78,19 +78,27 @@ Ubuntu distributions.
|
||||
|
||||
```bash
|
||||
apt update
|
||||
apt install fio iperf iperf3 socat
|
||||
apt install fio iperf iperf3 socat stress
|
||||
```
|
||||
|
||||
### Remove snapd
|
||||
### Remove counterproductive packages
|
||||
|
||||
* snapd:
|
||||
|
||||
This prevents snapd from trying to mount squashfs filesystem when the kernel
|
||||
might not support it. This might be the case when the image is used with direct
|
||||
kernel boot. This step is specific to Ubuntu distributions.
|
||||
|
||||
* pollinate:
|
||||
|
||||
Remove this package which can fail and lead to the SSH daemon failing to start.
|
||||
See #2113 for details.
|
||||
|
||||
```bash
|
||||
apt remove --purge snapd
|
||||
apt remove --purge snapd pollinate
|
||||
```
|
||||
|
||||
|
||||
### Cleanup the image
|
||||
|
||||
Leave no trace in the image before unmounting its content.
|
||||
@@ -108,15 +116,18 @@ umount /mnt
|
||||
Renaming is important to identify this is a modified image.
|
||||
|
||||
```bash
|
||||
mv focal-server-cloudimg-amd64.raw focal-server-cloudimg-amd64-custom.raw
|
||||
mv focal-server-cloudimg-amd64.raw focal-server-cloudimg-amd64-custom-$(date "+%Y%m%d")-0.raw
|
||||
```
|
||||
|
||||
The `-0` is the revision and is only necessary to change if multiple images are
|
||||
updated on the same day.
|
||||
|
||||
### Create QCOW2 from RAW
|
||||
|
||||
Last step is to create the QCOW2 image back from the modified image.
|
||||
|
||||
```bash
|
||||
qemu-img convert -p -f raw -O qcow2 focal-server-cloudimg-amd64-custom.raw focal-server-cloudimg-amd64-custom.qcow2
|
||||
qemu-img convert -p -f raw -O qcow2 focal-server-cloudimg-amd64-custom-$(date "+%Y%m%d")-0.raw focal-server-cloudimg-amd64-custom-$(date "+%Y%m%d")-0.qcow2
|
||||
```
|
||||
|
||||
## Switch CI to use the new image
|
||||
@@ -134,3 +145,5 @@ Last step is about updating the integration tests to work with this new image.
|
||||
The key point is to identify where the Linux filesystem partition is located,
|
||||
as we might need to update the direct kernel boot command line, replacing
|
||||
`/dev/vda1` with the appropriate partition number.
|
||||
|
||||
Update all references to the previous image name to the new one.
|
||||
|
||||
@@ -28,9 +28,13 @@ This document describes the device model supported by `cloud-hypervisor`.
|
||||
### Serial port
|
||||
|
||||
Simple emulation of a serial port by reading and writing to specific port I/O
|
||||
addresses. Used as the default console for Linux when booting with the option
|
||||
`console=ttyS0`, the serial port can be very useful to gather early logs from
|
||||
the operating system booted inside the VM.
|
||||
addresses. The serial port can be very useful to gather early logs from the
|
||||
operating system booted inside the VM.
|
||||
|
||||
For x86_64, The default serial port is from an emulated 16550A device. It can
|
||||
be used as the default console for Linux when booting with the option
|
||||
`console=ttyS0`. For AArch64, the default serial port is from an emulated
|
||||
PL011 UART device. The related command line for AArch64 is `console=ttyAMA0`.
|
||||
|
||||
This device is always built-in, and it is disabled by default. It can be
|
||||
enabled with the `--serial` option, as long as its parameter is not `off`.
|
||||
@@ -44,6 +48,10 @@ This device is built-in by default, but it can be compiled out with Rust
|
||||
features. When compiled in, it is always enabled, and cannot be disabled
|
||||
from the command line.
|
||||
|
||||
For AArch64 machines, an ARM PrimeCell Real Time Clock(PL031) is implemented.
|
||||
This device is built-in by default for the AArch64 platform, and it is always
|
||||
enabled, and cannot be disabled from the command line.
|
||||
|
||||
### I/O APIC
|
||||
|
||||
`cloud-hypervisor` supports a so-called split IRQ chip implementation by
|
||||
@@ -65,6 +73,11 @@ enabled by default, the handling of reboot/shutdown goes through the dedicated
|
||||
ACPI device. In case ACPI is disabled, this device is enabled to bring to the
|
||||
VM some reboot/shutdown support.
|
||||
|
||||
### ARM PrimeCell General Purpose Input/Output (PL061)
|
||||
|
||||
Simplified ARM PrimeCell GPIO (PL061) implementation. Only supports key 3 to
|
||||
trigger a graceful shutdown of the AArch64 guest.
|
||||
|
||||
### ACPI device
|
||||
|
||||
This is a dedicated device for handling ACPI shutdown and reboot when ACPI is
|
||||
@@ -100,9 +113,8 @@ selecting `--serial tty --console off` from the command line.
|
||||
### virtio-iommu
|
||||
|
||||
As we want to improve our nested guests support, we added support for exposing
|
||||
a [paravirtualized IOMMU](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/iommu.md)
|
||||
device through virtio. This allows for a safer nested virtio and directly
|
||||
assigned devices support.
|
||||
a [paravirtualized IOMMU](iommu.md) device through virtio. This allows for a
|
||||
safer nested virtio and directly assigned devices support.
|
||||
|
||||
This device is always built-in, and it is enabled based on the presence of the
|
||||
parameter `iommu=on` in any of the virtio or VFIO devices. If at least one of
|
||||
@@ -171,8 +183,8 @@ This device is always built-in, and it is enabled when `vhost_user=true` and
|
||||
shared file system, allowing for an efficient and reliable way of sharing
|
||||
a filesystem between the host and the cloud-hypervisor guest.
|
||||
|
||||
See our [filesystem sharing](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/fs.md)
|
||||
documentation for more details on how to use virtio-fs with cloud-hypervisor.
|
||||
See our [filesystem sharing](fs.md) documentation for more details on how to
|
||||
use virtio-fs with cloud-hypervisor.
|
||||
|
||||
This device is always built-in, and it is enabled based on the presence of the
|
||||
flag `--fs`.
|
||||
@@ -193,9 +205,8 @@ VFIO (Virtual Function I/O) is a kernel framework that exposes direct device
|
||||
access to userspace. `cloud-hypervisor` uses VFIO to directly assign host
|
||||
physical devices into its guest.
|
||||
|
||||
See our [VFIO documentation](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/vfio.md)
|
||||
for more details on how to directly assign host devices to `cloud-hypervisor`
|
||||
guests.
|
||||
See our [VFIO documentation](vfio.md) for more details on how to directly
|
||||
assign host devices to `cloud-hypervisor` guests.
|
||||
|
||||
Because VFIO implies `vfio-pci` in the `cloud-hypervisor` context, the VFIO
|
||||
support is built-in when the `pci` feature is selected. And because the `pci`
|
||||
|
||||
29
docs/fs.md
29
docs/fs.md
@@ -12,7 +12,7 @@ This virtual device relies on the _vhost-user_ protocol, which assumes the backe
|
||||
|
||||
_Build virtiofsd_
|
||||
```bash
|
||||
git clone --depth 1 "https://github.com/sboeuf/qemu.git" -b "virtio-fs" $VIRTIOFSD_DIR
|
||||
git clone --depth 1 "https://gitlab.com/virtio-fs/qemu.git" -b "qemu5.0-virtiofs-dax" $VIRTIOFSD_DIR
|
||||
cd $VIRTIOFSD_DIR
|
||||
./configure --prefix=$PWD --target-list=x86_64-softmmu
|
||||
make virtiofsd -j `nproc`
|
||||
@@ -35,45 +35,42 @@ The `cache=none` option should be the default when using `virtiofsd` with the __
|
||||
|
||||
The `cache=always` option will allow for the guest page cache to be used, which will increase the memory footprint of the guest. This option should be used only for specific use cases where a single VM is going to be running on a host.
|
||||
|
||||
### The kernel
|
||||
### Kernel support
|
||||
|
||||
In order to leverage __virtio-fs__ support from within the guest, and because the code has not been merged in upstream Linux kernel yet, it is required to build a custom kernel embedding the patches.
|
||||
|
||||
The following branch `virtio-fs-virtio-iommu` on the repository https://github.com/cloud-hypervisor/linux.git includes all the needed patches to support __virtio-fs__.
|
||||
|
||||
Make sure to build a kernel out of this branch that can be then used to boot the VM.
|
||||
Modern Linux kernels starting (at least v5.10) have support for virtio-fs. Use
|
||||
of older kernels, with additional patches, are not supported.
|
||||
|
||||
## How to share directories with cloud-hypervisor
|
||||
|
||||
### Start the VM
|
||||
Once the daemon is running, the option `--fs` from __cloud-hypervisor__ needs to be used.
|
||||
|
||||
Direct kernel boot option is preferred since we need to provide the custom kernel including the __virtio-fs__ patches. We could boot from `hypervisor-fw` if we had previously edited the image to replace the kernel binary.
|
||||
Direct kernel boot is the preferred option, but we can boot from an EFI cloud image if it contains a recent enough kernel.
|
||||
|
||||
Because _vhost-user_ expects a dedicated process (__virtiofsd__ in this case) to be able to access the guest RAM to communicate through the _virtqueues_ with the driver running in the guest, `--memory` option needs to be slightly modified. It needs to specify a backing file for the memory so that an external process can access it.
|
||||
Because _vhost-user_ expects a dedicated process (__virtiofsd__ in this case) to be able to access the guest RAM to communicate through the _virtqueues_ with the driver running in the guest, `--memory` option needs to be slightly modified. It must specify `shared=on` to share the memory pages so that an external process can access them.
|
||||
|
||||
Assuming you have `focal-server-cloudimg-amd64.raw` and `custom-vmlinux.bin` on your system, here is the __cloud-hypervisor__ command you need to run:
|
||||
Assuming you have `focal-server-cloudimg-amd64.raw` and `vmlinux` on your system, here is the __cloud-hypervisor__ command you need to run:
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus 4 \
|
||||
--memory "size=512M,shared=on" \
|
||||
--cpus boot=1 \
|
||||
--memory size=1G,shared=on \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--kernel custom-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--kernel vmlinux \
|
||||
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
||||
--fs tag=myfs,socket=/tmp/virtiofs,num_queues=1,queue_size=512
|
||||
```
|
||||
|
||||
By default, DAX is enabled with a cache window of 8GiB. You can specify a custom size (let's say 4GiB for this example) for the cache by explicitly setting DAX and the cache size:
|
||||
|
||||
```bash
|
||||
--fs tag=virtiofs,socket=/tmp/virtiofs,num_queues=1,queue_size=512,dax=on,cache_size=4G
|
||||
--fs tag=myfs,socket=/tmp/virtiofs,num_queues=1,queue_size=512,dax=on,cache_size=4G
|
||||
|
||||
```
|
||||
|
||||
In case you don't want to use a shared window of cache to pass the shared files content, this means you will have to explicitly disable DAX with `dax=off`. Note that in this case, the `cache_size` parameter will be ignored.
|
||||
|
||||
```bash
|
||||
--fs tag=virtiofs,socket=/tmp/virtiofs,num_queues=1,queue_size=512,dax=off
|
||||
--fs tag=myfs,socket=/tmp/virtiofs,num_queues=1,queue_size=512,dax=off
|
||||
|
||||
```
|
||||
|
||||
|
||||
151
docs/hotplug.md
151
docs/hotplug.md
@@ -27,16 +27,16 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
--rng \
|
||||
--api-socket=/tmp/ch-socket
|
||||
--api-socket=/tmp/ch-socket
|
||||
$ popd
|
||||
```
|
||||
|
||||
Notice the addition of `--api-socket=/tmp/ch-socket` and a `max` parameter on `--cpus boot=4.max=8`.
|
||||
Notice the addition of `--api-socket=/tmp/ch-socket` and a `max` parameter on `--cpus boot=4,max=8`.
|
||||
|
||||
To ask the VMM to add additional vCPUs then use the resize API:
|
||||
|
||||
```shell
|
||||
curl -H "Accept: application/json" -H "Content-Type: application/json" -i -XPUT --unix-socket /tmp/ch-socket -d "{ \"desired_vcpus\":8}" http://localhost/api/v1/vm.resize
|
||||
./ch-remote --api-socket=/tmp/ch-socket resize --cpus 8
|
||||
```
|
||||
|
||||
The extra vCPU threads will be created and advertised to the running kernel. The kernel does not bring up the CPUs immediately and instead the user must "online" them from inside the VM:
|
||||
@@ -56,19 +56,23 @@ 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:
|
||||
|
||||
```shell
|
||||
curl -H "Accept: application/json" -H "Content-Type: application/json" -i -XPUT --unix-socket /tmp/ch-socket -d "{ \"desired_vcpus\":2}" http://localhost/api/v1/vm.resize
|
||||
./ch-remote --api-socket=/tmp/ch-socket resize --cpus 2
|
||||
```
|
||||
|
||||
As per adding CPUs to the guest, after a reboot the VM will be running with the reduced number of vCPUs.
|
||||
|
||||
## Memory Hot Plug
|
||||
|
||||
### ACPI method
|
||||
|
||||
Extra memory can be added from a running Cloud Hypervisor instance. This is controlled by two mechanisms:
|
||||
|
||||
1. Allocating some of the guest physical address space for hotplug memory.
|
||||
2. Making a HTTP API request to the VMM to ask for a new amount of RAM to be assigned to the VM. In the case of expanding the memory for the VM the new memory will be hotplugged into the running VM, if reducing the size of the memory then change will take effect after the next reboot.
|
||||
|
||||
To use memory hotplug start the VM specifying some size RAM in the "hotplug_size" parameter to the memory configuration. Not all the memory specified in this parameter will be available to hotplug as there are spacing and alignment requirements so it is recommended to make it larger than the hotplug RAM needed.
|
||||
To use memory hotplug start the VM specifying some size RAM in the `hotplug_size` parameter to the memory configuration. Not all the memory specified in this parameter will be available to hotplug as there are spacing and alignment requirements so it is recommended to make it larger than the hotplug RAM needed.
|
||||
|
||||
Because the ACPI method is the default, there is no need to add the extra option `hotplug_method=acpi`.
|
||||
|
||||
```shell
|
||||
$ pushd $CLOUDH
|
||||
@@ -81,7 +85,7 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--memory size=1024M,hotplug_size=8192M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
--rng \
|
||||
--api-socket=/tmp/ch-socket
|
||||
--api-socket=/tmp/ch-socket
|
||||
$ popd
|
||||
```
|
||||
|
||||
@@ -91,10 +95,10 @@ Before issuing the API request it is necessary to run the following command insi
|
||||
root@ch-guest ~ # echo online | sudo tee /sys/devices/system/memory/auto_online_blocks
|
||||
```
|
||||
|
||||
To ask the VMM to add expand the RAM for the VM (request is in bytes):
|
||||
To ask the VMM to expand the RAM for the VM:
|
||||
|
||||
```shell
|
||||
curl -H "Accept: application/json" -H "Content-Type: application/json" -i -XPUT --unix-socket /tmp/ch-socket -d "{ \"desired_vcpus\": 4, \"desired_ram\" : 3221225472}" http://localhost/api/v1/vm.resize
|
||||
./ch-remote --api-socket=/tmp/ch-socket resize --memory 3G
|
||||
```
|
||||
|
||||
The new memory is now available to use inside the VM:
|
||||
@@ -111,3 +115,134 @@ Due to guest OS limitations is is necessary to ensure that amount of memory adde
|
||||
The same API can also be used to reduce the desired RAM for a VM but the change will not be applied until the VM is rebooted.
|
||||
|
||||
Memory and CPU resizing can be combined together into the same HTTP API request.
|
||||
|
||||
### virtio-mem method
|
||||
|
||||
Extra memory can be added and removed from a running Cloud Hypervisor instance. This is controlled by two mechanisms:
|
||||
|
||||
1. Allocating some of the guest physical address space for hotplug memory.
|
||||
2. Making a HTTP API request to the VMM to ask for a new amount of RAM to be assigned to the VM.
|
||||
|
||||
To use memory hotplug start the VM specifying some size RAM in the `hotplug_size` parameter along with `hotplug_method=virtio-mem` to the memory configuration.
|
||||
|
||||
```shell
|
||||
$ pushd $CLOUDH
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel custom-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--memory size=1024M,hotplug_size=8192M,hotplug_method=virtio-mem \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
--api-socket=/tmp/ch-socket
|
||||
$ popd
|
||||
```
|
||||
|
||||
To ask the VMM to expand the RAM for the VM (request is in bytes):
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket resize --memory 3G
|
||||
```
|
||||
|
||||
The new memory is now available to use inside the VM:
|
||||
|
||||
```shell
|
||||
free -h
|
||||
total used free shared buff/cache available
|
||||
Mem: 3.0Gi 71Mi 2.8Gi 0.0Ki 47Mi 2.8Gi
|
||||
Swap: 32Mi 0B 32Mi
|
||||
```
|
||||
|
||||
The same API can also be used to reduce the desired RAM for a VM. It is important to note that reducing RAM size might only partially work, as the guest might be using some of it.
|
||||
|
||||
## PCI Device Hot Plug
|
||||
|
||||
Extra PCI devices can be added and removed from a running Cloud Hypervisor instance. This is controlled by making a HTTP API request to the VMM to ask for the additional device to be added, or for the existing device to be removed.
|
||||
|
||||
To use PCI device hotplug start the VM with the HTTP server.
|
||||
|
||||
```shell
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel custom-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
--api-socket=/tmp/ch-socket
|
||||
```
|
||||
|
||||
Notice the addition of `--api-socket=/tmp/ch-socket`.
|
||||
|
||||
### Add VFIO Device
|
||||
|
||||
To ask the VMM to add additional VFIO device then use the `add-device` API.
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket add-device path=/sys/bus/pci/devices/0000:01:00.0/
|
||||
```
|
||||
|
||||
### Add Disk Device
|
||||
|
||||
To ask the VMM to add additional disk device then use the `add-disk` API.
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket add-disk path=/foo/bar/cloud.img
|
||||
```
|
||||
|
||||
### Add Fs Device
|
||||
|
||||
To ask the VMM to add additional fs device then use the `add-fs` API.
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket add-fs tag=myfs,socket=/foo/bar/virtiofs.sock
|
||||
```
|
||||
|
||||
### Add Net Device
|
||||
|
||||
To ask the VMM to add additional network device then use the `add-net` API.
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket add-net tap=chtap0
|
||||
```
|
||||
|
||||
### Add Pmem Device
|
||||
|
||||
To ask the VMM to add additional PMEM device then use the `add-pmem` API.
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket add-pmem file=/foo/bar.cloud.img
|
||||
```
|
||||
|
||||
### Add Vsock Device
|
||||
|
||||
To ask the VMM to add additional vsock device then use the `add-vsock` API.
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket add-vsock cid=3,socket=/foo/bar/vsock.sock
|
||||
```
|
||||
|
||||
### Common Across All PCI Devices
|
||||
|
||||
The extra PCI device will be created and advertised to the running kernel. The new device can be found by checking the list of PCI devices.
|
||||
|
||||
```shell
|
||||
root@ch-guest ~ # lspci
|
||||
00:00.0 Host bridge: Intel Corporation Device 0d57
|
||||
00:01.0 Unassigned class [ffff]: Red Hat, Inc. Virtio console (rev 01)
|
||||
00:02.0 Mass storage controller: Red Hat, Inc. Virtio block device (rev 01)
|
||||
00:03.0 Unassigned class [ffff]: Red Hat, Inc. Virtio RNG (rev 01)
|
||||
```
|
||||
|
||||
After a reboot the added PCI device will remain.
|
||||
|
||||
### Remove PCI device
|
||||
|
||||
Removing a PCI device works the same way for all kind of PCI devices. The unique identifier related to the device must be provided. This identifier can be provided by the user when adding the new device, or by default Cloud Hypervisor will assign one.
|
||||
|
||||
```shell
|
||||
./ch-remote --api-socket=/tmp/ch-socket remove-device _disk0
|
||||
```
|
||||
|
||||
As per adding a PCI device to the guest, after a reboot the VM will be running without the removed PCI device.
|
||||
|
||||
@@ -8,7 +8,7 @@ the host kernel. The required Linux and KVM changes can be found in the
|
||||
[KVM SGX Tree](https://github.com/intel/kvm-sgx).
|
||||
|
||||
Utilizing SGX in the guest requires a kernel/OS with SGX support, e.g. a kernel
|
||||
built using the [SGX Linux Development Tree](https://github.com/jsakkine-intel/linux-sgx.git)
|
||||
built using the [SGX Linux Development Tree](https://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-sgx.git)
|
||||
or the [KVM SGX Tree](https://github.com/intel/kvm-sgx). Running KVM SGX as the
|
||||
guest kernel allows nested virtualization of SGX.
|
||||
|
||||
@@ -19,7 +19,7 @@ following [instructions](https://github.com/intel/linux-sgx).
|
||||
|
||||
## Cloud-Hypervisor support
|
||||
|
||||
Assuming the host exposes `/dev/sgx/virt_epc`, we can pass SGX enclaves through
|
||||
Assuming the host exposes `/dev/sgx_vepc`, we can pass SGX enclaves through
|
||||
the guest.
|
||||
|
||||
In order to use SGX enclaves within a Cloud-Hypervisor VM, we must define one
|
||||
@@ -32,7 +32,7 @@ memory, the second one being 32MiB with no pre-allocated memory.
|
||||
--cpus boot=1 \
|
||||
--memory size=1G \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--kernel bzImage \
|
||||
--kernel vmlinux \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--sgx-epc size=64M,prefault=on size=32M,prefault=off
|
||||
```
|
||||
@@ -42,14 +42,14 @@ Once booted, and assuming your guest kernel contains the patches from the
|
||||
have been correctly created under `/dev/sgx`:
|
||||
|
||||
```bash
|
||||
ls /dev/sgx
|
||||
enclave provision virt_epc
|
||||
ls /dev/sgx*
|
||||
/dev/sgx_enclave /dev/sgx_provision /dev/sgx_vepc
|
||||
```
|
||||
|
||||
From this point, it is possible to run any SGX application from the guest, as
|
||||
it will access `/dev/sgx/enclave` device to create dedicated SGX enclaves.
|
||||
it will access `/dev/sgx_enclave` device to create dedicated SGX enclaves.
|
||||
|
||||
Note: There is only one contiguous SGX EPC region, which contains all SGX EPC
|
||||
sections. This region is exposed through ACPI and marked as reserved through
|
||||
the e820 table. It is treated yet as another device, which means it should
|
||||
the e820 table. It is treated as yet another device, which means it should
|
||||
appear at the end of the guest address space.
|
||||
|
||||
44
docs/io_throttling.md
Normal file
44
docs/io_throttling.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# I/O Throttling
|
||||
|
||||
Cloud Hypervisor now supports I/O throttling on virtio-block and virtio-net
|
||||
devices. This support is based on the [`rate-limiter` module](https://github.com/firecracker-microvm/firecracker/tree/master/src/rate_limiter)
|
||||
from Firecracker. This document explains the user interface of this
|
||||
feature, and highlights some internal implementations that can help users
|
||||
better understand the expected behavior of I/O throttling in practice.
|
||||
|
||||
Cloud Hypervisor allows to limit both the I/O bandwidth (e.g. bytes/s)
|
||||
and I/O operations (ops/s) independently. For virtio-net devices, while
|
||||
sharing the same "rate limit" from user inputs (on both bandwidth and
|
||||
operations), the RX and TX queues are throttled independently.
|
||||
To limit the I/O bandwidth, Cloud Hypervisor
|
||||
provides three user options, i.e., `bw_size` (bytes), `bw_one_time_burst`
|
||||
(bytes), and `bw_refill_time` (ms). Both `bw_size` and `bw_refill_time`
|
||||
are required, while `bw_one_time_burst` is optional.
|
||||
Internally, these options define a TokenBucket with a maximum capacity
|
||||
(`bw_size` bytes), an initial burst size (`bw_one_time_burst`) and an
|
||||
interval for refilling purposes (`bw_refill_time`). The "refill-rate" is
|
||||
`bw_size` bytes per `bw_refill_time` ms, and it is the constant rate at
|
||||
which the tokens replenish. The refill process only starts happening
|
||||
after the initial burst budget is consumed. Consumption from the token
|
||||
bucket is unbounded in speed which allows for bursts bound in size by
|
||||
the amount of tokens available. Once the token bucket is empty,
|
||||
consumption speed is bound by the "refill-rate". Similarly, Cloud
|
||||
Hypervisor provides another three options for limiting I/O operations,
|
||||
i.e., `ops_size` (I/O operations), `bw_one_time_burst` (I/O operations),
|
||||
and `bw_refill_time` (ms).
|
||||
|
||||
One caveat in the I/O throttling is that every-time the bucket gets
|
||||
empty, it will stop I/O operations for a fixed amount of time
|
||||
(`cool_down_time`). The `cool_down_time` now is fixed at `100 ms`, it
|
||||
can have big implications to the actual rate limit (which can be a lot
|
||||
different the expected "refill-rate" derived from user inputs). For
|
||||
example, to have a 1000 IOPS limit on a virtio-blk device, users should
|
||||
be able to provide either of the following two options:
|
||||
`ops_size=1000,ops_refill_time=1000` or
|
||||
`ops_size=10,ops_refill_time=10`. However, the actual IOPS limits are
|
||||
likely to be ~1000 IOPS and ~100 IOPS respectively. The reason is the
|
||||
actual rate limit users get can be as low as
|
||||
`ops_size/(ops_refill_time+cool_down_time)`. As a result, it is
|
||||
generally advisable to keep `bw/ops_refill_time` larger than `100 ms`
|
||||
(`cool_down_time`) to make sure the actual rate limit is close to users'
|
||||
expectation ("refill-rate").
|
||||
@@ -89,7 +89,7 @@ virtual IOMMU:
|
||||
--cpus boot=1 \
|
||||
--memory size=512M \
|
||||
--disk path=focal-server-cloudimg-amd64.raw,iommu=on \
|
||||
--kernel custom-bzImage \
|
||||
--kernel custom-vmlinux \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
```
|
||||
|
||||
@@ -166,7 +166,7 @@ be consumed.
|
||||
--cpus boot=1 \
|
||||
--memory size=8G,hugepages=on \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--kernel custom-bzImage \
|
||||
--kernel custom-vmlinux \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw hugepagesz=2M hugepages=2048" \
|
||||
--net tap=,mac=,iommu=on
|
||||
```
|
||||
@@ -183,7 +183,7 @@ passing through is `0000:00:01.0`.
|
||||
--cpus boot=1 \
|
||||
--memory size=8G,hugepages=on \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--kernel custom-bzImage \
|
||||
--kernel custom-vmlinux \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw kvm-intel.nested=1 vfio_iommu_type1.allow_unsafe_interrupts rw hugepagesz=2M hugepages=2048" \
|
||||
--device path=/sys/bus/pci/devices/0000:00:01.0,iommu=on
|
||||
```
|
||||
@@ -194,6 +194,7 @@ guest, and bind it to VFIO (it should appear as `0000:00:04.0`).
|
||||
```bash
|
||||
echo 0000:00:04.0 > /sys/bus/pci/devices/0000\:00\:04.0/driver/unbind
|
||||
echo 8086 1502 > /sys/bus/pci/drivers/vfio-pci/new_id
|
||||
echo 0000:00:04.0 > /sys/bus/pci/drivers/vfio-pci/bind
|
||||
```
|
||||
|
||||
Last thing is to start the L2 guest with the huge pages memory backend.
|
||||
@@ -203,7 +204,7 @@ Last thing is to start the L2 guest with the huge pages memory backend.
|
||||
--cpus boot=1 \
|
||||
--memory size=4G,hugepages=on \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--kernel custom-bzImage \
|
||||
--kernel custom-vmlinux \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--device path=/sys/bus/pci/devices/0000:00:04.0
|
||||
```
|
||||
|
||||
139
docs/live_migration.md
Normal file
139
docs/live_migration.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Live Migration
|
||||
|
||||
This document gives two examples of how to use the live migration
|
||||
support in Cloud Hypervisor:
|
||||
|
||||
1. local migration - migrating between two VMs running on the same
|
||||
machine;
|
||||
1. nested-vm migration - migrating between two nested VMs whose host VMs
|
||||
are running on the same machine.
|
||||
|
||||
## Local Migration
|
||||
Launch the source VM (on the host machine):
|
||||
```bash
|
||||
$ target/release/cloud-hypervisor
|
||||
--kernel ~/workloads/vmlinux \
|
||||
--disk path=~/workloads/focal.raw \
|
||||
--cpus boot=1 --memory size=1G \
|
||||
--cmdline "root=/dev/vda1 console=ttyS0" \
|
||||
--serial tty --console off --api-socket=/tmp/api1
|
||||
```
|
||||
|
||||
Launch the destination VM from the same directory (on the host machine):
|
||||
```bash
|
||||
$ target/release/cloud-hypervisor --api-socket=/tmp/api2
|
||||
```
|
||||
|
||||
Get ready for receiving migration for the destination VM (on the host machine):
|
||||
```bash
|
||||
$ target/release/ch-remote --api-socket=/tmp/api2 receive-migration unix:/tmp/sock
|
||||
```
|
||||
|
||||
Start to send migration for the source VM (on the host machine):
|
||||
```bash
|
||||
$ target/release/ch-remote --api-socket=/tmp/api1 send-migration unix:/tmp/sock
|
||||
```
|
||||
|
||||
When the above commands completed, the source VM should be successfully
|
||||
migrated to the destination VM. Now the destination VM is running while
|
||||
the source VM is paused.
|
||||
|
||||
## Nested-VM Migration
|
||||
|
||||
Launch VM 1 (on the host machine) with an extra virtio-blk device for
|
||||
exposing a guest image for the nested source VM:
|
||||
> Note: the example below also attached an additional virtio-blk device
|
||||
> with a dummy image for testing purpose (which is optional).
|
||||
```bash
|
||||
$ head -c 1M < /dev/urandom > tmp.img # create a dummy image for testing
|
||||
$ sudo /target/release/cloud-hypervisor \
|
||||
--serial tty --console off \
|
||||
--cpus boot=1 --memory size=512M \
|
||||
--kernel vmlinux \
|
||||
--cmdline "root=/dev/vda1 console=ttyS0" \
|
||||
--disk path=focal-1.raw path=focal-nested.raw path=tmp.img\
|
||||
--net ip=192.168.101.1
|
||||
```
|
||||
|
||||
Launch VM 2 (on the host machine) with an extra virtio-blk device for
|
||||
exposing the same guest image for the nested destination VM:
|
||||
```bash
|
||||
$ sudo /target/release/cloud-hypervisor \
|
||||
--serial tty --console off \
|
||||
--cpus boot=1 --memory size=512M \
|
||||
--kernel vmlinux \
|
||||
--cmdline "root=/dev/vda1 console=ttyS0" \
|
||||
--disk path=focal-2.raw path=focal-nested.raw path=tmp.img\
|
||||
--net ip=192.168.102.1
|
||||
```
|
||||
|
||||
Launch the nested source VM (inside the guest OS of the VM 1) :
|
||||
```bash
|
||||
vm-1:~$ sudo ./cloud-hypervisor \
|
||||
--serial tty --console off \
|
||||
--memory size=128M \
|
||||
--kernel vmlinux \
|
||||
--cmdline "console=ttyS0 root=/dev/vda1" \
|
||||
--disk path=/dev/vdb path=/dev/vdc \
|
||||
--api-socket=/tmp/api1 \
|
||||
--net ip=192.168.100.1
|
||||
vm-1:~$ # setup the guest network if needed
|
||||
vm-1:~$ sudo ip addr add 192.168.101.2/24 dev ens4
|
||||
vm-1:~$ sudo ip link set up dev ens4
|
||||
vm-1:~$ sudo ip r add default via 192.168.101.1
|
||||
```
|
||||
Optional: Run the guest workload below (on the guest OS of the nested source VM),
|
||||
which performs intensive virtio-blk operations. Now the console of the nested
|
||||
source VM should repeatedly print `"equal"`, and our goal is migrating
|
||||
this VM and the running workload without interruption.
|
||||
```bash
|
||||
#/bin/bash
|
||||
|
||||
# On the guest OS of the nested source VM
|
||||
|
||||
input="/dev/vdb"
|
||||
result=$(md5sum $input)
|
||||
tmp=$(md5sum $input)
|
||||
|
||||
while [[ "$result" == "$tmp" ]]
|
||||
do
|
||||
echo "equal"
|
||||
tmp=$(md5sum $input)
|
||||
done
|
||||
|
||||
echo "not equal"
|
||||
echo "result = $result"
|
||||
echo "tmp = $tmp"
|
||||
```
|
||||
|
||||
Launch the nested destination VM (inside the guest OS of the VM 2):
|
||||
```bash
|
||||
vm-2:~$ sudo ./cloud-hypervisor --api-socket=/tmp/api2
|
||||
vm-2:~$ # setup the guest network with the following commands if needed
|
||||
vm-2:~$ sudo ip addr add 192.168.102.2/24 dev ens4
|
||||
vm-2:~$ sudo ip link set up dev ens4
|
||||
vm-2:~$ sudo ip r add default via 192.168.102.1
|
||||
vm-2:~$ ping 192.168.101.2 # This should succeed
|
||||
```
|
||||
> Note: If the above ping failed, please check the iptables rule on the
|
||||
> host machine, e.g. whether the policy for the `FORWARD` chain is set
|
||||
> to `DROP` (which is the default setting configured by Docker).
|
||||
|
||||
Get ready for receiving migration for the nested destination VM (inside
|
||||
the guest OS of the VM 2):
|
||||
```bash
|
||||
vm-2:~$ sudo ./ch-remote --api-socket=/tmp/api2 receive-migration unix:/tmp/sock2
|
||||
vm-2:~$ sudo socat TCP-LISTEN:6000,reuseaddr UNIX-CLIENT:/tmp/sock2
|
||||
```
|
||||
|
||||
Start to send migration for the nested source VM (inside the guest OS of
|
||||
the VM 1):
|
||||
```bash
|
||||
vm-1:~$ sudo socat UNIX-LISTEN:/tmp/sock1,reuseaddr TCP:192.168.102.2:6000
|
||||
vm-1:~$ sudo ./ch-remote --api-socket=/tmp/api1 send-migration unix:/tmp/sock1
|
||||
```
|
||||
|
||||
When the above commands completed, the source VM should be successfully
|
||||
migrated to the destination VM without interrupting our testing guest
|
||||
workload. Now the destination VM is running the testing guest workload
|
||||
while the source VM is paused.
|
||||
32
docs/macvtap-bridge.md
Normal file
32
docs/macvtap-bridge.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Using MACVTAP to Bridge onto Host Network
|
||||
|
||||
Cloud Hypervisor supports using a MACVTAP device which is derived from a MACVLAN. Full details of configuring MACVLAN or MACVTAP is out of scope of this document. However the example below indicates how to bridge the guest directly onto the network the host is on. Due to the lack of hairpin mode it not usually possible to reach the guest directly from the host.
|
||||
|
||||
```bash
|
||||
# The MAC address must be attached to the macvtap and be used inside the guest
|
||||
mac="c2:67:4f:53:29:cb"
|
||||
# Host network adapter to bridge the guest onto
|
||||
host_net="eno1"
|
||||
|
||||
# Create the macvtap0 as a new virtual MAC associated with the host network
|
||||
sudo ip link add link "$host_net" name macvtap0 type macvtap
|
||||
sudo ip link set macvtap0 address "$mac" up
|
||||
sudo ip link show macvtap0
|
||||
|
||||
# A new character device is created for this interface
|
||||
tapindex=$(< /sys/class/net/macvtap0/ifindex)
|
||||
tapdevice="/dev/tap$tapindex"
|
||||
|
||||
# Ensure that we can access this device
|
||||
sudo chown "$UID.$UID" "$tapdevice"
|
||||
|
||||
# Use --net fd=3 to point to fd 3 which the shell has opened to point to the /dev/tapN device
|
||||
target/debug/cloud-hypervisor \
|
||||
--kernel ~/src/linux/vmlinux \
|
||||
--disk path=~/workloads/focal.raw \
|
||||
--cpus boot=1 --memory size=512M \
|
||||
--cmdline "root=/dev/vda1 console=hvc0" \
|
||||
--net fd=3,mac=$mac 3<>$"$tapdevice"
|
||||
```
|
||||
|
||||
As the guest is now connected to the same L2 network as the host you can obtain an IP address based on your host network (potentially including via DHCP)
|
||||
@@ -15,6 +15,7 @@ struct MemoryConfig {
|
||||
mergeable: bool,
|
||||
shared: bool,
|
||||
hugepages: bool,
|
||||
hugepage_size: Option<u64>,
|
||||
hotplug_method: HotplugMethod,
|
||||
hotplug_size: Option<u64>,
|
||||
hotplugged_size: Option<u64>,
|
||||
@@ -76,23 +77,24 @@ _Example_
|
||||
--memory size=1G,shared=on
|
||||
```
|
||||
|
||||
### `hugepages`
|
||||
### `hugepages` and `hugepage_size`
|
||||
|
||||
Specifies if the memory must be `mmap(2)` with `MAP_HUGETLB` and `MAP_HUGE_2MB`
|
||||
flags. This performs a memory mapping relying on 2MiB pages instead of the
|
||||
default 4kiB pages.
|
||||
Specifies if the memory must be created and `mmap(2)` with `MAP_HUGETLB` and size
|
||||
flags. This performs a memory mapping relying on the specified huge page size. If no huge page size is supplied the system's default huge page size is used.
|
||||
|
||||
By using hugepages, one can improve the overall performance of the VM, assuming
|
||||
the guest will allocate hugepages as well. Another interesting use case is VFIO
|
||||
as it speeds up the VM's boot time since the amount of IOMMU mappings are
|
||||
reduced.
|
||||
|
||||
The user is responsible for ensuring there are sufficient huge pages of the specified size for the VMM to use. Failure to do so may result in strange VMM behaviour.
|
||||
|
||||
By default this option is turned off.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=1G,hugepages=on
|
||||
--memory size=1G,hugepages=on,hugepage_size=2M
|
||||
```
|
||||
|
||||
### `hotplug_method`
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
cloud-hypervisor can emulate one or more virtual network interfaces, represented at the hypervisor host by [tap devices](https://www.kernel.org/doc/Documentation/networking/tuntap.txt). This guide briefly describes, in a manual and distribution neutral way, how to setup and use networking with cloud-hypervisor.
|
||||
|
||||
## Multiple queue support for net devices ##
|
||||
## Multiple queue support for net devices
|
||||
|
||||
While multiple vcpus defined for guest, to gain the benefit of vcpu scalable to improve performance, it suggests to define multiple queue pairs for net devices, one Tx/Rx queue pair per one vcpu, that means the number of queue pairs at least is equal to the vcpu count. In that case, after virtnet driver set cpu affinity for virtqueues in guest kernel, vcpus could handle interrupt from different virtqueue pairs in parallel.
|
||||
|
||||
@@ -23,7 +23,7 @@ Use one `--net` command-line argument from cloud-hypervisor to specify the emula
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus 4 \
|
||||
--cpus boot=4 \
|
||||
--memory "size=512M" \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--kernel my-vmlinux.bin \
|
||||
@@ -34,14 +34,14 @@ Use one `--net` command-line argument from cloud-hypervisor to specify the emula
|
||||
|
||||
The `--net` argument takes 1 or more space-separated strings of key value pairs containing the following 4 keys or fields:
|
||||
|
||||
| Name | Purpose | Optional |
|
||||
| -----------|----------------------------| ----------|
|
||||
| tap | tap device name | Yes |
|
||||
| mac | vNIC mac address | Yes |
|
||||
| ip | tap IP IP address | yes |
|
||||
| mask | tap IP netmask | Yes |
|
||||
| num_queues | the number of queues | yes |
|
||||
| queue_size | the size of each queue | Yes |
|
||||
| Name | Purpose | Optional |
|
||||
| ---------- | ---------------------- | -------- |
|
||||
| tap | tap device name | Yes |
|
||||
| mac | vNIC mac address | Yes |
|
||||
| ip | tap IP IP address | yes |
|
||||
| mask | tap IP netmask | Yes |
|
||||
| num_queues | the number of queues | yes |
|
||||
| queue_size | the size of each queue | Yes |
|
||||
|
||||
num_queues is the total number of tx and rx queues, the default value is 2, and it could be increased by multiples of 2. Additionally, num_queues is suggested to be as 2 times of vcpu count. The default value for queue_size is 256.
|
||||
|
||||
@@ -104,6 +104,7 @@ bridge name bridge id STP enabled interfaces
|
||||
ich-dpl 8000.067afc1b9a67 no ich1
|
||||
ich-int 8000.725412ffce6f no ich0
|
||||
```
|
||||
|
||||
This completes the layer 2 wiring: The cloud-hypervisor is now connected to the hypervisor host via the 2 linux bridges.
|
||||
|
||||
## IP (Layer 3) provisioning
|
||||
@@ -116,6 +117,7 @@ On the hypervisor host add the network gateway IP address of each network to the
|
||||
root@host:~# ip addr add 192.168.4.1/24 dev ich-int
|
||||
root@host:~# ip addr add 10.0.1.1/24 dev ich-dpl
|
||||
```
|
||||
|
||||
The routing table of the hypervisor host should now also have corresponding routing entries:
|
||||
|
||||
```bash
|
||||
@@ -127,9 +129,10 @@ Destination Gateway Genmask Flags Metric Ref Use Iface
|
||||
192.168.4.0 0.0.0.0 255.255.255.0 U 0 0 0 ich-int
|
||||
192.168.178.0 0.0.0.0 255.255.255.0 U 600 0 0 wlan1
|
||||
```
|
||||
|
||||
### Virtual Machine
|
||||
|
||||
Within the virtual machine set the vNIC's to up state and provision the corresponding IP addresses on the 2 vNIC's. The steps outlined below use the ip command as an example. Alternative distribution specific procedures can also apply.
|
||||
Within the virtual machine set the vNIC's to up state and provision the corresponding IP addresses on the 2 vNIC's. The steps outlined below use the ip command as an example. Alternative distribution specific procedures can also apply.
|
||||
|
||||
```bash
|
||||
root@guest:~# ip link set up enp0s2
|
||||
@@ -165,7 +168,7 @@ root@192.168.4.2's password:
|
||||
Linux cloud-hypervisor 5.2.0 #2 SMP Thu Jul 11 08:08:16 CEST 2019 x86_64
|
||||
|
||||
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
|
||||
permitted by applicable law.
|
||||
permitted by applicable law.
|
||||
|
||||
|
||||
Last login: Fri Jul 12 13:27:56 2019 from 192.168.4.1
|
||||
@@ -189,7 +192,7 @@ nameserver 192.168.178.1
|
||||
make sure that the default gateway of the hypervisor host (in this example host 192.168.178.1 which is an adsl router) has an entry in the routing table for the 192.168.4.0/24 network otherwise IP connectivity will not work.
|
||||
|
||||
```bash
|
||||
root@guest:~# nslookup ftp.nl.debian.org
|
||||
root@guest:~# nslookup ftp.nl.debian.org
|
||||
Server: 192.168.178.1
|
||||
Address: 192.168.178.1#53
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Profiling
|
||||
|
||||
`perf` can be used to profile the `cloud-hypervisor` binary but it is necessary to make some modifications to the the build in order to produce a binary that gives useful results.
|
||||
`perf` can be used to profile the `cloud-hypervisor` binary but it is necessary to make some modifications to the build in order to produce a binary that gives useful results.
|
||||
|
||||
## Building a suitable binary
|
||||
|
||||
|
||||
68
docs/seccomp.md
Normal file
68
docs/seccomp.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Seccomp filtering
|
||||
|
||||
As a means to harden Cloud Hypervisor's security, the project leverages seccomp
|
||||
filtering.
|
||||
|
||||
## What is seccomp filtering
|
||||
|
||||
A seccomp filter is a way for a process to tell the kernel which system calls
|
||||
are authorized.
|
||||
In case this process calls into a prohibited system call, the kernel will kill
|
||||
the process right away.
|
||||
|
||||
## How does it apply to Cloud Hypervisor
|
||||
|
||||
Cloud Hypervisor is a multi threaded application. It spawns dedicated threads
|
||||
for virtual CPUs, virtio devices and HTTP server, along with the main thread
|
||||
representing the VMM.
|
||||
|
||||
Each of these threads has a limited scope of what it is expected to perform,
|
||||
which is why different filters are applied to each of them.
|
||||
|
||||
By default, Cloud Hypervisor enables seccomp filtering as the project believes
|
||||
that security should not be an option.
|
||||
|
||||
For development and debugging purposes, one might want to disable this feature
|
||||
or log the faulty system call.
|
||||
|
||||
### Disabling seccomp filters
|
||||
|
||||
Append `--seccomp false` to Cloud Hypervisor's command line to prevent seccomp
|
||||
filtering from being applied.
|
||||
|
||||
### Logging prohibited system calls
|
||||
|
||||
In the context of debug, one alternative to disabling seccomp filtering is to
|
||||
log faulty system calls that would have caused the application to be killed by
|
||||
the kernel.
|
||||
|
||||
Append `--seccomp log` to Cloud Hypervisor's command line to enable faulty
|
||||
system calls to be logged.
|
||||
|
||||
The kernel running on the host machine must have the `audit` parameter enabled.
|
||||
If this is not the case, update kernel boot options by appending `audit=1`.
|
||||
|
||||
Unauthorized system calls will be logged to the journal similarly to the
|
||||
following example
|
||||
|
||||
```
|
||||
type=SECCOMP msg=audit(1423263412.694:7878): auid=1000 uid=1000 gid=1000 ses=3 subj=unconfined_u:unconfined_r:cloud_hypervisor:s0-s0:c0.c1023 pid=1193 comm="cloud-hypervisor" exe="/usr/bin/cloud-hypervisor" sig=0 arch=c000003e syscall=47 compat=0 ip=0x7f4f63982604 code=0x50000
|
||||
```
|
||||
|
||||
Provided `ausyscall` has been installed on the host, the system call can be
|
||||
identified with
|
||||
|
||||
```
|
||||
$ ausyscall 47
|
||||
recvmsg
|
||||
```
|
||||
|
||||
### Further debug with `strace`
|
||||
|
||||
One more way of debugging seccomp related issues is to use the `strace` tool as
|
||||
it will log every system call issued by the process. It is important to use
|
||||
`-f` option in order to trace each and every thread belonging to the process.
|
||||
|
||||
```
|
||||
strace -f ./cloud-hypervisor ...
|
||||
```
|
||||
@@ -20,7 +20,7 @@ First thing, we must run a Cloud-Hypervisor VM:
|
||||
--api-socket /tmp/cloud-hypervisor.sock \
|
||||
--cpus boot=4 \
|
||||
--memory size=4G \
|
||||
--kernel bzImage \
|
||||
--kernel vmlinux \
|
||||
--cmdline "root=/dev/vda1 console=hvc0 rw" \
|
||||
--disk path=focal-server-cloudimg-amd64.raw
|
||||
```
|
||||
|
||||
@@ -11,7 +11,7 @@ by the VFIO kernel drivers. However, by default, a host device will be bound to
|
||||
its native driver, which is not the VFIO one.
|
||||
|
||||
As a consequence, a device must be unbound from its native driver before passing
|
||||
it to `cloud-hypervisor` for assigning it to a guess.
|
||||
it to `cloud-hypervisor` for assigning it to a guest.
|
||||
|
||||
### Example
|
||||
|
||||
@@ -54,6 +54,7 @@ $ lspci -n -s 01:00.0
|
||||
01:00.0 ff00: 10ec:525a (rev 01)
|
||||
|
||||
$ echo 10ec 525a > /sys/bus/pci/drivers/vfio-pci/new_id
|
||||
$ echo 0000:01:00.0 > /sys/bus/pci/drivers/vfio-pci/bind
|
||||
```
|
||||
|
||||
Now the device is managed by the VFIO framework.
|
||||
@@ -78,5 +79,3 @@ takes the device's sysfs path as an argument. In our example it is
|
||||
The guest kernel will then detect the card reader on its PCI bus and provided
|
||||
that support for this device is enabled, it will probe and enable it for the
|
||||
guest to use.
|
||||
|
||||
|
||||
|
||||
@@ -35,38 +35,52 @@ Here are some good references for detailing them.
|
||||
|
||||
## Test environment
|
||||
|
||||
The below test environment is based on ubuntu release(16.04.1 LTS), as for other system, please check related document.
|
||||
The test runs with multiple queue (MQ) support enabled, using 4 queues defined for both SPDK and the virtual machine.
|
||||
Here are the details on how the test can be run.
|
||||
The below test environment is based on Ubuntu release (20.04.1 LTS), as for other system, please check related documents.
|
||||
This test runs with multiple queue (MQ) support enabled, using 4 queues defined for both SPDK and the virtual machine.
|
||||
Here are the detailed instructions.
|
||||
|
||||
### The hugepages settings in host linux
|
||||
Add "default_hugepagesz=1G hugepagesz=1G hugepages=2" into host linux cmdline.
|
||||
As for how to change Ubuntu linux cmdline in grub file, please ref below link:
|
||||
https://www.ostechnix.com/configure-grub-2-boot-loader-settings-ubuntu-16-04/
|
||||
reboot Ubuntu
|
||||
sudo mount -t hugetlbfs -o pagesize=1G none /dev/hugepages
|
||||
### The hugepages settings on the host
|
||||
Allocate enough persistent huge pages (e.g. 2GiB) on the host:
|
||||
|
||||
```bash
|
||||
# Assume the default hugepage size is 2MiB
|
||||
echo 1024 | sudo tee /proc/sys/vm/nr_hugepages
|
||||
# Confirm the value of "Hugepagesize" and "HugePages_Total"
|
||||
cat /proc/meminfo | grep -i huge
|
||||
```
|
||||
|
||||
### Download the SPDK code
|
||||
```bash
|
||||
git clone https://github.com/spdk/spdk
|
||||
cd spdk
|
||||
git submodule update --init
|
||||
```
|
||||
|
||||
### Create the build dep
|
||||
./scripts/pkgdep.sh
|
||||
|
||||
### Build spdk
|
||||
```bash
|
||||
./scripts/pkgdep.sh
|
||||
```
|
||||
|
||||
### Build SPDK
|
||||
```bash
|
||||
./configure
|
||||
make
|
||||
```
|
||||
|
||||
### Set the SPDk environment
|
||||
### Set the SPDK environment
|
||||
```bash
|
||||
sudo HUGEMEM=2048 scripts/setup.sh
|
||||
sudo ./app/vhost/vhost -S /var/tmp -s 1024 -m 0x3 &
|
||||
sudo ./build/bin/vhost -S /var/tmp -s 1024 -m 0x3 &
|
||||
```
|
||||
|
||||
### Create 512M block device
|
||||
```bash
|
||||
sudo scripts/rpc.py bdev_malloc_create 512 512 -b Malloc0
|
||||
sudo scripts/rpc.py vhost_create_blk_controller --cpumask 0x1 vhost.1 Malloc0
|
||||
```
|
||||
|
||||
_Launch the VM_
|
||||
### Launch the VM
|
||||
|
||||
VMs run in client mode. They connect to the socket created by the `dpdkvhostuser` in the SPDK backend.
|
||||
```bash
|
||||
@@ -89,9 +103,9 @@ login in guest
|
||||
# Use lsblk command to find out vhost-user-blk device
|
||||
lsblk
|
||||
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
|
||||
vda 252:0 0 2.2G 0 disk
|
||||
vda 252:0 0 2.2G 0 disk
|
||||
├─vda1 252:1 0 2.1G 0 part /
|
||||
├─vda14 252:14 0 4M 0 part
|
||||
├─vda14 252:14 0 4M 0 part
|
||||
└─vda15 252:15 0 106M 0 part /boot/efi
|
||||
vdb 253:16 0 512M 0 disk
|
||||
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
# How to test Vhost-user net with OpenVSwitch/DPDK
|
||||
|
||||
The purpose of this document is to illustrate how to test vhost-user-net in cloud-hypervisor with OVS/DPDK as the backend.
|
||||
The purpose of this document is to illustrate how to test vhost-user-net
|
||||
in cloud-hypervisor with OVS/DPDK as the backend. This document was
|
||||
tested with Open vSwitch v2.13.1, DPDK v19.11.3, and Cloud Hypervisor
|
||||
v15.0 on Ubuntu 20.04.1 (host kernel v5.4.0).
|
||||
|
||||
## Framework
|
||||
|
||||
It's a simple test to validate the communication between two virtual machine, connecting them to vhost-user ports respectively provided by `OVS/DPDK`.
|
||||
```
|
||||
+----+----------+ +-------------+-----------+-------------+ +----------+----+
|
||||
| | | | | | | | | |
|
||||
| |vhost-user|----------| vhost-user | ovs | vhost-user |----------|vhost-user| |
|
||||
| |net device| | port 1 | | port 2 | |net device| |
|
||||
| | | | | | | | | |
|
||||
| +----------+ +-------------+-----------+-------------+ +----------+ |
|
||||
| | | | | |
|
||||
|vm1 | | dpdk | | vm2 |
|
||||
| | | | | |
|
||||
+--+---------------------------------------------------------------------------------------------+--+
|
||||
| | hugepages | |
|
||||
| +---------------------------------------------------------------------------------------------+ |
|
||||
| |
|
||||
| host |
|
||||
| |
|
||||
+---------------------------------------------------------------------------------------------------+
|
||||
+----+----------+ +-------------+-----------+-------------+ +----------+----+
|
||||
| | | | | | | | | |
|
||||
| |vhost-user|----------| vhost-user | ovs | vhost-user |----------|vhost-user| |
|
||||
| |net device| | port 1 | | port 2 | |net device| |
|
||||
| | | | | | | | | |
|
||||
| +----------+ +-------------+-----------+-------------+ +----------+ |
|
||||
| | | | | |
|
||||
|vm1 | | dpdk | | vm2 |
|
||||
| | | | | |
|
||||
+--+---------------------------------------------------------------------------------------------+--+
|
||||
| | hugepages | |
|
||||
| +---------------------------------------------------------------------------------------------+ |
|
||||
| |
|
||||
| host |
|
||||
| |
|
||||
+---------------------------------------------------------------------------------------------------+
|
||||
```
|
||||
## Prerequisites
|
||||
|
||||
@@ -30,44 +33,54 @@ Prior to running the test, the following steps need to be performed.
|
||||
- Install DPDK
|
||||
- Install OVS
|
||||
|
||||
Here are some good references for detailing them.
|
||||
- Red Hat
|
||||
* https://wiki.qemu.org/Documentation/vhost-user-ovs-dpdk
|
||||
- Ubuntu server
|
||||
* https://help.ubuntu.com/lts/serverguide/DPDK.html
|
||||
* https://software.intel.com/en-us/articles/set-up-open-vswitch-with-dpdk-on-ubuntu-server
|
||||
Here is a good reference for setting up OVS with DPDK from scratch:
|
||||
https://docs.openvswitch.org/en/latest/intro/install/dpdk/.
|
||||
|
||||
On Ubuntu systems (18.04 or newer), the OpenVswitch-DPDK package can be
|
||||
easily installed with:
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install openvswitch-switch-dpdk
|
||||
sudo update-alternatives --set ovs-vswitchd /usr/lib/openvswitch-switch-dpdk/ovs-vswitchd-dpdk
|
||||
```
|
||||
## Test
|
||||
The test runs with multiple queue (MQ) support enabled, using 2 pairs of TX/RX queues defined for both OVS and the virtual machine. Here are the details on how the test can be run.
|
||||
The test runs with multiple queue (MQ) support enabled, using 2 pairs of
|
||||
TX/RX queues defined for both OVS and the virtual machine. Here are the
|
||||
detailed instructions.
|
||||
|
||||
_Setup OVS_
|
||||
|
||||
`ovs_test.sh` is created to setup and start OVS. OVS will provide the `dpdkvhostuser` backend running in server mode.
|
||||
Here is an example how to configure a basic OpenVswitch using DPDK:
|
||||
```bash
|
||||
mkdir -p /var/run/openvswitch
|
||||
# load the ovs kernel module
|
||||
modprobe openvswitch
|
||||
killall ovsdb-server ovs-vswitchd
|
||||
rm -f /var/run/openvswitch/vhost-user*
|
||||
rm -f /etc/openvswitch/conf.db
|
||||
export DB_SOCK=/var/run/openvswitch/db.sock
|
||||
ovsdb-tool create /etc/openvswitch/conf.db /usr/share/openvswitch/vswitch.ovsschema
|
||||
ovsdb-server --remote=punix:$DB_SOCK --remote=db:Open_vSwitch,Open_vSwitch,manager_options --pidfile --detach
|
||||
ovs-vsctl --no-wait init
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-lcore-mask=0xf
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-socket-mem=1024
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-init=true
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:pmd-cpu-mask=0xf
|
||||
ovs-vswitchd unix:$DB_SOCK --pidfile --detach --log-file=/var/log/openvswitch/ovs-vswitchd.log
|
||||
sudo service openvswitch-switch start
|
||||
ovs-vsctl init
|
||||
ovs-vsctl set Open_vSwitch . other_config:dpdk-init=true
|
||||
# run on core 0-3 only
|
||||
ovs-vsctl set Open_vSwitch . other_config:dpdk-lcore-mask=0xf
|
||||
# allocate 2G huge pages (to NUMA 0 only)
|
||||
ovs-vsctl set Open_vSwitch . other_config:dpdk-socket-mem=1024
|
||||
# run PMD (Pull Mode Driver) threads on core 0-3 only
|
||||
ovs-vsctl set Open_vSwitch . other_config:pmd-cpu-mask=0xf
|
||||
sudo service openvswitch-switch restart
|
||||
# double check the configurations
|
||||
ovs-vsctl list Open_vSwitch
|
||||
```
|
||||
|
||||
Here is an example how to create a bridge and add two DPDK ports to it
|
||||
(for later use via Cloud Hypervisor):
|
||||
```bash
|
||||
# create a bridge
|
||||
ovs-vsctl add-br ovsbr0 -- set bridge ovsbr0 datapath_type=netdev
|
||||
# create two DPDK ports and add them to the bridge
|
||||
ovs-vsctl add-port ovsbr0 vhost-user1 -- set Interface vhost-user1 type=dpdkvhostuser
|
||||
ovs-vsctl add-port ovsbr0 vhost-user2 -- set Interface vhost-user2 type=dpdkvhostuser
|
||||
ovs-vsctl add-port ovsbr0 vhost-user2 -- set Interface vhost-user2
|
||||
type=dpdkvhostuser
|
||||
# set the number of rx queues
|
||||
ovs-vsctl set Interface vhost-user1 options:n_rxq=2
|
||||
ovs-vsctl set Interface vhost-user2 options:n_rxq=2
|
||||
```
|
||||
_Run ovs_test.sh_
|
||||
```bash
|
||||
./ovs_test.sh
|
||||
```
|
||||
|
||||
_Launch the VMs_
|
||||
|
||||
@@ -89,19 +102,21 @@ VMs run in client mode. They connect to the socket created by the `dpdkvhostuser
|
||||
--kernel vmlinux \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--net "mac=52:54:20:11:C5:02,vhost_user=true,socket=/var/run/openvswitch/vhost-user2,num_queues=4"
|
||||
--net mac=52:54:20:11:C5:02,vhost_user=true,socket=/var/run/openvswitch/vhost-user2,num_queues=4
|
||||
```
|
||||
|
||||
_Setup VM1_
|
||||
```bash
|
||||
# From inside the guest
|
||||
sudo ip addr add 172.100.0.1/24 dev enp0s3
|
||||
sudo ip addr add 172.100.0.1/24 dev ens2
|
||||
sudo ip link set up dev ens2
|
||||
```
|
||||
|
||||
_Setup VM2_
|
||||
```bash
|
||||
# From inside the guest
|
||||
sudo ip addr add 172.100.0.2/24 dev enp0s3
|
||||
sudo ip addr add 172.100.0.2/24 dev ens2
|
||||
sudo ip link set up dev ens2
|
||||
```
|
||||
|
||||
_Ping VM1 from VM2_
|
||||
@@ -129,4 +144,3 @@ _Run VM2 as client_
|
||||
# From inside the guest
|
||||
iperf3 -c 172.100.0.1 -t 30 -p 4444 &
|
||||
```
|
||||
|
||||
|
||||
92
docs/virtiofs-root.md
Normal file
92
docs/virtiofs-root.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# HOWTO VirtioFS rootfs
|
||||
|
||||
A quick guide for using virtiofs as a cloud-hypervisor guest's rootfs (i.e.
|
||||
with no root block device). This document is a quick getting started guide.
|
||||
There are many more steps to take to make this a production ready, secure
|
||||
setup.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. virtiofsd from the qemu project
|
||||
* We are using the Qemu version for now
|
||||
* There is a Rust version being worked on that may be a better option in the future
|
||||
* Part of the qemu-system-common package on Ubuntu
|
||||
* Part of the qemu-common package on Fedora
|
||||
2. cloud-hypervisor - the newer the better, but I tested with 0.12
|
||||
3. a rootfs - This howto uses an alpine rootfs available here:
|
||||
* https://dl-cdn.alpinelinux.org/alpine/v3.13/releases/x86_64/alpine-minirootfs-3.13.2-x86_64.tar.gz
|
||||
* Others should work
|
||||
|
||||
## To create the VM rootfs
|
||||
|
||||
```bash
|
||||
mkdir rootfs/
|
||||
cd rootfs
|
||||
# this needs sudo to be able to set root permissions on fs components
|
||||
sudo tar -xf /path/to/alpine-minirootfs-3.13.1-x86_64.tar.gz
|
||||
# this will get created when the VM actually boots by the dhcp client
|
||||
# but we need it in the chroot to download packages
|
||||
sudo cp /etc/resolv.conf etc/
|
||||
# the alpine mini rootfs is meant for docker containers, we need a few extra
|
||||
# things for a working rootfs
|
||||
sudo chroot $PWD apk add openrc busybox-initscripts
|
||||
# we are using the paravirt console in cloud-hypervisor, so enable it in init
|
||||
# append it after the other console since it doesn't work just appending it
|
||||
sudo sed -i '/vt100/a \n# paravirt console\nhvc0::respawn:/sbin/getty -L hvc0 115200 vt100' etc/inittab
|
||||
# set no password for root user... you obviously don't want to do this for
|
||||
# any sort of production setup
|
||||
sudo sed -i 's/root:!::0:::::/root:::0:::::/' etc/shadow
|
||||
# set up init scripts
|
||||
for i in acpid crond
|
||||
sudo ln -sf /etc/init.d/$i etc/runlevels/default/$i
|
||||
end
|
||||
for i in bootmisc hostname hwclock loadkmap modules networking swap sysctl syslog urandom
|
||||
sudo ln -sf /etc/init.d/$i etc/runlevels/boot/$i
|
||||
end
|
||||
|
||||
for i in killprocs mount-ro savecache
|
||||
sudo ln -sf /etc/init.d/$i etc/runlevels/shutdown/$i
|
||||
end
|
||||
|
||||
for i in devfs dmesg hwdrivers mdev
|
||||
sudo ln -sf /etc/init.d/$i etc/runlevels/sysinit/$i
|
||||
end
|
||||
# setup network config
|
||||
echo 'auto lo
|
||||
iface lo inet loopback
|
||||
|
||||
auto eth0
|
||||
iface eth0 inet dhcp
|
||||
' | sudo tee etc/network/interfaces
|
||||
|
||||
```
|
||||
|
||||
## To run the VM
|
||||
|
||||
```bash
|
||||
# starting in the directory above rootfs
|
||||
sudo virtiofsd --socket-path=$PWD/virtiofs-rootfs.sock -o source=$PWD/rootfs -o cache=none &
|
||||
sudo cloud-hypervisor \
|
||||
--cpus boot=1,max=1 \
|
||||
--kernel vmlinux \
|
||||
--fs tag=/dev/root,socket=$PWD/virtiofs-rootfs.sock \
|
||||
--memory size=2G,shared=on \
|
||||
--cmdline "console=hvc0 rootfstype=virtiofs root=/dev/root ro debug" \
|
||||
--api-socket $PWD/ch.sock \
|
||||
--rng \
|
||||
--net ...
|
||||
```
|
||||
|
||||
Note: an important part of the above is the `tag=/dev/root` and
|
||||
`root=/dev/root` parts. For whatever reason, it would only work with that as
|
||||
the tag.
|
||||
|
||||
Note: another important bit is that the memory is shared. This is required for
|
||||
virtiofs
|
||||
|
||||
## Message from the author
|
||||
|
||||
If you find any issues or have suggestions, feel free to reach out to @iggy on
|
||||
the cloud-hypervisor slack. Also if this works for you, I'd like to know as
|
||||
well. It would also be nice to get steps for preparing other distribution root
|
||||
filesystems.
|
||||
@@ -184,6 +184,16 @@ Set-Service -Name sshd -StartupType ‘Automatic’
|
||||
|
||||
This allows for SSH login from a remote machine, for example through the `administrator` user: `ssh administrator@192.168.249.2`. For a more detailed OpenSSH guide, please follow the MSDN article from the [links](#links) section.
|
||||
|
||||
## Hotplug capability
|
||||
|
||||
CPU hotplug is supported. The VM operating system needs to support hotplug and be appropriately licensed. SKU limitations like constraints on the number of cores are to be taken into consideration. Note, that Windows doesn't support CPU hot-remove. When `ch-remote` is invoked to reduce the number of CPUs, the result will be visible after the OS reboot within the same hypervisor instance.
|
||||
|
||||
RAM hotplug is supported. Note, that while the `pnpmem.sys` driver in use supports RAM hot-remove, the RAM being unplugged has to be not in use and have no reserved pages. In most cases it means, hot-remove won't work. Same as with the CPU hot-remove, when `ch-remote` is invoked to reduce the RAM size, the result will be visible after the OS reboot.
|
||||
|
||||
Network device hotplug and hot-remove are supported.
|
||||
|
||||
Disk hotplug and hot-remove are supported. After the device has been hotplugged, it will need to be onlined from within the guest. Among other tools, powershell applets `Get-Disk` and `Set-Disk` can be used for the disk configuration and activation.
|
||||
|
||||
## Debugging
|
||||
|
||||
The Windows guest debugging process relies heavily on QEMU and [socat](http://www.dest-unreach.org/socat/). The procedure requires two Windows VMs:
|
||||
|
||||
11
event_monitor/Cargo.toml
Normal file
11
event_monitor/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "event_monitor"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.96"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
77
event_monitor/src/lib.rs
Normal file
77
event_monitor/src/lib.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
static mut MONITOR: Option<(File, Instant)> = None;
|
||||
|
||||
/// This function must only be called once from the main process before any threads
|
||||
/// are created to avoid race conditions
|
||||
pub fn set_monitor(file: File) -> Result<(), std::io::Error> {
|
||||
assert!(unsafe { MONITOR.is_none() });
|
||||
let fd = file.as_raw_fd();
|
||||
let ret = unsafe {
|
||||
let mut flags = libc::fcntl(fd, libc::F_GETFL);
|
||||
flags |= libc::O_NONBLOCK;
|
||||
libc::fcntl(fd, libc::F_SETFL, flags)
|
||||
};
|
||||
if ret < 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
unsafe {
|
||||
MONITOR = Some((file, Instant::now()));
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Event<'a> {
|
||||
timestamp: Duration,
|
||||
source: &'a str,
|
||||
event: &'a str,
|
||||
properties: Option<&'a HashMap<Cow<'a, str>, Cow<'a, str>>>,
|
||||
}
|
||||
|
||||
pub fn event_log(source: &str, event: &str, properties: Option<&HashMap<Cow<str>, Cow<str>>>) {
|
||||
if let Some((file, start)) = unsafe { MONITOR.as_ref() } {
|
||||
let e = Event {
|
||||
timestamp: start.elapsed(),
|
||||
source,
|
||||
event,
|
||||
properties,
|
||||
};
|
||||
serde_json::to_writer_pretty(file, &e).ok();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Through the use of Cow<'a, str> it is possible to use String as well as
|
||||
&str as the parameters:
|
||||
e.g.
|
||||
event!("cpu_manager", "create_vcpu", "id", cpu_id.to_string());
|
||||
*/
|
||||
#[macro_export]
|
||||
macro_rules! event {
|
||||
($source:expr, $event:expr) => {
|
||||
$crate::event_log($source, $event, None)
|
||||
};
|
||||
($source:expr, $event:expr, $($key:expr, $value:expr),*) => {
|
||||
{
|
||||
let mut properties = ::std::collections::HashMap::new();
|
||||
$(
|
||||
properties.insert($key.into(), $value.into());
|
||||
)+
|
||||
$crate::event_log($source, $event, Some(&properties))
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
837
fuzz/Cargo.lock
generated
Normal file
837
fuzz/Cargo.lock
generated
Normal file
@@ -0,0 +1,837 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "acpi_tables"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"vm-memory",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
|
||||
|
||||
[[package]]
|
||||
name = "api_client"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "237430fd6ed3740afe94eefcc278ae21e050285be882804e0d6e8695f0c94691"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e906254e445520903e7fc9da4f709886c84ae4bc4ddaf0e093188d66df4dc820"
|
||||
|
||||
[[package]]
|
||||
name = "arch"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"acpi_tables",
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
"hypervisor",
|
||||
"libc",
|
||||
"linux-loader",
|
||||
"log",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"versionize",
|
||||
"versionize_derive",
|
||||
"vm-fdt",
|
||||
"vm-memory",
|
||||
"vm-migration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "block_util"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"io-uring",
|
||||
"libc",
|
||||
"log",
|
||||
"qcow",
|
||||
"thiserror",
|
||||
"versionize",
|
||||
"versionize_derive",
|
||||
"virtio-bindings",
|
||||
"vm-memory",
|
||||
"vm-virtio",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"term_size",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloud-hypervisor"
|
||||
version = "15.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"api_client",
|
||||
"clap",
|
||||
"epoll",
|
||||
"event_monitor",
|
||||
"hypervisor",
|
||||
"libc",
|
||||
"log",
|
||||
"option_parser",
|
||||
"seccomp",
|
||||
"serde_json",
|
||||
"signal-hook",
|
||||
"thiserror",
|
||||
"vm-memory",
|
||||
"vmm",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloud-hypervisor-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"block_util",
|
||||
"cloud-hypervisor",
|
||||
"libc",
|
||||
"libfuzzer-sys",
|
||||
"qcow",
|
||||
"seccomp",
|
||||
"virtio-devices",
|
||||
"vm-memory",
|
||||
"vm-virtio",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc64"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55626594feae15d266d52440b26ff77de0e22230cf0c113abe619084c1ddc910"
|
||||
|
||||
[[package]]
|
||||
name = "devices"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"acpi_tables",
|
||||
"anyhow",
|
||||
"arch",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"epoll",
|
||||
"libc",
|
||||
"log",
|
||||
"versionize",
|
||||
"versionize_derive",
|
||||
"vm-device",
|
||||
"vm-memory",
|
||||
"vm-migration",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epoll"
|
||||
version = "4.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20df693c700404f7e19d4d6fae6b15215d2913c27955d2b9d6f2c0f537511cd0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event_monitor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hypervisor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"epoll",
|
||||
"iced-x86",
|
||||
"kvm-bindings",
|
||||
"kvm-ioctls",
|
||||
"libc",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced-x86"
|
||||
version = "1.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d907dfc273d9ed0da940e8145ceb77759056a1fb2110b44b83d1b203cddb906"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb82832e05cc4ca298f198a8db108837b4f7b7b1248e3cba8e48f151aece80cf"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
|
||||
[[package]]
|
||||
name = "kvm-bindings"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/cloud-hypervisor/kvm-bindings?branch=ch-v0.4.0#1a68725639283e622f4bb64584885b30bfe8be44"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kvm-ioctls"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2924454e22895c738e43331ae310459c74a11ded9c97dc250129ee10d2f9ca2"
|
||||
dependencies = [
|
||||
"kvm-bindings",
|
||||
"libc",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5600b4e6efc5421841a2138a6b082e07fe12f9aaa12783d50e5d13325b26b4fc"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36a9a84a6e8b55dfefb04235e55edb2b9a2a18488fcae777a6bdaa6f06f1deb3"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-loader"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c819cc8275b0f2c1ed9feec455ca288b45d82932384a6a5f7a86812ee3427459"
|
||||
dependencies = [
|
||||
"vm-memory",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "micro_http"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#9b605a8b61df602cda62076d30d9f70307ea336f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "net_gen"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "net_util"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"epoll",
|
||||
"libc",
|
||||
"log",
|
||||
"net_gen",
|
||||
"rate_limiter",
|
||||
"serde",
|
||||
"versionize",
|
||||
"versionize_derive",
|
||||
"virtio-bindings",
|
||||
"vm-memory",
|
||||
"vm-virtio",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
|
||||
|
||||
[[package]]
|
||||
name = "option_parser"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "pci"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
"hypervisor",
|
||||
"libc",
|
||||
"log",
|
||||
"versionize",
|
||||
"versionize_derive",
|
||||
"vfio-bindings",
|
||||
"vfio-ioctls",
|
||||
"vm-allocator",
|
||||
"vm-device",
|
||||
"vm-memory",
|
||||
"vm-migration",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qcow"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"libc",
|
||||
"log",
|
||||
"remain",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rate_limiter"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remain"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ba1e78fa68412cb93ef642fd4d20b9a941be49ee9333875ebaf13112673ea7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "seccomp"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/firecracker-microvm/firecracker?tag=v0.24.4#8f44986a0e956a77f2b2324c12f73bec16130c82"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term_size"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"term_size",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "versionize"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7429cf68de8f091b667d27323ed323afd39584a56d533995b12ddd748e5e6ca9"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"crc64",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"syn",
|
||||
"versionize_derive",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "versionize_derive"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "140aa9fd298f667ea50fa1cb0d8530076924079285c623b18b8f8a1c28386b4a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vfio-bindings"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a21f546f2bda37f5a8cfb138c87f95b8e34d2d78d6a7a92ba3785f4e08604a7"
|
||||
dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vfio-ioctls"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/vfio-ioctls?branch=master#5cb8550556620546e74b9fe6d1174e0d1fc83888"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"kvm-bindings",
|
||||
"kvm-ioctls",
|
||||
"log",
|
||||
"vfio-bindings",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vhost"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/vhost?branch=master#e5b930b73a47cbdd79858e2e68bcb57123d1c1f3"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtio-bindings"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff512178285488516ed85f15b5d0113a7cdb89e9e8a760b269ae4f02b84bd6b"
|
||||
|
||||
[[package]]
|
||||
name = "virtio-devices"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"block_util",
|
||||
"byteorder",
|
||||
"epoll",
|
||||
"event_monitor",
|
||||
"io-uring",
|
||||
"libc",
|
||||
"log",
|
||||
"net_gen",
|
||||
"net_util",
|
||||
"pci",
|
||||
"rate_limiter",
|
||||
"seccomp",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"versionize",
|
||||
"versionize_derive",
|
||||
"vhost",
|
||||
"virtio-bindings",
|
||||
"vm-allocator",
|
||||
"vm-device",
|
||||
"vm-memory",
|
||||
"vm-migration",
|
||||
"vm-virtio",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vm-allocator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arch",
|
||||
"libc",
|
||||
"vm-memory",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vm-device"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"vfio-ioctls",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vm-fdt"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/vm-fdt?branch=master#13ab882e2f8387753064351c69fbe35c422b448b"
|
||||
|
||||
[[package]]
|
||||
name = "vm-memory"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/rust-vmm/vm-memory?rev=5bd7138758183a73ac0da27ce40c004d95f1a7e9#5bd7138758183a73ac0da27ce40c004d95f1a7e9"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vm-migration"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"versionize",
|
||||
"versionize_derive",
|
||||
"vm-memory",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vm-virtio"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"virtio-bindings",
|
||||
"vm-memory",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vmm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"acpi_tables",
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"arch",
|
||||
"bitflags",
|
||||
"block_util",
|
||||
"clap",
|
||||
"devices",
|
||||
"epoll",
|
||||
"event_monitor",
|
||||
"hypervisor",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"linux-loader",
|
||||
"log",
|
||||
"micro_http",
|
||||
"net_util",
|
||||
"option_parser",
|
||||
"pci",
|
||||
"qcow",
|
||||
"seccomp",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"signal-hook",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
"versionize",
|
||||
"versionize_derive",
|
||||
"vfio-ioctls",
|
||||
"virtio-devices",
|
||||
"vm-allocator",
|
||||
"vm-device",
|
||||
"vm-memory",
|
||||
"vm-migration",
|
||||
"vm-virtio",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vmm-sys-util"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01cf11afbc4ebc0d5c7a7748a77d19e2042677fc15faa2f4ccccb27c18a60605"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
@@ -9,18 +9,23 @@ edition = "2018"
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.72"
|
||||
libfuzzer-sys = "0.3"
|
||||
block_util = { path = "../block_util" }
|
||||
libc = "0.2.96"
|
||||
libfuzzer-sys = "0.4.2"
|
||||
qcow = { path = "../qcow" }
|
||||
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.22.0" }
|
||||
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.24.4" }
|
||||
virtio-devices = { path = "../virtio-devices" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
vmm-sys-util = "0.8.0"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vm-memory = "0.4.0"
|
||||
vm-memory = "0.5.0"
|
||||
|
||||
[dependencies.cloud-hypervisor]
|
||||
path = ".."
|
||||
|
||||
[patch.crates-io]
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.4.0", features = ["with-serde", "fam-wrappers"] }
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory", rev = "5bd7138758183a73ac0da27ce40c004d95f1a7e9"}
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
#![no_main]
|
||||
|
||||
use block_util::{async_io::DiskFile, qcow_sync::QcowDiskSync};
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use seccomp::SeccompAction;
|
||||
use std::ffi;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Cursor, Read, Seek, SeekFrom};
|
||||
@@ -16,7 +18,6 @@ use virtio_devices::{Block, VirtioDevice, VirtioInterrupt, VirtioInterruptType};
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vm_virtio::Queue;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use seccomp::SeccompAction;
|
||||
|
||||
const MEM_SIZE: u64 = 256 * 1024 * 1024;
|
||||
const DESC_SIZE: u64 = 16; // Bytes in one virtio descriptor.
|
||||
@@ -83,17 +84,18 @@ fuzz_target!(|bytes| {
|
||||
|
||||
let shm = memfd_create(&ffi::CString::new("fuzz").unwrap(), 0).unwrap();
|
||||
let disk_file: File = unsafe { File::from_raw_fd(shm) };
|
||||
let raw_img = qcow::RawFile::new(disk_file, false);
|
||||
let qcow_disk = Box::new(QcowDiskSync::new(disk_file, false)) as Box<dyn DiskFile>;
|
||||
|
||||
let mut block = Block::new(
|
||||
"tmp".to_owned(),
|
||||
raw_img,
|
||||
qcow_disk,
|
||||
PathBuf::from(""),
|
||||
false,
|
||||
false,
|
||||
2,
|
||||
256,
|
||||
SeccompAction::Allow,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -6,35 +6,30 @@ edition = "2018"
|
||||
license = "Apache-2.0 OR BSD-3-Clause"
|
||||
|
||||
[features]
|
||||
kvm = []
|
||||
mshv = []
|
||||
kvm = ["kvm-ioctls", "kvm-bindings"]
|
||||
mshv = ["mshv-ioctls", "mshv-bindings"]
|
||||
tdx = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
epoll = ">=4.0.1"
|
||||
thiserror = "1.0"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch", features = ["with-serde", "fam-wrappers"] }
|
||||
|
||||
mshv-bindings = {git = "https://github.com/cloud-hypervisor/mshv", branch = "master", features = ["with-serde", "fam-wrappers"] }
|
||||
mshv-ioctls = { git = "https://github.com/cloud-hypervisor/mshv", branch = "master" }
|
||||
|
||||
libc = "0.2.96"
|
||||
log = "0.4.14"
|
||||
kvm-ioctls = { version = "0.9.0", optional = true }
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.4.0", features = ["with-serde", "fam-wrappers"], optional = true }
|
||||
mshv-bindings = {git = "https://github.com/cloud-hypervisor/mshv", branch = "master", features = ["with-serde", "fam-wrappers"], optional = true }
|
||||
mshv-ioctls = { git = "https://github.com/cloud-hypervisor/mshv", branch = "master", optional = true}
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-memory = { version = "0.4.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-memory = { version = "0.5.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vmm-sys-util = { version = ">=0.5.0", features = ["with-serde"] }
|
||||
|
||||
[dependencies.linux-loader]
|
||||
git = "https://github.com/rust-vmm/linux-loader"
|
||||
features = ["elf", "bzimage"]
|
||||
|
||||
[target.'cfg(target_arch = "x86_64")'.dependencies.iced-x86]
|
||||
version = "1.9.1"
|
||||
version = "1.11"
|
||||
default-features = false
|
||||
features = ["std", "decoder", "op_code_info", "instr_info", "fast_fmt"]
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.8.2"
|
||||
env_logger = "0.8.3"
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
extern crate thiserror;
|
||||
|
||||
use core::fmt::Debug;
|
||||
use std::fmt::{self, Display};
|
||||
use thiserror::Error;
|
||||
@@ -47,8 +45,8 @@ pub enum PlatformError {
|
||||
#[error("Set CPU state failure: {0}")]
|
||||
SetCpuStateFailure(#[source] anyhow::Error),
|
||||
|
||||
#[error("Unmapped virtual address: {0}")]
|
||||
UnmappedGVA(#[source] anyhow::Error),
|
||||
#[error("Translate virtual address: {0}")]
|
||||
TranslateVirtualAddress(#[source] anyhow::Error),
|
||||
|
||||
#[error("Unsupported CPU Mode: {0}")]
|
||||
UnsupportedCpuMode(#[source] anyhow::Error),
|
||||
|
||||
@@ -14,4 +14,5 @@
|
||||
pub mod emulator;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[macro_use]
|
||||
pub mod x86;
|
||||
|
||||
@@ -4,14 +4,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_camel_case_types, clippy::upper_case_acronyms)]
|
||||
|
||||
//
|
||||
// CMP-Compare Two Operands
|
||||
//
|
||||
|
||||
extern crate iced_x86;
|
||||
|
||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
||||
use crate::arch::x86::emulator::instructions::*;
|
||||
use crate::arch::x86::regs::*;
|
||||
@@ -71,8 +69,6 @@ macro_rules! cmp_rm_r {
|
||||
|
||||
state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
@@ -95,8 +91,6 @@ macro_rules! cmp_r_rm {
|
||||
|
||||
state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
@@ -119,8 +113,6 @@ macro_rules! cmp_rm_imm {
|
||||
|
||||
state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
@@ -230,71 +222,63 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
// cmp ah,al
|
||||
fn test_cmp_rm8_r8_1() -> MockResult {
|
||||
fn test_cmp_rm8_r8_1() {
|
||||
let rax: u64 = 0x0;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x38, 0xc4]; // cmp ah,al
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], None);
|
||||
let mut vmm = MockVmm::new(ip, vec![(Register::RAX, rax)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b1000100, rflags);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// cmp eax,100
|
||||
fn test_cmp_rm32_imm8_1() -> MockResult {
|
||||
fn test_cmp_rm32_imm8_1() {
|
||||
let rax: u64 = 0xabcdef;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x83, 0xf8, 0x64]; // cmp eax,100
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], None);
|
||||
let mut vmm = MockVmm::new(ip, vec![(Register::RAX, rax)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b100, rflags);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// cmp eax,-1
|
||||
fn test_cmp_rm32_imm8_2() -> MockResult {
|
||||
fn test_cmp_rm32_imm8_2() {
|
||||
let rax: u64 = 0xabcdef;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x83, 0xf8, 0xff]; // cmp eax,-1
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], None);
|
||||
let mut vmm = MockVmm::new(ip, vec![(Register::RAX, rax)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b101, rflags);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// cmp rax,rbx
|
||||
fn test_cmp_rm64_r64() -> MockResult {
|
||||
fn test_cmp_rm64_r64() {
|
||||
let rax: u64 = 0xabcdef;
|
||||
let rbx: u64 = 0x1234;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x48, 0x39, 0xd8, 0x00, 0xc3]; // cmp rax,rbx + two bytes garbage
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax), (Register::RBX, rbx)], None);
|
||||
let mut vmm = MockVmm::new(ip, vec![(Register::RAX, rax), (Register::RBX, rbx)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b100, rflags);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmp_64() -> MockResult {
|
||||
fn test_cmp_64() {
|
||||
let data = [
|
||||
(0xabcdef, 0x1234, 0b100),
|
||||
(0x0, 0x101, 0b1001_0101),
|
||||
@@ -309,7 +293,7 @@ mod tests {
|
||||
let rax = d.0;
|
||||
let rbx = d.1;
|
||||
let insn = [0x48, 0x39, 0xd8]; // cmp rax,rbx
|
||||
let mut vmm = MockVMM::new(
|
||||
let mut vmm = MockVmm::new(
|
||||
0x1000,
|
||||
vec![(Register::RAX, rax), (Register::RBX, rbx)],
|
||||
None,
|
||||
@@ -319,12 +303,10 @@ mod tests {
|
||||
let rflags: u64 = vmm.cpu_state(0).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(d.2, rflags);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmp_32() -> MockResult {
|
||||
fn test_cmp_32() {
|
||||
let data = [
|
||||
(0xabcdef, 0x1234, 0b100),
|
||||
(0x0, 0x101, 0b1001_0101),
|
||||
@@ -339,7 +321,7 @@ mod tests {
|
||||
let rax = d.0;
|
||||
let rbx = d.1;
|
||||
let insn = [0x39, 0xd8]; // cmp eax,ebx
|
||||
let mut vmm = MockVMM::new(
|
||||
let mut vmm = MockVmm::new(
|
||||
0x1000,
|
||||
vec![(Register::RAX, rax), (Register::RBX, rbx)],
|
||||
None,
|
||||
@@ -349,7 +331,5 @@ mod tests {
|
||||
let rflags: u64 = vmm.cpu_state(0).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(d.2, rflags);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
extern crate iced_x86;
|
||||
|
||||
use crate::arch::emulator::{EmulationError, PlatformEmulator, PlatformError};
|
||||
use crate::arch::x86::emulator::CpuStateManager;
|
||||
use crate::arch::x86::Exception;
|
||||
@@ -13,6 +11,8 @@ use iced_x86::*;
|
||||
|
||||
pub mod cmp;
|
||||
pub mod mov;
|
||||
pub mod movs;
|
||||
pub mod or;
|
||||
|
||||
fn get_op<T: CpuStateManager>(
|
||||
insn: &Instruction,
|
||||
@@ -38,8 +38,14 @@ fn get_op<T: CpuStateManager>(
|
||||
}
|
||||
}
|
||||
|
||||
let value = match insn.op_kind(op_index) {
|
||||
OpKind::Register => state.read_reg(insn.op_register(op_index))?,
|
||||
let value = match insn
|
||||
.try_op_kind(op_index)
|
||||
.map_err(|e| PlatformError::InvalidOperand(e.into()))?
|
||||
{
|
||||
OpKind::Register => state.read_reg(
|
||||
insn.try_op_register(op_index)
|
||||
.map_err(|e| PlatformError::InvalidOperand(e.into()))?,
|
||||
)?,
|
||||
OpKind::Memory => {
|
||||
let addr = memory_operand_address(insn, state, false)?;
|
||||
let mut memory: [u8; 8] = [0; 8];
|
||||
@@ -85,8 +91,15 @@ fn set_op<T: CpuStateManager>(
|
||||
}
|
||||
}
|
||||
|
||||
match insn.op_kind(op_index) {
|
||||
OpKind::Register => state.write_reg(insn.op_register(op_index), value)?,
|
||||
match insn
|
||||
.try_op_kind(op_index)
|
||||
.map_err(|e| PlatformError::InvalidOperand(e.into()))?
|
||||
{
|
||||
OpKind::Register => state.write_reg(
|
||||
insn.try_op_register(op_index)
|
||||
.map_err(|e| PlatformError::InvalidOperand(e.into()))?,
|
||||
value,
|
||||
)?,
|
||||
OpKind::Memory => {
|
||||
let addr = memory_operand_address(insn, state, true)?;
|
||||
platform.write_memory(addr, &value.to_le_bytes()[..op_size])?;
|
||||
@@ -117,7 +130,7 @@ fn memory_operand_address<T: CpuStateManager>(
|
||||
address += index;
|
||||
}
|
||||
|
||||
address += insn.memory_displacement() as u64;
|
||||
address += insn.memory_displacement64();
|
||||
|
||||
// Translate to a linear address.
|
||||
state.linearize(insn.memory_segment(), address, write)
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
// Copies the second operand (source operand) to the first operand (destination operand).
|
||||
//
|
||||
|
||||
extern crate iced_x86;
|
||||
|
||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
||||
use crate::arch::x86::emulator::instructions::*;
|
||||
use crate::arch::x86::Exception;
|
||||
@@ -39,8 +37,6 @@ macro_rules! mov_rm_r {
|
||||
)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
@@ -67,41 +63,50 @@ macro_rules! mov_rm_imm {
|
||||
)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! mov_r_rm {
|
||||
($bound:ty) => {
|
||||
macro_rules! movzx {
|
||||
($dest_op_size:ty, $src_op_size:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let src_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
let src_value = get_op(
|
||||
&insn,
|
||||
1,
|
||||
std::mem::size_of::<$src_op_size>(),
|
||||
state,
|
||||
platform,
|
||||
)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
set_op(
|
||||
&insn,
|
||||
0,
|
||||
std::mem::size_of::<$bound>(),
|
||||
std::mem::size_of::<$dest_op_size>(),
|
||||
state,
|
||||
platform,
|
||||
src_value,
|
||||
)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// MOV r/rm is a special case of MOVZX, where both operands have the same size.
|
||||
macro_rules! mov_r_rm {
|
||||
($op_size:ty) => {
|
||||
movzx!($op_size, $op_size);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! mov_r_imm {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
@@ -123,8 +128,6 @@ macro_rules! mov_r_imm {
|
||||
)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
@@ -210,6 +213,32 @@ impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm64_r64 {
|
||||
mov_rm_r!(u64);
|
||||
}
|
||||
|
||||
// MOVZX
|
||||
pub struct Movzx_r16_rm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Movzx_r16_rm8 {
|
||||
movzx!(u16, u8);
|
||||
}
|
||||
|
||||
pub struct Movzx_r32_rm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Movzx_r32_rm8 {
|
||||
movzx!(u32, u8);
|
||||
}
|
||||
|
||||
pub struct Movzx_r64_rm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Movzx_r64_rm8 {
|
||||
movzx!(u64, u8);
|
||||
}
|
||||
|
||||
pub struct Movzx_r32_rm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Movzx_r32_rm16 {
|
||||
movzx!(u32, u16);
|
||||
}
|
||||
|
||||
pub struct Movzx_r64_rm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Movzx_r64_rm16 {
|
||||
movzx!(u64, u16);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(unused_mut)]
|
||||
@@ -218,12 +247,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
// mov rax,rbx
|
||||
fn test_mov_r64_r64() -> MockResult {
|
||||
fn test_mov_r64_r64() {
|
||||
let rbx: u64 = 0x8899aabbccddeeff;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x48, 0x89, 0xd8];
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RBX, rbx)], None);
|
||||
let mut vmm = MockVmm::new(ip, vec![(Register::RBX, rbx)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rax: u64 = vmm
|
||||
@@ -232,18 +261,16 @@ mod tests {
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, rbx);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax,0x1122334411223344
|
||||
fn test_mov_r64_imm64() -> MockResult {
|
||||
fn test_mov_r64_imm64() {
|
||||
let imm64: u64 = 0x1122334411223344;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x48, 0xb8, 0x44, 0x33, 0x22, 0x11, 0x44, 0x33, 0x22, 0x11];
|
||||
let mut vmm = MockVMM::new(ip, vec![], None);
|
||||
let mut vmm = MockVmm::new(ip, vec![], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rax: u64 = vmm
|
||||
@@ -252,20 +279,18 @@ mod tests {
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, imm64);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax, [rax+rax]
|
||||
fn test_mov_r64_m64() -> MockResult {
|
||||
fn test_mov_r64_m64() {
|
||||
let target_rax: u64 = 0x1234567812345678;
|
||||
let mut rax: u64 = 0x100;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory: [u8; 8] = target_rax.to_le_bytes();
|
||||
let insn = [0x48, 0x8b, 0x04, 0x00];
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], Some((rax + rax, &memory)));
|
||||
let mut vmm = MockVmm::new(ip, vec![(Register::RAX, rax)], Some((rax + rax, &memory)));
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
rax = vmm
|
||||
@@ -274,18 +299,16 @@ mod tests {
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, target_rax);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov al,0x11
|
||||
fn test_mov_r8_imm8() -> MockResult {
|
||||
fn test_mov_r8_imm8() {
|
||||
let imm8: u8 = 0x11;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0xb0, 0x11];
|
||||
let mut vmm = MockVMM::new(ip, vec![], None);
|
||||
let mut vmm = MockVmm::new(ip, vec![], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let al = vmm
|
||||
@@ -294,18 +317,16 @@ mod tests {
|
||||
.read_reg(Register::AL)
|
||||
.unwrap();
|
||||
assert_eq!(al as u8, imm8);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov eax,0x11
|
||||
fn test_mov_r32_imm8() -> MockResult {
|
||||
fn test_mov_r32_imm8() {
|
||||
let imm8: u8 = 0x11;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0xb8, 0x11, 0x00, 0x00, 0x00];
|
||||
let mut vmm = MockVMM::new(ip, vec![], None);
|
||||
let mut vmm = MockVmm::new(ip, vec![], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let eax = vmm
|
||||
@@ -314,18 +335,16 @@ mod tests {
|
||||
.read_reg(Register::EAX)
|
||||
.unwrap();
|
||||
assert_eq!(eax as u8, imm8);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax,0x11223344
|
||||
fn test_mov_r64_imm32() -> MockResult {
|
||||
fn test_mov_r64_imm32() {
|
||||
let imm32: u32 = 0x11223344;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x48, 0xc7, 0xc0, 0x44, 0x33, 0x22, 0x11];
|
||||
let mut vmm = MockVMM::new(ip, vec![], None);
|
||||
let mut vmm = MockVmm::new(ip, vec![], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rax: u64 = vmm
|
||||
@@ -334,19 +353,17 @@ mod tests {
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, imm32 as u64);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov byte ptr [rax],dh
|
||||
fn test_mov_m8_r8() -> MockResult {
|
||||
fn test_mov_m8_r8() {
|
||||
let rax: u64 = 0x100;
|
||||
let dh: u8 = 0x99;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x88, 0x30];
|
||||
let mut vmm = MockVMM::new(
|
||||
let mut vmm = MockVmm::new(
|
||||
ip,
|
||||
vec![(Register::RAX, rax), (Register::DH, dh.into())],
|
||||
None,
|
||||
@@ -357,19 +374,17 @@ mod tests {
|
||||
vmm.read_memory(rax, &mut memory).unwrap();
|
||||
|
||||
assert_eq!(u8::from_le_bytes(memory), dh);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov dword ptr [rax],esi
|
||||
fn test_mov_m32_r32() -> MockResult {
|
||||
fn test_mov_m32_r32() {
|
||||
let rax: u64 = 0x100;
|
||||
let esi: u32 = 0x8899;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x89, 0x30];
|
||||
let mut vmm = MockVMM::new(
|
||||
let mut vmm = MockVmm::new(
|
||||
ip,
|
||||
vec![(Register::RAX, rax), (Register::ESI, esi.into())],
|
||||
None,
|
||||
@@ -380,20 +395,18 @@ mod tests {
|
||||
vmm.read_memory(rax, &mut memory).unwrap();
|
||||
|
||||
assert_eq!(u32::from_le_bytes(memory), esi);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov dword ptr [rax+0x00000001],edi
|
||||
fn test_mov_m32imm32_r32() -> MockResult {
|
||||
fn test_mov_m32imm32_r32() {
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x1;
|
||||
let edi: u32 = 0x8899;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x89, 0x3c, 0x05, 0x01, 0x00, 0x00, 0x00];
|
||||
let mut vmm = MockVMM::new(
|
||||
let mut vmm = MockVmm::new(
|
||||
ip,
|
||||
vec![(Register::RAX, rax), (Register::EDI, edi.into())],
|
||||
None,
|
||||
@@ -404,13 +417,11 @@ mod tests {
|
||||
vmm.read_memory(rax + displacement, &mut memory).unwrap();
|
||||
|
||||
assert_eq!(u32::from_le_bytes(memory), edi);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov eax,dword ptr [rax+10h]
|
||||
fn test_mov_r32_m32imm32() -> MockResult {
|
||||
fn test_mov_r32_m32imm32() {
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x10;
|
||||
let eax: u32 = 0xaabbccdd;
|
||||
@@ -418,7 +429,7 @@ mod tests {
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x8b, 0x40, 0x10];
|
||||
let mut vmm = MockVMM::new(
|
||||
let mut vmm = MockVmm::new(
|
||||
ip,
|
||||
vec![(Register::RAX, rax)],
|
||||
Some((rax + displacement, &memory)),
|
||||
@@ -431,13 +442,11 @@ mod tests {
|
||||
.read_reg(Register::EAX)
|
||||
.unwrap();
|
||||
assert_eq!(new_eax, eax as u64);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov al,byte ptr [rax+10h]
|
||||
fn test_mov_r8_m32imm32() -> MockResult {
|
||||
fn test_mov_r8_m32imm32() {
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x10;
|
||||
let al: u8 = 0xaa;
|
||||
@@ -445,7 +454,7 @@ mod tests {
|
||||
let cpu_id = 0;
|
||||
let insn = [0x8a, 0x40, 0x10];
|
||||
let memory: [u8; 1] = al.to_le_bytes();
|
||||
let mut vmm = MockVMM::new(
|
||||
let mut vmm = MockVmm::new(
|
||||
ip,
|
||||
vec![(Register::RAX, rax)],
|
||||
Some((rax + displacement, &memory)),
|
||||
@@ -458,14 +467,12 @@ mod tests {
|
||||
.read_reg(Register::AL)
|
||||
.unwrap();
|
||||
assert_eq!(new_al, al as u64);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax, 0x100
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
fn test_mov_r64_imm64_and_r64_m64() -> MockResult {
|
||||
fn test_mov_r64_imm64_and_r64_m64() {
|
||||
let target_rax: u64 = 0x1234567812345678;
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x10;
|
||||
@@ -476,7 +483,7 @@ mod tests {
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x01, 0x00, 0x00, // mov rax, 0x100
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
];
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((rax + displacement, &memory)));
|
||||
let mut vmm = MockVmm::new(ip, vec![], Some((rax + displacement, &memory)));
|
||||
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
|
||||
|
||||
let rbx: u64 = vmm
|
||||
@@ -485,14 +492,12 @@ mod tests {
|
||||
.read_reg(Register::RBX)
|
||||
.unwrap();
|
||||
assert_eq!(rbx, target_rax);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax, 0x100
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
fn test_mov_r64_imm64_and_r64_m64_first_insn() -> MockResult {
|
||||
fn test_mov_r64_imm64_and_r64_m64_first_insn() {
|
||||
let target_rax: u64 = 0x1234567812345678;
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x10;
|
||||
@@ -504,11 +509,11 @@ mod tests {
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
];
|
||||
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((rax + displacement, &memory)));
|
||||
let mut vmm = MockVmm::new(ip, vec![], Some((rax + displacement, &memory)));
|
||||
// Only run the first instruction.
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
assert_eq!(ip + 7 as u64, vmm.cpu_state(cpu_id).unwrap().ip());
|
||||
assert_eq!(ip + 7, vmm.cpu_state(cpu_id).unwrap().ip());
|
||||
|
||||
let new_rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
@@ -516,15 +521,13 @@ mod tests {
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, new_rax);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax, 0x100
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
// mov rax, 0x200
|
||||
fn test_mov_r64_imm64_and_r64_m64_two_insns() -> MockResult {
|
||||
fn test_mov_r64_imm64_and_r64_m64_two_insns() {
|
||||
let target_rax: u64 = 0x1234567812345678;
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x10;
|
||||
@@ -537,11 +540,11 @@ mod tests {
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x02, 0x00, 0x00, // mov rax, 0x200
|
||||
];
|
||||
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((rax + displacement, &memory)));
|
||||
let mut vmm = MockVmm::new(ip, vec![], Some((rax + displacement, &memory)));
|
||||
// Run the 2 first instructions.
|
||||
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
|
||||
|
||||
assert_eq!(ip + 7 + 4 as u64, vmm.cpu_state(cpu_id).unwrap().ip());
|
||||
assert_eq!(ip + 7 + 4, vmm.cpu_state(cpu_id).unwrap().ip());
|
||||
|
||||
let rbx: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
@@ -557,7 +560,61 @@ mod tests {
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, new_rax);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
#[test]
|
||||
// movzx eax, bl
|
||||
fn test_movzx_r32_r8l() {
|
||||
let bx: u16 = 0x8899;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x0f, 0xb6, 0xc3];
|
||||
let mut vmm = MockVmm::new(ip, vec![(Register::BX, bx as u64)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let eax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::EAX)
|
||||
.unwrap();
|
||||
assert_eq!(eax, (bx & 0xff) as u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// movzx eax, bh
|
||||
fn test_movzx_r32_r8h() {
|
||||
let bx: u16 = 0x8899;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x0f, 0xb6, 0xc7];
|
||||
let mut vmm = MockVmm::new(ip, vec![(Register::BX, bx as u64)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let eax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::EAX)
|
||||
.unwrap();
|
||||
assert_eq!(eax, (bx >> 8) as u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// movzx eax, byte ptr [rbx]
|
||||
fn test_movzx_r32_m8() {
|
||||
let rbx: u64 = 0x100;
|
||||
let value: u8 = 0xaa;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x0f, 0xb7, 0x03];
|
||||
let memory: [u8; 1] = value.to_le_bytes();
|
||||
let mut vmm = MockVmm::new(ip, vec![(Register::RBX, rbx)], Some((rbx, &memory)));
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let eax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::EAX)
|
||||
.unwrap();
|
||||
assert_eq!(eax, value as u64);
|
||||
}
|
||||
}
|
||||
|
||||
145
hypervisor/src/arch/x86/emulator/instructions/movs.rs
Normal file
145
hypervisor/src/arch/x86/emulator/instructions/movs.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
//
|
||||
// Copyright © 2021 Microsoft
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
//
|
||||
// MOVS - Move Data from String to String
|
||||
//
|
||||
|
||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
||||
use crate::arch::x86::emulator::instructions::*;
|
||||
use crate::arch::x86::regs::DF;
|
||||
use crate::arch::x86::Exception;
|
||||
|
||||
pub struct Movsd_m32_m32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Movsd_m32_m32 {
|
||||
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 mut rsi = state
|
||||
.read_reg(Register::RSI)
|
||||
.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::<u32>();
|
||||
|
||||
while count > 0 {
|
||||
let mut memory: [u8; 4] = [0; 4];
|
||||
|
||||
let src = state
|
||||
.linearize(Register::DS, rsi, false)
|
||||
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||
let dst = state
|
||||
.linearize(Register::ES, rdi, true)
|
||||
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||
|
||||
platform
|
||||
.read_memory(src, &mut memory[0..len])
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
platform
|
||||
.write_memory(dst, &memory[0..len])
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
if df {
|
||||
rsi = rsi.wrapping_sub(len as u64);
|
||||
rdi = rdi.wrapping_sub(len as u64);
|
||||
} else {
|
||||
rsi = rsi.wrapping_add(len as u64);
|
||||
rdi = rdi.wrapping_add(len as u64);
|
||||
}
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
state
|
||||
.write_reg(Register::RSI, rsi)
|
||||
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||
state
|
||||
.write_reg(Register::RDI, rdi)
|
||||
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||
if insn.has_rep_prefix() {
|
||||
state
|
||||
.write_reg(Register::ECX, 0)
|
||||
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(unused_mut)]
|
||||
use super::*;
|
||||
use crate::arch::x86::emulator::mock_vmm::*;
|
||||
|
||||
#[test]
|
||||
fn test_rep_movsd_m32_m32() {
|
||||
let ip: u64 = 0x1000;
|
||||
let memory: [u8; 24] = [
|
||||
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||
0xdd, 0xcc, 0xbb, 0xaa, // 0xaabbccdd
|
||||
0xa5, 0x5a, 0xa5, 0x5a, // 0x5aa55aa5
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
0x00, 0x00, 0x00, 0x00, // 0x00000000
|
||||
];
|
||||
let insn = [0xf3, 0xa5]; // rep movsd
|
||||
let regs = vec![(Register::ECX, 3), (Register::ESI, 0), (Register::EDI, 0xc)];
|
||||
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(0xc, &mut data).unwrap();
|
||||
assert_eq!(0x12345678, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(0xc + 4, &mut data).unwrap();
|
||||
assert_eq!(0xaabbccdd, <u32>::from_le_bytes(data));
|
||||
vmm.read_memory(0xc + 8, &mut data).unwrap();
|
||||
assert_eq!(0x5aa55aa5, <u32>::from_le_bytes(data));
|
||||
// The rest should be default value 0 from MockVmm
|
||||
vmm.read_memory(0xc + 12, &mut data).unwrap();
|
||||
assert_eq!(0x0, <u32>::from_le_bytes(data));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movsd_m32_m32() {
|
||||
let ip: u64 = 0x1000;
|
||||
let memory: [u8; 4] = [
|
||||
0x78, 0x56, 0x34, 0x12, // 0x12345678
|
||||
];
|
||||
let insn = [0xa5]; // movsd
|
||||
let regs = vec![(Register::ESI, 0), (Register::EDI, 0x8)];
|
||||
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(0x8, &mut data).unwrap();
|
||||
assert_eq!(0x12345678, <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));
|
||||
}
|
||||
}
|
||||
83
hypervisor/src/arch/x86/emulator/instructions/or.rs
Normal file
83
hypervisor/src/arch/x86/emulator/instructions/or.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// Copyright © 2021 Microsoft
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
//
|
||||
// OR - Logical inclusive OR
|
||||
//
|
||||
|
||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
||||
use crate::arch::x86::emulator::instructions::*;
|
||||
use crate::arch::x86::Exception;
|
||||
|
||||
macro_rules! or_rm_r {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let src_reg_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
let dst_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
let result = src_reg_value | dst_value;
|
||||
|
||||
set_op(
|
||||
&insn,
|
||||
0,
|
||||
std::mem::size_of::<$bound>(),
|
||||
state,
|
||||
platform,
|
||||
result,
|
||||
)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Or_rm8_r8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Or_rm8_r8 {
|
||||
or_rm_r!(u8);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(unused_mut)]
|
||||
use super::*;
|
||||
|
||||
use crate::arch::x86::emulator::mock_vmm::*;
|
||||
|
||||
#[test]
|
||||
// or byte ptr [rax+1h], sil
|
||||
fn test_or_rm8_r8() {
|
||||
let rax = 0;
|
||||
let insn = [0x40, 0x08, 0x70, 0x1];
|
||||
let cpu_id = 0;
|
||||
let ip: u64 = 0x1000;
|
||||
let sil = 0xaa;
|
||||
let memory = [0x0, 0x55];
|
||||
|
||||
let mut vmm = MockVmm::new(
|
||||
ip,
|
||||
vec![(Register::SIL, sil), (Register::RAX, rax)],
|
||||
Some((0, &memory)),
|
||||
);
|
||||
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let mut out: [u8; 1] = [0; 1];
|
||||
|
||||
vmm.read_memory(rax + 1, &mut out).unwrap();
|
||||
assert_eq!(u8::from_le_bytes(out), 0xff);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
extern crate iced_x86;
|
||||
|
||||
use crate::arch::emulator::{EmulationError, EmulationResult, PlatformEmulator, PlatformError};
|
||||
use crate::arch::x86::emulator::instructions::*;
|
||||
use crate::arch::x86::regs::*;
|
||||
@@ -518,7 +516,17 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
||||
(mov, Mov_rm32_imm32),
|
||||
(mov, Mov_rm32_r32),
|
||||
(mov, Mov_rm64_imm32),
|
||||
(mov, Mov_rm64_r64)
|
||||
(mov, Mov_rm64_r64),
|
||||
// MOVZX
|
||||
(mov, Movzx_r16_rm8),
|
||||
(mov, Movzx_r32_rm8),
|
||||
(mov, Movzx_r64_rm8),
|
||||
(mov, Movzx_r32_rm16),
|
||||
(mov, Movzx_r64_rm16),
|
||||
// MOVS
|
||||
(movs, Movsd_m32_m32),
|
||||
// OR
|
||||
(or, Or_rm8_r8)
|
||||
);
|
||||
|
||||
handler
|
||||
@@ -632,8 +640,6 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
||||
mod mock_vmm {
|
||||
#![allow(unused_mut)]
|
||||
|
||||
extern crate env_logger;
|
||||
|
||||
use super::*;
|
||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
||||
use crate::arch::x86::emulator::{Emulator, EmulatorCpuState as CpuState};
|
||||
@@ -642,29 +648,31 @@ mod mock_vmm {
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MockVMM {
|
||||
pub struct MockVmm {
|
||||
memory: Vec<u8>,
|
||||
state: Arc<Mutex<CpuState>>,
|
||||
}
|
||||
|
||||
unsafe impl Sync for MockVMM {}
|
||||
unsafe impl Sync for MockVmm {}
|
||||
|
||||
pub type MockResult = Result<(), EmulationError<Exception>>;
|
||||
|
||||
impl MockVMM {
|
||||
pub fn new(ip: u64, regs: Vec<(Register, u64)>, memory: Option<(u64, &[u8])>) -> MockVMM {
|
||||
impl MockVmm {
|
||||
pub fn new(ip: u64, regs: Vec<(Register, u64)>, memory: Option<(u64, &[u8])>) -> MockVmm {
|
||||
let _ = env_logger::try_init();
|
||||
let cs_reg = segment_from_gdt(gdt_entry(0xc09b, 0, 0xffffffff), 1);
|
||||
let ds_reg = segment_from_gdt(gdt_entry(0xc093, 0, 0xffffffff), 2);
|
||||
let es_reg = segment_from_gdt(gdt_entry(0xc093, 0, 0xffffffff), 3);
|
||||
let mut initial_state = CpuState::default();
|
||||
initial_state.set_ip(ip);
|
||||
initial_state.write_segment(Register::CS, cs_reg).unwrap();
|
||||
initial_state.write_segment(Register::DS, ds_reg).unwrap();
|
||||
initial_state.write_segment(Register::ES, es_reg).unwrap();
|
||||
for (reg, value) in regs {
|
||||
initial_state.write_reg(reg, value).unwrap();
|
||||
}
|
||||
|
||||
let mut vmm = MockVMM {
|
||||
let mut vmm = MockVmm {
|
||||
memory: vec![0; 8192],
|
||||
state: Arc::new(Mutex::new(initial_state)),
|
||||
};
|
||||
@@ -700,7 +708,7 @@ mod mock_vmm {
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformEmulator for MockVMM {
|
||||
impl PlatformEmulator for MockVmm {
|
||||
type CpuState = CpuState;
|
||||
|
||||
fn read_memory(&self, gva: u64, data: &mut [u8]) -> Result<(), PlatformError> {
|
||||
@@ -708,7 +716,7 @@ mod mock_vmm {
|
||||
"Memory read {} bytes from [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gva
|
||||
gva + data.len() as u64 - 1
|
||||
);
|
||||
data.copy_from_slice(&self.memory[gva as usize..gva as usize + data.len()]);
|
||||
Ok(())
|
||||
@@ -719,7 +727,7 @@ mod mock_vmm {
|
||||
"Memory write {} bytes at [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gva
|
||||
gva + data.len() as u64 - 1
|
||||
);
|
||||
self.memory[gva as usize..gva as usize + data.len()].copy_from_slice(data);
|
||||
|
||||
@@ -765,7 +773,7 @@ mod tests {
|
||||
//
|
||||
// mov rax, 0x1000
|
||||
// Test with a first instruction truncated.
|
||||
fn test_fetch_first_instruction() -> MockResult {
|
||||
fn test_fetch_first_instruction() {
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
@@ -782,7 +790,7 @@ mod tests {
|
||||
0x48, 0xc7, 0xc0, 0x00, // mov rax, 0x1000 -- Missing bytes: 0x00, 0x10, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((ip, &memory)));
|
||||
let mut vmm = MockVmm::new(ip, vec![], Some((ip, &memory)));
|
||||
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
|
||||
|
||||
let rax: u64 = vmm
|
||||
@@ -791,8 +799,6 @@ mod tests {
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, ip);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -801,7 +807,7 @@ mod tests {
|
||||
// mov rax, 0x1000
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
// Test with a 2nd instruction truncated.
|
||||
fn test_fetch_second_instruction() -> MockResult {
|
||||
fn test_fetch_second_instruction() {
|
||||
let target_rax: u64 = 0x1234567812345678;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
@@ -819,7 +825,7 @@ mod tests {
|
||||
0x48, 0x8b, // Truncated mov rbx, qword ptr [rax+10h] -- missing [0x58, 0x10]
|
||||
];
|
||||
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((ip, &memory)));
|
||||
let mut vmm = MockVmm::new(ip, vec![], Some((ip, &memory)));
|
||||
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
|
||||
|
||||
let rbx: u64 = vmm
|
||||
@@ -828,8 +834,6 @@ mod tests {
|
||||
.read_reg(Register::RBX)
|
||||
.unwrap();
|
||||
assert_eq!(rbx, target_rax);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -838,7 +842,7 @@ mod tests {
|
||||
// mov rax, 0x1000
|
||||
// Test with a first instruction truncated and a bad fetched instruction.
|
||||
// Verify that the instruction emulation returns an error.
|
||||
fn test_fetch_bad_insn() -> MockResult {
|
||||
fn test_fetch_bad_insn() {
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
@@ -851,9 +855,7 @@ mod tests {
|
||||
0x48, 0xc7, 0xc0, 0x00, // mov rax, 0x1000 -- Missing bytes: 0x00, 0x10, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((ip, &memory)));
|
||||
let mut vmm = MockVmm::new(ip, vec![], Some((ip, &memory)));
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,27 +11,14 @@
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
|
||||
pub mod emulator;
|
||||
pub mod gdt;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(unused)]
|
||||
#[allow(
|
||||
clippy::unreadable_literal,
|
||||
clippy::redundant_static_lifetimes,
|
||||
clippy::trivially_copy_pass_by_ref,
|
||||
clippy::useless_transmute,
|
||||
clippy::should_implement_trait,
|
||||
clippy::transmute_ptr_to_ptr,
|
||||
clippy::unreadable_literal,
|
||||
clippy::redundant_static_lifetimes
|
||||
)]
|
||||
pub mod msr_index;
|
||||
|
||||
pub mod emulator;
|
||||
|
||||
// MTRR constants
|
||||
pub const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11
|
||||
pub const MTRR_MEM_TYPE_WB: u64 = 0x6;
|
||||
@@ -40,11 +27,11 @@ pub const MTRR_MEM_TYPE_WB: u64 = 0x6;
|
||||
pub const NUM_IOAPIC_PINS: usize = 24;
|
||||
|
||||
// X86 Exceptions
|
||||
#[allow(dead_code)]
|
||||
#[allow(dead_code, clippy::upper_case_acronyms)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Exception {
|
||||
DE = 1, // Divide Error
|
||||
DB = 2, // Debug Exception
|
||||
DE = 0, // Divide Error
|
||||
DB = 1, // Debug Exception
|
||||
BP = 3, // Breakpoint
|
||||
OF = 4, // Overflow
|
||||
BR = 5, // BOUND Range Exceeded
|
||||
@@ -122,3 +109,23 @@ pub fn segment_type_ro(t: u8) -> bool {
|
||||
pub fn segment_type_expand_down(t: u8) -> bool {
|
||||
!segment_type_code(t) && (t & EXPAND_DOWN_SEGMENT_TYPE != 0)
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! msr {
|
||||
($msr:expr) => {
|
||||
MsrEntry {
|
||||
index: $msr,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! msr_data {
|
||||
($msr:expr, $data:expr) => {
|
||||
MsrEntry {
|
||||
index: $msr,
|
||||
data: $data,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ pub const PF_SHIFT: usize = 2;
|
||||
pub const AF_SHIFT: usize = 4;
|
||||
pub const ZF_SHIFT: usize = 6;
|
||||
pub const SF_SHIFT: usize = 7;
|
||||
pub const DF_SHIFT: usize = 10;
|
||||
pub const OF_SHIFT: usize = 11;
|
||||
|
||||
pub const CF: u64 = 1 << CF_SHIFT;
|
||||
@@ -29,4 +30,5 @@ pub const PF: u64 = 1 << PF_SHIFT;
|
||||
pub const AF: u64 = 1 << AF_SHIFT;
|
||||
pub const ZF: u64 = 1 << ZF_SHIFT;
|
||||
pub const SF: u64 = 1 << SF_SHIFT;
|
||||
pub const DF: u64 = 1 << DF_SHIFT;
|
||||
pub const OF: u64 = 1 << OF_SHIFT;
|
||||
|
||||
@@ -23,6 +23,8 @@ use crate::CpuState;
|
||||
use crate::MpState;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::Xsave;
|
||||
#[cfg(feature = "mshv")]
|
||||
use mshv_bindings::*;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -172,7 +174,7 @@ pub enum HypervisorCpuError {
|
||||
/// Enabling HyperV SynIC error
|
||||
///
|
||||
#[error("Failed to enable HyperV SynIC")]
|
||||
EnableHyperVSynIC(#[source] anyhow::Error),
|
||||
EnableHyperVSyncIc(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting AArch64 core register error
|
||||
///
|
||||
@@ -198,6 +200,17 @@ pub enum HypervisorCpuError {
|
||||
///
|
||||
#[error("Failed to set system register: {0}")]
|
||||
SetSysRegister(#[source] anyhow::Error),
|
||||
///
|
||||
/// GVA translation error
|
||||
///
|
||||
#[error("Failed to translate GVA: {0}")]
|
||||
TranslateVirtualAddress(#[source] anyhow::Error),
|
||||
///
|
||||
/// Failed to initialize TDX on CPU
|
||||
///
|
||||
#[cfg(feature = "tdx")]
|
||||
#[error("Failed to initialize TDX: {0}")]
|
||||
InitializeTdx(#[source] std::io::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -398,4 +411,14 @@ pub trait Vcpu: Send + Sync {
|
||||
/// Triggers the running of the current virtual CPU returning an exit reason.
|
||||
///
|
||||
fn run(&self) -> std::result::Result<VmExit, HypervisorCpuError>;
|
||||
#[cfg(all(feature = "mshv", target_arch = "x86_64"))]
|
||||
///
|
||||
/// Translate guest virtual address to guest physical address
|
||||
///
|
||||
fn translate_gva(&self, gva: u64, flags: u64) -> Result<(u64, hv_translate_gva_result)>;
|
||||
///
|
||||
/// Initialize TDX support on the vCPU
|
||||
///
|
||||
#[cfg(feature = "tdx")]
|
||||
fn tdx_init(&self, hob_address: u64) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -12,10 +12,7 @@ use crate::vm::Vm;
|
||||
use crate::x86_64::CpuId;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::x86_64::MsrList;
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
use kvm_ioctls::Cap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -43,16 +40,6 @@ pub enum HypervisorError {
|
||||
#[error("Failed to get API Version: {0}")]
|
||||
GetApiVersion(#[source] anyhow::Error),
|
||||
///
|
||||
/// Vcpu mmap error
|
||||
///
|
||||
#[error("Failed to get Vcpu Mmap: {0}")]
|
||||
GetVcpuMmap(#[source] anyhow::Error),
|
||||
///
|
||||
/// Max Vcpu error
|
||||
///
|
||||
#[error("Failed to get number of max vcpus: {0}")]
|
||||
GetMaxVcpu(#[source] anyhow::Error),
|
||||
///
|
||||
/// CpuId error
|
||||
///
|
||||
#[error("Failed to get cpuid: {0}")]
|
||||
@@ -67,6 +54,11 @@ pub enum HypervisorError {
|
||||
///
|
||||
#[error("Incompatible API version")]
|
||||
IncompatibleApiVersion,
|
||||
///
|
||||
/// Checking extensions failed
|
||||
///
|
||||
#[error("Checking extensions:{0}")]
|
||||
CheckExtensions(#[source] anyhow::Error),
|
||||
}
|
||||
|
||||
///
|
||||
@@ -85,39 +77,32 @@ pub trait Hypervisor: Send + Sync {
|
||||
/// Return a hypervisor-agnostic Vm trait object
|
||||
///
|
||||
fn create_vm(&self) -> Result<Arc<dyn Vm>>;
|
||||
#[cfg(feature = "kvm")]
|
||||
///
|
||||
/// Returns the size of the memory mapping required to use the vcpu's structures
|
||||
/// Create a Vm of a specific type using the underlying hypervisor
|
||||
/// Return a hypervisor-agnostic Vm trait object
|
||||
///
|
||||
fn get_vcpu_mmap_size(&self) -> Result<usize>;
|
||||
#[cfg(feature = "kvm")]
|
||||
///
|
||||
/// Gets the recommended maximum number of VCPUs per VM.
|
||||
///
|
||||
fn get_max_vcpus(&self) -> Result<usize>;
|
||||
#[cfg(feature = "kvm")]
|
||||
///
|
||||
/// Gets the recommended number of VCPUs per VM.
|
||||
///
|
||||
fn get_nr_vcpus(&self) -> Result<usize>;
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
///
|
||||
/// Checks if a particular `Cap` is available.
|
||||
///
|
||||
fn check_capability(&self, c: Cap) -> bool;
|
||||
fn create_vm_with_type(&self, _vm_type: u64) -> Result<Arc<dyn Vm>> {
|
||||
unreachable!()
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Get the supported CpuID
|
||||
///
|
||||
fn get_cpuid(&self) -> Result<CpuId>;
|
||||
#[cfg(not(feature = "mshv"))]
|
||||
///
|
||||
/// Check particular extensions if any
|
||||
///
|
||||
fn check_required_extensions(&self) -> Result<()>;
|
||||
fn check_required_extensions(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Retrieve the list of MSRs supported by the hypervisor.
|
||||
///
|
||||
fn get_msr_list(&self) -> Result<MsrList>;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
///
|
||||
/// Retrieve AArch64 host maximum IPA size supported by KVM.
|
||||
///
|
||||
fn get_host_ipa_limit(&self) -> i32;
|
||||
}
|
||||
|
||||
@@ -32,19 +32,17 @@ pub use {kvm_ioctls::Cap, kvm_ioctls::Kvm};
|
||||
|
||||
// This macro gets the offset of a structure (i.e `str`) member (i.e `field`) without having
|
||||
// an instance of that structure.
|
||||
// It uses a null pointer to retrieve the offset to the field.
|
||||
// Inspired by C solution: `#define offsetof(str, f) ((size_t)(&((str *)0)->f))`.
|
||||
// Doing `offset__of!(user_pt_regs, pstate)` in our rust code will trigger the following:
|
||||
// unsafe { &(*(0 as *const user_pt_regs)).pstate as *const _ as usize }
|
||||
// The dereference expression produces an lvalue, but that lvalue is not actually read from,
|
||||
// we're just doing pointer math on it, so in theory, it should safe.
|
||||
#[macro_export]
|
||||
macro_rules! offset__of {
|
||||
($str:ty, $field:ident) => {
|
||||
unsafe { &(*std::ptr::null::<$str>()).$field as *const _ as usize }
|
||||
};
|
||||
}
|
||||
($str:ty, $($field:ident)+) => ({
|
||||
let tmp: std::mem::MaybeUninit<$str> = std::mem::MaybeUninit::uninit();
|
||||
let tmp = unsafe { tmp.assume_init() };
|
||||
let base = &tmp as *const _ as usize;
|
||||
let member = &tmp.$($field)* as *const _ as usize;
|
||||
|
||||
member - base
|
||||
});
|
||||
}
|
||||
// Get the ID of a core register
|
||||
#[macro_export]
|
||||
macro_rules! arm64_core_reg_id {
|
||||
|
||||
@@ -16,11 +16,14 @@ pub use crate::aarch64::{
|
||||
use crate::cpu;
|
||||
use crate::device;
|
||||
use crate::hypervisor;
|
||||
use crate::vec_with_array_field;
|
||||
use crate::vm::{self, VmmOps};
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::{arm64_core_reg_id, offset__of};
|
||||
use kvm_ioctls::{NoDatamatch, VcpuFd, VmFd};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use std::convert::TryInto;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::result;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -32,49 +35,46 @@ use vmm_sys_util::eventfd::EventFd;
|
||||
// x86_64 dependencies
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod x86_64;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::x86::NUM_IOAPIC_PINS;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use aarch64::{RegList, Register, StandardRegisters};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use kvm_bindings::{
|
||||
kvm_enable_cap, kvm_msr_entry, MsrList, KVM_CAP_HYPERV_SYNIC, KVM_CAP_SPLIT_IRQCHIP,
|
||||
};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use x86_64::{
|
||||
check_required_kvm_extensions, FpuState, SpecialRegisters, StandardRegisters, KVM_TSS_ADDRESS,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use aarch64::{RegList, Register, StandardRegisters};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::{
|
||||
CpuId, CpuIdEntry, ExtendedControlRegisters, LapicState, MsrEntries, VcpuKvmState as CpuState,
|
||||
Xsave, CPUID_FLAG_VALID_INDEX,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use kvm_bindings::{
|
||||
kvm_enable_cap, kvm_msr_entry, MsrList, KVM_CAP_HYPERV_SYNIC, KVM_CAP_SPLIT_IRQCHIP,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::x86::NUM_IOAPIC_PINS;
|
||||
|
||||
// aarch64 dependencies
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod aarch64;
|
||||
pub use kvm_bindings;
|
||||
#[cfg(feature = "tdx")]
|
||||
use kvm_bindings::KVMIO;
|
||||
pub use kvm_bindings::{
|
||||
kvm_create_device, kvm_device_type_KVM_DEV_TYPE_VFIO, kvm_irq_routing, kvm_irq_routing_entry,
|
||||
kvm_userspace_memory_region, KVM_IRQ_ROUTING_IRQCHIP, KVM_IRQ_ROUTING_MSI,
|
||||
KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY, KVM_MSI_VALID_DEVID,
|
||||
};
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use kvm_bindings::{
|
||||
kvm_regs, user_fpsimd_state, user_pt_regs, KVM_NR_SPSR, KVM_REG_ARM64, KVM_REG_ARM_CORE,
|
||||
KVM_REG_SIZE_U128, KVM_REG_SIZE_U32, KVM_REG_SIZE_U64,
|
||||
};
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use std::mem;
|
||||
|
||||
pub use kvm_bindings;
|
||||
pub use kvm_bindings::{
|
||||
kvm_create_device, kvm_device_type_KVM_DEV_TYPE_VFIO, kvm_irq_routing, kvm_irq_routing_entry,
|
||||
kvm_userspace_memory_region, KVM_IRQ_ROUTING_MSI, KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY,
|
||||
KVM_MSI_VALID_DEVID,
|
||||
};
|
||||
pub use kvm_ioctls;
|
||||
pub use kvm_ioctls::{Cap, Kvm};
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use std::mem;
|
||||
use thiserror::Error;
|
||||
#[cfg(feature = "tdx")]
|
||||
use vmm_sys_util::{ioctl::ioctl_with_val, ioctl_expr, ioctl_ioc_nr, ioctl_iowr_nr};
|
||||
///
|
||||
/// Export generically-named wrappers of kvm-bindings for Unix-based platforms
|
||||
///
|
||||
@@ -86,6 +86,21 @@ pub use {
|
||||
kvm_bindings::kvm_vcpu_events as VcpuEvents, kvm_ioctls::DeviceFd, kvm_ioctls::IoEventAddress,
|
||||
kvm_ioctls::VcpuExit,
|
||||
};
|
||||
|
||||
#[cfg(feature = "tdx")]
|
||||
ioctl_iowr_nr!(KVM_MEMORY_ENCRYPT_OP, KVMIO, 0xba, std::os::raw::c_ulong);
|
||||
|
||||
#[cfg(feature = "tdx")]
|
||||
#[repr(u32)]
|
||||
enum TdxCommand {
|
||||
#[allow(dead_code)]
|
||||
Capabilities = 0,
|
||||
InitVm,
|
||||
InitVcpu,
|
||||
InitMemRegion,
|
||||
Finalize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct KvmVmState {}
|
||||
|
||||
@@ -98,36 +113,6 @@ pub struct KvmVm {
|
||||
state: KvmVmState,
|
||||
}
|
||||
|
||||
// Returns a `Vec<T>` with a size in bytes at least as large as `size_in_bytes`.
|
||||
fn vec_with_size_in_bytes<T: Default>(size_in_bytes: usize) -> Vec<T> {
|
||||
let rounded_size = (size_in_bytes + size_of::<T>() - 1) / size_of::<T>();
|
||||
let mut v = Vec::with_capacity(rounded_size);
|
||||
v.resize_with(rounded_size, T::default);
|
||||
v
|
||||
}
|
||||
|
||||
// The kvm API has many structs that resemble the following `Foo` structure:
|
||||
//
|
||||
// ```
|
||||
// #[repr(C)]
|
||||
// struct Foo {
|
||||
// some_data: u32
|
||||
// entries: __IncompleteArrayField<__u32>,
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// In order to allocate such a structure, `size_of::<Foo>()` would be too small because it would not
|
||||
// include any space for `entries`. To make the allocation large enough while still being aligned
|
||||
// for `Foo`, a `Vec<Foo>` is created. Only the first element of `Vec<Foo>` would actually be used
|
||||
// as a `Foo`. The remaining memory in the `Vec<Foo>` is for `entries`, which must be contiguous
|
||||
// with `Foo`. This function is used to make the `Vec<Foo>` with enough space for `count` entries.
|
||||
use std::mem::size_of;
|
||||
fn vec_with_array_field<T: Default, F>(count: usize) -> Vec<T> {
|
||||
let element_space = count * size_of::<F>();
|
||||
let vec_size_bytes = size_of::<T>() + element_space;
|
||||
vec_with_size_in_bytes(vec_size_bytes)
|
||||
}
|
||||
|
||||
///
|
||||
/// Implementation of Vm trait for KVM
|
||||
/// Example:
|
||||
@@ -182,7 +167,7 @@ impl vm::Vm for KvmVm {
|
||||
) -> vm::Result<Arc<dyn cpu::Vcpu>> {
|
||||
let vc = self
|
||||
.fd
|
||||
.create_vcpu(id)
|
||||
.create_vcpu(id as u64)
|
||||
.map_err(|e| vm::HypervisorVmError::CreateVcpu(e.into()))?;
|
||||
let vcpu = KvmVcpu {
|
||||
fd: vc,
|
||||
@@ -314,8 +299,10 @@ impl vm::Vm for KvmVm {
|
||||
// Create split irqchip
|
||||
// Only the local APIC is emulated in kernel, both PICs and IOAPIC
|
||||
// are not.
|
||||
let mut cap: kvm_enable_cap = Default::default();
|
||||
cap.cap = KVM_CAP_SPLIT_IRQCHIP;
|
||||
let mut cap = kvm_enable_cap {
|
||||
cap: KVM_CAP_SPLIT_IRQCHIP,
|
||||
..Default::default()
|
||||
};
|
||||
cap.args[0] = NUM_IOAPIC_PINS as u64;
|
||||
self.fd
|
||||
.enable_cap(&cap)
|
||||
@@ -372,14 +359,117 @@ impl vm::Vm for KvmVm {
|
||||
.get_dirty_log(slot, memory_size as usize)
|
||||
.map_err(|e| vm::HypervisorVmError::GetDirtyLog(e.into()))
|
||||
}
|
||||
|
||||
///
|
||||
/// Initialize TDX for this VM
|
||||
///
|
||||
#[cfg(feature = "tdx")]
|
||||
fn tdx_init(&self, cpuid: &CpuId, max_vcpus: u32) -> vm::Result<()> {
|
||||
#[repr(C)]
|
||||
struct TdxInitVm {
|
||||
max_vcpus: u32,
|
||||
reserved: u32,
|
||||
attributes: u64,
|
||||
cpuid: u64,
|
||||
}
|
||||
let data = TdxInitVm {
|
||||
max_vcpus,
|
||||
reserved: 0,
|
||||
attributes: 1, // TDX1_TD_ATTRIBUTE_DEBUG,
|
||||
cpuid: cpuid.as_fam_struct_ptr() as u64,
|
||||
};
|
||||
|
||||
tdx_command(
|
||||
&self.fd.as_raw_fd(),
|
||||
TdxCommand::InitVm,
|
||||
0,
|
||||
&data as *const _ as u64,
|
||||
)
|
||||
.map_err(vm::HypervisorVmError::InitializeTdx)
|
||||
}
|
||||
|
||||
///
|
||||
/// Finalize the TDX setup for this VM
|
||||
///
|
||||
#[cfg(feature = "tdx")]
|
||||
fn tdx_finalize(&self) -> vm::Result<()> {
|
||||
tdx_command(&self.fd.as_raw_fd(), TdxCommand::Finalize, 0, 0)
|
||||
.map_err(vm::HypervisorVmError::FinalizeTdx)
|
||||
}
|
||||
|
||||
///
|
||||
/// Initialize memory regions for the TDX VM
|
||||
///
|
||||
#[cfg(feature = "tdx")]
|
||||
fn tdx_init_memory_region(
|
||||
&self,
|
||||
host_address: u64,
|
||||
guest_address: u64,
|
||||
size: u64,
|
||||
measure: bool,
|
||||
) -> vm::Result<()> {
|
||||
#[repr(C)]
|
||||
struct TdxInitMemRegion {
|
||||
host_address: u64,
|
||||
guest_address: u64,
|
||||
pages: u64,
|
||||
}
|
||||
let data = TdxInitMemRegion {
|
||||
host_address,
|
||||
guest_address,
|
||||
pages: size / 4096,
|
||||
};
|
||||
|
||||
tdx_command(
|
||||
&self.fd.as_raw_fd(),
|
||||
TdxCommand::InitMemRegion,
|
||||
if measure { 1 } else { 0 },
|
||||
&data as *const _ as u64,
|
||||
)
|
||||
.map_err(vm::HypervisorVmError::InitMemRegionTdx)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tdx")]
|
||||
fn tdx_command(
|
||||
fd: &RawFd,
|
||||
command: TdxCommand,
|
||||
metadata: u32,
|
||||
data: u64,
|
||||
) -> std::result::Result<(), std::io::Error> {
|
||||
#[repr(C)]
|
||||
struct TdxIoctlCmd {
|
||||
command: TdxCommand,
|
||||
metadata: u32,
|
||||
data: u64,
|
||||
}
|
||||
let cmd = TdxIoctlCmd {
|
||||
command,
|
||||
metadata,
|
||||
data,
|
||||
};
|
||||
let ret = unsafe {
|
||||
ioctl_with_val(
|
||||
fd,
|
||||
KVM_MEMORY_ENCRYPT_OP(),
|
||||
&cmd as *const TdxIoctlCmd as std::os::raw::c_ulong,
|
||||
)
|
||||
};
|
||||
|
||||
if ret < 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wrapper over KVM system ioctls.
|
||||
pub struct KvmHypervisor {
|
||||
kvm: Kvm,
|
||||
}
|
||||
/// Enum for KVM related error
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum KvmError {
|
||||
#[error("Capability missing: {0:?}")]
|
||||
CapabilityMissing(Cap),
|
||||
}
|
||||
pub type KvmResult<T> = result::Result<T, KvmError>;
|
||||
@@ -405,18 +495,18 @@ impl KvmHypervisor {
|
||||
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
|
||||
///
|
||||
impl hypervisor::Hypervisor for KvmHypervisor {
|
||||
/// Create a KVM vm object and return the object as Vm trait object
|
||||
/// Create a KVM vm object of a specific VM type and return the object as Vm trait object
|
||||
/// Example
|
||||
/// # extern crate hypervisor;
|
||||
/// # use hypervisor::KvmHypervisor;
|
||||
/// use hypervisor::KvmVm;
|
||||
/// let hypervisor = KvmHypervisor::new().unwrap();
|
||||
/// let vm = hypervisor.create_vm().unwrap()
|
||||
/// let vm = hypervisor.create_vm_with_type(KvmVmType::LegacyVm).unwrap()
|
||||
///
|
||||
fn create_vm(&self) -> hypervisor::Result<Arc<dyn vm::Vm>> {
|
||||
fn create_vm_with_type(&self, vm_type: u64) -> hypervisor::Result<Arc<dyn vm::Vm>> {
|
||||
let fd: VmFd;
|
||||
loop {
|
||||
match self.kvm.create_vm() {
|
||||
match self.kvm.create_vm_with_type(vm_type) {
|
||||
Ok(res) => fd = res,
|
||||
Err(e) => {
|
||||
if e.errno() == libc::EINTR {
|
||||
@@ -438,7 +528,7 @@ impl hypervisor::Hypervisor for KvmHypervisor {
|
||||
{
|
||||
let msr_list = self.get_msr_list()?;
|
||||
let num_msrs = msr_list.as_fam_struct_ref().nmsrs as usize;
|
||||
let mut msrs = MsrEntries::new(num_msrs);
|
||||
let mut msrs = MsrEntries::new(num_msrs).unwrap();
|
||||
let indices = msr_list.as_slice();
|
||||
let msr_entries = msrs.as_mut_slice();
|
||||
for (pos, index) in indices.iter().enumerate() {
|
||||
@@ -461,38 +551,34 @@ impl hypervisor::Hypervisor for KvmHypervisor {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_required_extensions(&self) -> hypervisor::Result<()> {
|
||||
check_required_kvm_extensions(&self.kvm).expect("Missing KVM capabilities");
|
||||
Ok(())
|
||||
/// Create a KVM vm object and return the object as Vm trait object
|
||||
/// Example
|
||||
/// # extern crate hypervisor;
|
||||
/// # use hypervisor::KvmHypervisor;
|
||||
/// use hypervisor::KvmVm;
|
||||
/// let hypervisor = KvmHypervisor::new().unwrap();
|
||||
/// let vm = hypervisor.create_vm().unwrap()
|
||||
///
|
||||
fn create_vm(&self) -> hypervisor::Result<Arc<dyn vm::Vm>> {
|
||||
#[allow(unused_mut)]
|
||||
let mut vm_type: u64 = 0; // Create with default platform type
|
||||
|
||||
// When KVM supports Cap::ArmVmIPASize, it is better to get the IPA
|
||||
// size from the host and use that when creating the VM, which may
|
||||
// avoid unnecessary VM creation failures.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
if self.kvm.check_extension(Cap::ArmVmIPASize) {
|
||||
vm_type = self.kvm.get_host_ipa_limit().try_into().unwrap();
|
||||
}
|
||||
|
||||
self.create_vm_with_type(vm_type)
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the size of the memory mapping required to use the vcpu's `kvm_run` structure.
|
||||
///
|
||||
fn get_vcpu_mmap_size(&self) -> hypervisor::Result<usize> {
|
||||
self.kvm
|
||||
.get_vcpu_mmap_size()
|
||||
.map_err(|e| hypervisor::HypervisorError::GetVcpuMmap(e.into()))
|
||||
}
|
||||
///
|
||||
/// Gets the recommended maximum number of VCPUs per VM.
|
||||
///
|
||||
fn get_max_vcpus(&self) -> hypervisor::Result<usize> {
|
||||
Ok(self.kvm.get_max_vcpus())
|
||||
}
|
||||
///
|
||||
/// Gets the recommended number of VCPUs per VM.
|
||||
///
|
||||
fn get_nr_vcpus(&self) -> hypervisor::Result<usize> {
|
||||
Ok(self.kvm.get_nr_vcpus())
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Checks if a particular `Cap` is available.
|
||||
///
|
||||
fn check_capability(&self, c: Cap) -> bool {
|
||||
self.kvm.check_extension(c)
|
||||
fn check_required_extensions(&self) -> hypervisor::Result<()> {
|
||||
check_required_kvm_extensions(&self.kvm)
|
||||
.map_err(|e| hypervisor::HypervisorError::CheckExtensions(e.into()))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call to get the system supported CPUID values.
|
||||
@@ -502,6 +588,7 @@ impl hypervisor::Hypervisor for KvmHypervisor {
|
||||
.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
|
||||
.map_err(|e| hypervisor::HypervisorError::GetCpuId(e.into()))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Retrieve the list of MSRs supported by KVM.
|
||||
@@ -511,6 +598,13 @@ impl hypervisor::Hypervisor for KvmHypervisor {
|
||||
.get_msr_index_list()
|
||||
.map_err(|e| hypervisor::HypervisorError::GetMsrList(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
///
|
||||
/// Retrieve AArch64 host maximum IPA size supported by KVM.
|
||||
///
|
||||
fn get_host_ipa_limit(&self) -> i32 {
|
||||
self.kvm.get_host_ipa_limit()
|
||||
}
|
||||
}
|
||||
/// Vcpu struct for KVM
|
||||
pub struct KvmVcpu {
|
||||
@@ -604,11 +698,13 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
// emulated as it will influence later which MSRs should be saved.
|
||||
self.hyperv_synic.store(true, Ordering::Release);
|
||||
|
||||
let mut cap: kvm_enable_cap = Default::default();
|
||||
cap.cap = KVM_CAP_HYPERV_SYNIC;
|
||||
let cap = kvm_enable_cap {
|
||||
cap: KVM_CAP_HYPERV_SYNIC,
|
||||
..Default::default()
|
||||
};
|
||||
self.fd
|
||||
.enable_cap(&cap)
|
||||
.map_err(|e| cpu::HypervisorCpuError::EnableHyperVSynIC(e.into()))
|
||||
.map_err(|e| cpu::HypervisorCpuError::EnableHyperVSyncIc(e.into()))
|
||||
}
|
||||
///
|
||||
/// X86 specific call to retrieve the CPUID registers.
|
||||
@@ -1039,7 +1135,7 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
fn system_registers(&self, state: &mut Vec<Register>) -> cpu::Result<()> {
|
||||
// Call KVM_GET_REG_LIST to get all registers available to the guest. For ArmV8 there are
|
||||
// around 500 registers.
|
||||
let mut reg_list = RegList::new(512);
|
||||
let mut reg_list = RegList::new(500).unwrap();
|
||||
self.fd
|
||||
.get_reg_list(&mut reg_list)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetRegList(e.into()))?;
|
||||
@@ -1173,7 +1269,7 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
let msrs = if num_msrs != expected_num_msrs {
|
||||
let mut faulty_msr_index = num_msrs;
|
||||
let mut msr_entries_tmp =
|
||||
MsrEntries::from_entries(&msr_entries.as_slice()[..faulty_msr_index]);
|
||||
MsrEntries::from_entries(&msr_entries.as_slice()[..faulty_msr_index]).unwrap();
|
||||
|
||||
loop {
|
||||
warn!(
|
||||
@@ -1183,7 +1279,7 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
|
||||
let start_pos = faulty_msr_index + 1;
|
||||
let mut sub_msr_entries =
|
||||
MsrEntries::from_entries(&msr_entries.as_slice()[start_pos..]);
|
||||
MsrEntries::from_entries(&msr_entries.as_slice()[start_pos..]).unwrap();
|
||||
let expected_num_msrs = sub_msr_entries.as_fam_struct_ref().nmsrs as usize;
|
||||
let num_msrs = self.get_msrs(&mut sub_msr_entries)?;
|
||||
|
||||
@@ -1230,12 +1326,13 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
///
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn state(&self) -> cpu::Result<CpuState> {
|
||||
let mut state = CpuState::default();
|
||||
// Get this vCPUs multiprocessing state.
|
||||
state.mp_state = self.get_mp_state()?;
|
||||
let mut state = CpuState {
|
||||
mp_state: self.get_mp_state()?,
|
||||
mpidr: self.read_mpidr()?,
|
||||
..Default::default()
|
||||
};
|
||||
self.core_registers(&mut state.core_regs)?;
|
||||
self.system_registers(&mut state.sys_regs)?;
|
||||
state.mpidr = self.read_mpidr()?;
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
@@ -1306,7 +1403,8 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
);
|
||||
|
||||
let start_pos = faulty_msr_index + 1;
|
||||
let sub_msr_entries = MsrEntries::from_entries(&state.msrs.as_slice()[start_pos..]);
|
||||
let sub_msr_entries =
|
||||
MsrEntries::from_entries(&state.msrs.as_slice()[start_pos..]).unwrap();
|
||||
let expected_num_msrs = sub_msr_entries.as_fam_struct_ref().nmsrs as usize;
|
||||
let num_msrs = self.set_msrs(&sub_msr_entries)?;
|
||||
|
||||
@@ -1333,6 +1431,15 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Initialize TDX for this CPU
|
||||
///
|
||||
#[cfg(feature = "tdx")]
|
||||
fn tdx_init(&self, hob_address: u64) -> cpu::Result<()> {
|
||||
tdx_command(&self.fd.as_raw_fd(), TdxCommand::InitVcpu, 0, hob_address)
|
||||
.map_err(cpu::HypervisorCpuError::InitializeTdx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Device struct for KVM
|
||||
|
||||
@@ -8,11 +8,10 @@
|
||||
//
|
||||
//
|
||||
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
use crate::arch::x86::{msr_index, SegmentRegisterOps, MTRR_ENABLE, MTRR_MEM_TYPE_WB};
|
||||
use crate::kvm::{Cap, Kvm, KvmError, KvmResult};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
///
|
||||
/// Export generically-named wrappers of kvm-bindings for Unix-based platforms
|
||||
@@ -95,25 +94,6 @@ impl SegmentRegisterOps for SegmentRegister {
|
||||
|
||||
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffb_d000);
|
||||
|
||||
macro_rules! msr {
|
||||
($msr:expr) => {
|
||||
MsrEntry {
|
||||
index: $msr,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! msr_data {
|
||||
($msr:expr, $data:expr) => {
|
||||
MsrEntry {
|
||||
index: $msr,
|
||||
data: $data,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn boot_msr_entries() -> MsrEntries {
|
||||
MsrEntries::from_entries(&[
|
||||
msr!(msr_index::MSR_IA32_SYSENTER_CS),
|
||||
@@ -131,6 +111,7 @@ pub fn boot_msr_entries() -> MsrEntries {
|
||||
),
|
||||
msr_data!(msr_index::MSR_MTRRdefType, MTRR_ENABLE | MTRR_MEM_TYPE_WB),
|
||||
])
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user