mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
864 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
339912a576 | ||
|
|
7ea0bf41be | ||
|
|
44f200d67d | ||
|
|
18d8c5669f | ||
|
|
57b7f35739 | ||
|
|
ed4537f4ae | ||
|
|
885412a99e | ||
|
|
128da81d83 | ||
|
|
0235bc4cee | ||
|
|
09a9cce95e | ||
|
|
ff818869dd | ||
|
|
c70f133b72 | ||
|
|
328cf39be1 | ||
|
|
64ad7657d1 | ||
|
|
cb6a53698c | ||
|
|
a91637067a | ||
|
|
88f3537b47 | ||
|
|
f557aadb67 | ||
|
|
ff6514572e | ||
|
|
11ec1e647c | ||
|
|
226816bb04 | ||
|
|
6e32c4531e | ||
|
|
9c994f882a | ||
|
|
b76d0e8b50 | ||
|
|
e151483f70 | ||
|
|
b904d666b0 | ||
|
|
0b99f96fc4 | ||
|
|
6d395c51db | ||
|
|
c4e8e653ac | ||
|
|
49e342314d | ||
|
|
0d9749282a | ||
|
|
83f3b34405 | ||
|
|
7bf0cc1ed5 | ||
|
|
fa09045f55 | ||
|
|
367201ed1e | ||
|
|
068470696f | ||
|
|
e2db476f6e | ||
|
|
07d1208dd5 | ||
|
|
4548de194d | ||
|
|
1b9ce69afa | ||
|
|
51548471fd | ||
|
|
70074ddf9f | ||
|
|
200cba0e20 | ||
|
|
5fd79571b7 | ||
|
|
3e1b0bc124 | ||
|
|
1632dbbf29 | ||
|
|
8d2e590886 | ||
|
|
e880aeed8d | ||
|
|
8bdf59bbee | ||
|
|
14edd6d9de | ||
|
|
01097aa130 | ||
|
|
9ca2c336f2 | ||
|
|
b1a1b2a622 | ||
|
|
556bda74a0 | ||
|
|
8db85eca1f | ||
|
|
b5766028a8 | ||
|
|
35757ec45a | ||
|
|
a51b48a095 | ||
|
|
d1ee623f0e | ||
|
|
a2752fe04f | ||
|
|
cc5dd8c297 | ||
|
|
2ed96cd3ed | ||
|
|
e02efe9ba0 | ||
|
|
654ac49bd6 | ||
|
|
8013e47856 | ||
|
|
7216d934de | ||
|
|
f45bbbfcac | ||
|
|
d87024a752 | ||
|
|
bb94dd18ee | ||
|
|
00e4f28e8f | ||
|
|
89661f6579 | ||
|
|
cd7eecc0b0 | ||
|
|
78ec651cbf | ||
|
|
3e54a586ce | ||
|
|
9d5c5a6410 | ||
|
|
8d072fef15 | ||
|
|
1dd1850747 | ||
|
|
0bead9ebe1 | ||
|
|
05a86d892e | ||
|
|
4d8dacec5e | ||
|
|
cc4422d58b | ||
|
|
239f422203 | ||
|
|
5636d91524 | ||
|
|
11c58870c4 | ||
|
|
b74907fa1b | ||
|
|
dbe67fca7f | ||
|
|
d3fc12b160 | ||
|
|
d73d3203dc | ||
|
|
16e1449f1e | ||
|
|
9dd1698556 | ||
|
|
d46dd4b31f | ||
|
|
0e75e7d079 | ||
|
|
be81fea46a | ||
|
|
cd010052f8 | ||
|
|
725bb8d038 | ||
|
|
ddfac7df0b | ||
|
|
6118359240 | ||
|
|
0a294bf2d7 | ||
|
|
baf10a45a9 | ||
|
|
c655e22e42 | ||
|
|
dd6ee3b39a | ||
|
|
8b651ec7ee | ||
|
|
5857d4851b | ||
|
|
0f63f8d001 | ||
|
|
2571e59438 | ||
|
|
d32e7fceb5 | ||
|
|
86655f4967 | ||
|
|
d9d1cdb0c6 | ||
|
|
ca155a790c | ||
|
|
487a43cdee | ||
|
|
96df501b7f | ||
|
|
e0e3779e96 | ||
|
|
66c40b0f02 | ||
|
|
a0c8bf4f9f | ||
|
|
02e1c54426 | ||
|
|
3d82867fe2 | ||
|
|
193950ce42 | ||
|
|
548a5234c5 | ||
|
|
bcb411b4d6 | ||
|
|
2c1772b401 | ||
|
|
363b478040 | ||
|
|
a00d29867c | ||
|
|
6ee7bfdd50 | ||
|
|
d926db5ef8 | ||
|
|
376b676910 | ||
|
|
219716c3f1 | ||
|
|
3bb26093eb | ||
|
|
06dc708515 | ||
|
|
70cfd1be67 | ||
|
|
6b79aca32d | ||
|
|
0149e65081 | ||
|
|
ecf3db5092 | ||
|
|
8cd40a7a80 | ||
|
|
c559f52fe5 | ||
|
|
f022953fff | ||
|
|
442ac9056c | ||
|
|
fe97785a9f | ||
|
|
e5bb3903ad | ||
|
|
0b6bc75d3a | ||
|
|
9da7435f06 | ||
|
|
3225c0c7c8 | ||
|
|
c03f3b54b6 | ||
|
|
ba5af92984 | ||
|
|
c74be069b5 | ||
|
|
b29d9607df | ||
|
|
c0c4c0f712 | ||
|
|
1c16428e4d | ||
|
|
1649845944 | ||
|
|
6372635195 | ||
|
|
dd09a0a890 | ||
|
|
b56e721d78 | ||
|
|
39f1c5f476 | ||
|
|
92cf2816b0 | ||
|
|
83c8c3c913 | ||
|
|
922bc0d9aa | ||
|
|
d760a77fbb | ||
|
|
ad8cdb5e85 | ||
|
|
889d06277a | ||
|
|
e3fc5c83bb | ||
|
|
06dc25cbd6 | ||
|
|
4ef388b67c | ||
|
|
5ffc6d6107 | ||
|
|
e98ea329bf | ||
|
|
081a6ebb51 | ||
|
|
ffe78c98fd | ||
|
|
741d640330 | ||
|
|
447cad3861 | ||
|
|
47fbe9af1e | ||
|
|
3f87a81e2b | ||
|
|
aeae5255c8 | ||
|
|
0039f45276 | ||
|
|
b792d4751d | ||
|
|
dc6c3a249d | ||
|
|
4c7cf634b9 | ||
|
|
69bd0036d9 | ||
|
|
87d81dd2b1 | ||
|
|
f03c3b737f | ||
|
|
f346687e3d | ||
|
|
953c238544 | ||
|
|
9ea052785e | ||
|
|
10c33ab257 | ||
|
|
14af5e1679 | ||
|
|
8ab2d5e539 | ||
|
|
63226e2b80 | ||
|
|
d0dbc7fb4d | ||
|
|
aac614e2ec | ||
|
|
9abf8d6868 | ||
|
|
6fdba7ca11 | ||
|
|
d2e42a0ed4 | ||
|
|
3e4d6a4081 | ||
|
|
7f18d0a281 | ||
|
|
99d42bb05e | ||
|
|
dbce6cb1ce | ||
|
|
cbc5e8da4a | ||
|
|
a3ddc2779d | ||
|
|
99353856ef | ||
|
|
d99c0c0d1d | ||
|
|
8e9ce3ab7c | ||
|
|
3acb988c1a | ||
|
|
ef67eab8c3 | ||
|
|
569a2a5f07 | ||
|
|
353c17eb4d | ||
|
|
3166f22751 | ||
|
|
b4376a0dd9 | ||
|
|
d813c45d81 | ||
|
|
aa13ede316 | ||
|
|
d1d9e7b241 | ||
|
|
d86e2ce34d | ||
|
|
b6d009830d | ||
|
|
2f70800e91 | ||
|
|
33f3a456c7 | ||
|
|
68ce3a9222 | ||
|
|
efb579b224 | ||
|
|
0ff6cdf5eb | ||
|
|
25c0c55bb5 | ||
|
|
6e0e632361 | ||
|
|
1c9df58440 | ||
|
|
1917e86206 | ||
|
|
787f5085b6 | ||
|
|
fec39ccf51 | ||
|
|
115085eb9e | ||
|
|
802e9009b9 | ||
|
|
37fbece885 | ||
|
|
ec6e87479f | ||
|
|
5b51024ef7 | ||
|
|
99a98f270d | ||
|
|
c0af33a4c9 | ||
|
|
0cf868b5c1 | ||
|
|
3da60dfa5e | ||
|
|
1c8a91940e | ||
|
|
8c117a8117 | ||
|
|
7d0aa1fd02 | ||
|
|
a0190b16aa | ||
|
|
1bd8eb68ff | ||
|
|
8363eddd97 | ||
|
|
70aa5bc231 | ||
|
|
b8ce1816e9 | ||
|
|
43eed4861f | ||
|
|
268c4ea214 | ||
|
|
29a0ddc58e | ||
|
|
4079e4f1ad | ||
|
|
fba0b5f93c | ||
|
|
05cc5f596c | ||
|
|
c1b33329db | ||
|
|
402a24a688 | ||
|
|
0809e7f675 | ||
|
|
a718716831 | ||
|
|
5a9dd7489c | ||
|
|
dec8d619d4 | ||
|
|
c39cdb3c1f | ||
|
|
26ae8b6d5c | ||
|
|
de31b3fadc | ||
|
|
b06ad85604 | ||
|
|
8d89736c68 | ||
|
|
55ee8eb482 | ||
|
|
1017157bb6 | ||
|
|
499989fb17 | ||
|
|
ce0f30bb54 | ||
|
|
fce202a90c | ||
|
|
95626ae564 | ||
|
|
1e1012c31b | ||
|
|
56207dc3a0 | ||
|
|
57fdaa3a39 | ||
|
|
19c5c0af9f | ||
|
|
e26e7b4d72 | ||
|
|
e8d6c9ecb0 | ||
|
|
0712027eb1 | ||
|
|
7ea5d7a416 | ||
|
|
7023d4ff84 | ||
|
|
5f31ad319e | ||
|
|
5083549afe | ||
|
|
beed5e5d6d | ||
|
|
7feb25daf0 | ||
|
|
f2574897cd | ||
|
|
2698924c0d | ||
|
|
6bd4927d11 | ||
|
|
f485922b78 | ||
|
|
1faaa2cd0a | ||
|
|
6c630f4908 | ||
|
|
3b39c41a01 | ||
|
|
309e346c4c | ||
|
|
3f6833b315 | ||
|
|
a862c804ba | ||
|
|
83914d80a2 | ||
|
|
a7aecb5eee | ||
|
|
a64d27f841 | ||
|
|
7a458d85d1 | ||
|
|
a92d852848 | ||
|
|
0664647109 | ||
|
|
f2c813e1cf | ||
|
|
f00df25d40 | ||
|
|
c016a0d4d3 | ||
|
|
5c96fbb19b | ||
|
|
b955f09b80 | ||
|
|
79ad6f8536 | ||
|
|
be4f04a906 | ||
|
|
f8415debe9 | ||
|
|
9014a5e59c | ||
|
|
612621bb46 | ||
|
|
18e1d2caf5 | ||
|
|
4be406a308 | ||
|
|
7bf2a2c382 | ||
|
|
7df80220ec | ||
|
|
84d2511de0 | ||
|
|
374263ddac | ||
|
|
7b76b6b61e | ||
|
|
e06f12673b | ||
|
|
89e658d9ff | ||
|
|
5ac68534b1 | ||
|
|
6acceaa87c | ||
|
|
a3898fd8e1 | ||
|
|
7026622c24 | ||
|
|
41e5384f15 | ||
|
|
681a30bd15 | ||
|
|
1976157761 | ||
|
|
0b1e626fe3 | ||
|
|
79bc42f3c2 | ||
|
|
eca75dcfc9 | ||
|
|
600d1664ee | ||
|
|
e6208d1c0e | ||
|
|
cf635c64d7 | ||
|
|
42ca292dce | ||
|
|
b7338c96eb | ||
|
|
bf4af14e73 | ||
|
|
24a3505f0b | ||
|
|
20f3691474 | ||
|
|
c56a3ce59a | ||
|
|
18f7a37b48 | ||
|
|
98496434b9 | ||
|
|
0b2994a6e3 | ||
|
|
553e6857bd | ||
|
|
083ce323c0 | ||
|
|
8fff4c1af3 | ||
|
|
35ecfb6ec5 | ||
|
|
a61a013311 | ||
|
|
b509fd94cd | ||
|
|
631af8a4f7 | ||
|
|
aa14fe214a | ||
|
|
45e3f49bba | ||
|
|
03db406645 | ||
|
|
ba1e89139a | ||
|
|
21d40d7489 | ||
|
|
c90a0ffff6 | ||
|
|
dc7931f592 | ||
|
|
841627f10a | ||
|
|
97fdb65012 | ||
|
|
d17d70fae1 | ||
|
|
71d1296d09 | ||
|
|
0d58e51a3a | ||
|
|
97012c511d | ||
|
|
c10e2a79b1 | ||
|
|
f3870ae638 | ||
|
|
3c0b389c82 | ||
|
|
9881c94f84 | ||
|
|
ceb8151747 | ||
|
|
2554f2a8d4 | ||
|
|
036af673e6 | ||
|
|
990362e314 | ||
|
|
83d57d3cce | ||
|
|
a0babfb9c1 | ||
|
|
3394a59466 | ||
|
|
f8e0376f6d | ||
|
|
6379074264 | ||
|
|
5dbc72948f | ||
|
|
04d111ee15 | ||
|
|
a9623c7a28 | ||
|
|
4baf85857a | ||
|
|
e3d2917d5f | ||
|
|
a84b540b65 | ||
|
|
8eb162e3d7 | ||
|
|
2804608a1c | ||
|
|
e0125653b1 | ||
|
|
e431a48201 | ||
|
|
c143cb3af0 | ||
|
|
b6feae0ace | ||
|
|
520aff2efc | ||
|
|
41880919a0 | ||
|
|
865291d840 | ||
|
|
346ee09e6b | ||
|
|
59012ccc6e | ||
|
|
3f17f4657b | ||
|
|
086ca9b935 | ||
|
|
9b724303ac | ||
|
|
948573db0d | ||
|
|
0352846af2 | ||
|
|
8b3f0043b0 | ||
|
|
9d532acab6 | ||
|
|
df2a7c1764 | ||
|
|
3f5dac988f | ||
|
|
3096f1d42f | ||
|
|
42a4e593ce | ||
|
|
b56da6ee24 | ||
|
|
90dcbc3232 | ||
|
|
f3522e85fc | ||
|
|
d3ac6a85e0 | ||
|
|
38a1b45783 | ||
|
|
e9841db486 | ||
|
|
c1f555cde3 | ||
|
|
505f4dfa53 | ||
|
|
67ad3ff1ba | ||
|
|
755cabea4c | ||
|
|
1ed4898d28 | ||
|
|
a807b91f86 | ||
|
|
f6236087d8 | ||
|
|
95f83320b1 | ||
|
|
dcc858a403 | ||
|
|
57ea412c64 | ||
|
|
e32c9525c0 | ||
|
|
2b4f60e57b | ||
|
|
5736205cf0 | ||
|
|
a8d1849485 | ||
|
|
55937f63bc | ||
|
|
426d5d6dd0 | ||
|
|
3f3d07b3dc | ||
|
|
296cb909f4 | ||
|
|
e7d52fc736 | ||
|
|
a598b935f2 | ||
|
|
1c0b6f1090 | ||
|
|
1b12790c13 | ||
|
|
bd307dcab5 | ||
|
|
83d757d89a | ||
|
|
1b8e7ac06c | ||
|
|
389e4fe8be | ||
|
|
06a9c3b839 | ||
|
|
36cf46e381 | ||
|
|
1efdea7466 | ||
|
|
e662a2e12c | ||
|
|
5496bd5dfb | ||
|
|
e655de1e39 | ||
|
|
a55a642b70 | ||
|
|
ded8959b9a | ||
|
|
53e49636ca | ||
|
|
0b0f3c69f0 | ||
|
|
6650f553bd | ||
|
|
c5bca514b9 | ||
|
|
c4106c0592 | ||
|
|
61f89e7dcc | ||
|
|
7a4be1534f | ||
|
|
cf0c4e07b6 | ||
|
|
36ec53c4a0 | ||
|
|
c7997ea4e5 | ||
|
|
b2d3aba19c | ||
|
|
9cd97bb64e | ||
|
|
868fdf9dfe | ||
|
|
12729eb034 | ||
|
|
a062401be6 | ||
|
|
09cc139b53 | ||
|
|
4635ebd4b2 | ||
|
|
ca96ff601b | ||
|
|
feb523ef78 | ||
|
|
7e47464a48 | ||
|
|
4c4f79c831 | ||
|
|
0fd701ac75 | ||
|
|
64924d5c2b | ||
|
|
9c11927305 | ||
|
|
b13d0e53c5 | ||
|
|
6001240ca1 | ||
|
|
9534604ef3 | ||
|
|
c152f9f6bc | ||
|
|
325e62372d | ||
|
|
9421884334 | ||
|
|
fcd09946b7 | ||
|
|
a20e481abe | ||
|
|
6e4374c392 | ||
|
|
44452cbe22 | ||
|
|
7b2ebd2fa5 | ||
|
|
8a0f6014ce | ||
|
|
d78da5d252 | ||
|
|
b35a94d83c | ||
|
|
961319d8ad | ||
|
|
b97f929706 | ||
|
|
b71564f284 | ||
|
|
f2be30f699 | ||
|
|
b14216e854 | ||
|
|
3498e67bda | ||
|
|
24d163b244 | ||
|
|
90697d4ae0 | ||
|
|
73c4156775 | ||
|
|
02f774824b | ||
|
|
74cd3d0dc2 | ||
|
|
85962099c7 | ||
|
|
04ee1efa70 | ||
|
|
5258a870e0 | ||
|
|
76d6d28f3e | ||
|
|
5132e3b907 | ||
|
|
599c5280ed | ||
|
|
2f649bb6ad | ||
|
|
ece0e6fa92 | ||
|
|
2593b67864 | ||
|
|
191e865261 | ||
|
|
ca3a441c49 | ||
|
|
de3ca97095 | ||
|
|
74dcb37ec3 | ||
|
|
1d55de9c74 | ||
|
|
2e94a86b31 | ||
|
|
996bdc6e08 | ||
|
|
9d18012c52 | ||
|
|
6a8bd8e8c5 | ||
|
|
d9184e5256 | ||
|
|
5e475e70e6 | ||
|
|
bd1e1677bd | ||
|
|
595c9d13a9 | ||
|
|
c2616f3f4f | ||
|
|
7ceb126184 | ||
|
|
879411e70f | ||
|
|
1181fd21fd | ||
|
|
c8967fcc37 | ||
|
|
19e893fa53 | ||
|
|
aa502b3e41 | ||
|
|
10531f052b | ||
|
|
b072eb454e | ||
|
|
b7c2a0f23f | ||
|
|
874d524a13 | ||
|
|
91e2601523 | ||
|
|
01900e3c2b | ||
|
|
e35ef40029 | ||
|
|
aa0af5f8d7 | ||
|
|
46066d6ae1 | ||
|
|
51c1738d55 | ||
|
|
0567def931 | ||
|
|
0110fb4edc | ||
|
|
4f945743cb | ||
|
|
8f6afec520 | ||
|
|
b14427540b | ||
|
|
ea4a95c4f6 | ||
|
|
f5084f7c65 | ||
|
|
45ab9f77b7 | ||
|
|
f0ea8eb70b | ||
|
|
3377e0e983 | ||
|
|
e9435b1327 | ||
|
|
09fca4a5dd | ||
|
|
1143d54ee0 | ||
|
|
934b20a77a | ||
|
|
b54ce6c3db | ||
|
|
3dd01443d5 | ||
|
|
43227cd5c4 | ||
|
|
176e438d17 | ||
|
|
c22c4675b3 | ||
|
|
7ccf58a019 | ||
|
|
48e1af7d3c | ||
|
|
341a69c39c | ||
|
|
5d34d85b92 | ||
|
|
bcd46906ec | ||
|
|
bc59ca72f3 | ||
|
|
995945dd25 | ||
|
|
8edd81ec82 | ||
|
|
726cfa41f4 | ||
|
|
d19a5d5356 | ||
|
|
69e8f60b91 | ||
|
|
c6eb69caba | ||
|
|
e4ed9bc557 | ||
|
|
f4509c3611 | ||
|
|
ecbb8ab282 | ||
|
|
29ebecccb0 | ||
|
|
5646a917ff | ||
|
|
6e22f23831 | ||
|
|
8e996ff2fe | ||
|
|
2d2f356d94 | ||
|
|
938c16f2b2 | ||
|
|
105a7dd7aa | ||
|
|
11ef495e6b | ||
|
|
34b3170680 | ||
|
|
427a2bacf5 | ||
|
|
1325c76525 | ||
|
|
4a51a6615f | ||
|
|
d5ce855649 | ||
|
|
496f932276 | ||
|
|
c33efe294a | ||
|
|
2d022d0c85 | ||
|
|
5b31b19530 | ||
|
|
ad202f9b7a | ||
|
|
3a225aaa23 | ||
|
|
1bfa07f48e | ||
|
|
8e682bcb00 | ||
|
|
1ee2922dbc | ||
|
|
2ce503f457 | ||
|
|
574576c8e9 | ||
|
|
aaf67c9ae4 | ||
|
|
8b9da4e286 | ||
|
|
271f11d9ec | ||
|
|
e436b382cc | ||
|
|
1ce1fe7334 | ||
|
|
73af65f417 | ||
|
|
cffde0ff65 | ||
|
|
e41b7d90d5 | ||
|
|
60425471dd | ||
|
|
2b76e1d7ba | ||
|
|
15ace525be | ||
|
|
8db630763a | ||
|
|
cd1470e289 | ||
|
|
99d8c34861 | ||
|
|
6725771dc3 | ||
|
|
0e8fd0bc17 | ||
|
|
0ef1e668a5 | ||
|
|
477332a94a | ||
|
|
7b102c4fa3 | ||
|
|
1e159dbefb | ||
|
|
a4ef6e57a7 | ||
|
|
ec45daac19 | ||
|
|
c773330a65 | ||
|
|
e4ae668bcd | ||
|
|
f6c058da56 | ||
|
|
089dffcb49 | ||
|
|
9d7034b9c6 | ||
|
|
a86edd3032 | ||
|
|
7805fe58e2 | ||
|
|
e66ffffd43 | ||
|
|
843c8ad51c | ||
|
|
a334831d54 | ||
|
|
dab9f1cf30 | ||
|
|
3a81f9328f | ||
|
|
e2886a4b1f | ||
|
|
c6d3f61a42 | ||
|
|
4689abd9f4 | ||
|
|
f479f0605d | ||
|
|
5034bea8c4 | ||
|
|
b8172408f8 | ||
|
|
b0a2ce0598 | ||
|
|
a0922930b1 | ||
|
|
d1ec65873e | ||
|
|
54e9ed9932 | ||
|
|
111225a2a5 | ||
|
|
fe49056129 | ||
|
|
d5558aea2a | ||
|
|
1ba995d952 | ||
|
|
c6c081099f | ||
|
|
3a1254a9b2 | ||
|
|
6e9172bf6f | ||
|
|
22cf8c97e5 | ||
|
|
9e6301be70 | ||
|
|
547230bb77 | ||
|
|
ef7e177df2 | ||
|
|
375e094c77 | ||
|
|
3df82337f1 | ||
|
|
0e273e8b0d | ||
|
|
ad6c0ee52b | ||
|
|
b1f6a59579 | ||
|
|
3494080e2f | ||
|
|
06e583c9ab | ||
|
|
7d8f795430 | ||
|
|
f8c6d7fe37 | ||
|
|
0dc122a9a9 | ||
|
|
e2ea9e2821 | ||
|
|
4e3bc20f2c | ||
|
|
e661139e1e | ||
|
|
c99a5fc7a5 | ||
|
|
5b2adb398a | ||
|
|
db5582f7d1 | ||
|
|
ce51755109 | ||
|
|
67ff0d7819 | ||
|
|
e7a51cde78 | ||
|
|
9ba06ce5f5 | ||
|
|
0e7d5d2761 | ||
|
|
ba9554389b | ||
|
|
c89b8e061f | ||
|
|
c2450bd055 | ||
|
|
bae9799259 | ||
|
|
3dd487c2cf | ||
|
|
eb87538100 | ||
|
|
9e8296b696 | ||
|
|
0dc3282ae0 | ||
|
|
67340c5295 | ||
|
|
160ea00d6b | ||
|
|
0389190c64 | ||
|
|
998fb48ff2 | ||
|
|
03d8a3ebcf | ||
|
|
08e4d1f481 | ||
|
|
d50a4cbb6b | ||
|
|
28eeb8a492 | ||
|
|
ec3520d8b2 | ||
|
|
19661d36af | ||
|
|
9747cd1059 | ||
|
|
0bdc7dcc1c | ||
|
|
e16817ea14 | ||
|
|
3de0a6d401 | ||
|
|
bb9c5e5faf | ||
|
|
0036302fc9 | ||
|
|
b4ec59cb0c | ||
|
|
8511603fdb | ||
|
|
df6e17840a | ||
|
|
fdb2d673d3 | ||
|
|
2d828121a6 | ||
|
|
55a8d3a0ea | ||
|
|
8e373f52a9 | ||
|
|
6eb0330bed | ||
|
|
c4c384bd1e | ||
|
|
180cc12b46 | ||
|
|
ccb76fc19f | ||
|
|
5668d7dfb7 | ||
|
|
7ca2a771c2 | ||
|
|
09abb5b709 | ||
|
|
7bac36bea4 | ||
|
|
0f5702f297 | ||
|
|
8914ce9da8 | ||
|
|
1f191d8130 | ||
|
|
129416de5c | ||
|
|
b3980f581b | ||
|
|
3a232ef31a | ||
|
|
cd83d258b8 | ||
|
|
1b0f35e42d | ||
|
|
85f818421f | ||
|
|
0031dacd7b | ||
|
|
2f855c9a67 | ||
|
|
b7e7455b49 | ||
|
|
795f2a5558 | ||
|
|
2b92a2778b | ||
|
|
cbe988d33e | ||
|
|
5e52729453 | ||
|
|
8b59316718 | ||
|
|
6e8972372e | ||
|
|
748ba1f50d | ||
|
|
a48d7c281e | ||
|
|
aea1f7743b | ||
|
|
ab8e559343 | ||
|
|
1b314cff5b | ||
|
|
683d116416 | ||
|
|
b93d50582d | ||
|
|
de4cd49c6d | ||
|
|
709545920e | ||
|
|
2b11966faa | ||
|
|
6835dfa5e9 | ||
|
|
d15e1f1a13 | ||
|
|
8ab15b9a98 | ||
|
|
287ec367d6 | ||
|
|
9fb0274479 | ||
|
|
51307dd509 | ||
|
|
de06bf4aed | ||
|
|
32ded2c72b | ||
|
|
8ecce8876e | ||
|
|
3931b99d4e | ||
|
|
4ae6b595d7 | ||
|
|
748018ace3 | ||
|
|
426ee39972 | ||
|
|
1d1043316b | ||
|
|
4517b76a23 | ||
|
|
1b32e2f8b2 | ||
|
|
5b3bcfa233 | ||
|
|
0489b6314e | ||
|
|
2c367bdde8 | ||
|
|
4f3f36fe5f | ||
|
|
4b08142117 | ||
|
|
b3e3a5fdd7 | ||
|
|
a1c6ef8385 | ||
|
|
16d0906dfd | ||
|
|
3e9ce6012c | ||
|
|
389264351e | ||
|
|
00becda899 | ||
|
|
fe5bde236a | ||
|
|
80cc2b6ef8 | ||
|
|
c37dadcc9a | ||
|
|
cefbf6b4a3 | ||
|
|
31209474b3 | ||
|
|
0e09b537e3 | ||
|
|
ca6d338ffa | ||
|
|
166b73e106 | ||
|
|
9398a0bd80 | ||
|
|
55f7df2e21 | ||
|
|
7fb1280666 | ||
|
|
bfa31f9c56 | ||
|
|
725e388684 | ||
|
|
c5eac2e822 | ||
|
|
c7b22156da | ||
|
|
e5e5a89e65 | ||
|
|
d98f2618bd | ||
|
|
2e01bf7f74 | ||
|
|
a4160c1fef | ||
|
|
d0c53a5357 | ||
|
|
3888f57600 | ||
|
|
7fd2022e8e | ||
|
|
1b6dd597b2 | ||
|
|
ad817f19b5 | ||
|
|
a8839c4a4e | ||
|
|
b173f6f654 | ||
|
|
def1d7cf86 | ||
|
|
e8c6d83f3f | ||
|
|
1c36065754 | ||
|
|
bccfa81368 | ||
|
|
a6959a7469 | ||
|
|
4487c8376b | ||
|
|
b62a40efae | ||
|
|
8f1e03fcf4 | ||
|
|
5ee68b1ee3 | ||
|
|
e2e02c8f69 | ||
|
|
ec94ae31ee | ||
|
|
83ab5ea528 | ||
|
|
30a7a8033e | ||
|
|
fc43a50f34 | ||
|
|
105c6cfc5a | ||
|
|
ee5792c0bb | ||
|
|
30e421d2e5 | ||
|
|
90b5014a50 | ||
|
|
cc3706afe1 | ||
|
|
1eac37bd5f | ||
|
|
d6bf1f5eb0 | ||
|
|
559faa272a | ||
|
|
6f8bd27cf7 | ||
|
|
47a7ebe434 | ||
|
|
1fd5384062 | ||
|
|
81862e8ed3 | ||
|
|
9fbf52b998 | ||
|
|
0bd910e8b0 | ||
|
|
ef92e55998 | ||
|
|
9f7ccb34cd | ||
|
|
e23f4e0783 | ||
|
|
6a29eba69e | ||
|
|
98e10062e8 | ||
|
|
c95b82d975 | ||
|
|
d83fc7f663 | ||
|
|
e3a5102f85 | ||
|
|
ea39fcca3f | ||
|
|
7e10b236d4 | ||
|
|
405b4a6829 | ||
|
|
a50b3784fe | ||
|
|
eae8043890 | ||
|
|
8868c9c950 | ||
|
|
7d16c74020 | ||
|
|
86e7f07485 | ||
|
|
578780ed0c | ||
|
|
ec01062ada | ||
|
|
d828fcd4ed | ||
|
|
d330f5389e | ||
|
|
e37ec26ccf | ||
|
|
4d9a2b17a7 | ||
|
|
7c3110e6d5 | ||
|
|
1c25d71127 | ||
|
|
8724ac1fe6 | ||
|
|
1ff0191b30 | ||
|
|
464ead57ec | ||
|
|
cee2d6627d | ||
|
|
bfeab9f69f | ||
|
|
3504947f0e | ||
|
|
d2442d39a8 | ||
|
|
1e87a409c7 | ||
|
|
1a2185ea96 | ||
|
|
8cea5db955 | ||
|
|
3ca6c29e28 | ||
|
|
d05586f520 | ||
|
|
d5f294b326 | ||
|
|
145df4b689 | ||
|
|
c45d24df16 | ||
|
|
f110029acf | ||
|
|
8a7f4b47cb | ||
|
|
c5bd8cabc4 | ||
|
|
d1b9d9d7f7 | ||
|
|
9806d83e7c | ||
|
|
f16b57716d | ||
|
|
3edf12accf | ||
|
|
7f2723d9c6 | ||
|
|
6c89c541da | ||
|
|
d272113d0c | ||
|
|
93883681be | ||
|
|
ca02a69fdf | ||
|
|
c4ffdad4bc | ||
|
|
2a5c248f32 | ||
|
|
926d149881 | ||
|
|
7f4b5dfae5 | ||
|
|
58b7057c7f | ||
|
|
b1efa5b26b | ||
|
|
b9ad3eda27 | ||
|
|
1d9050dbe3 | ||
|
|
3d08a0fba9 | ||
|
|
d274fe9cb8 | ||
|
|
cd24f470f4 | ||
|
|
2e22f950b7 |
20
.github/workflows/build.yaml
vendored
20
.github/workflows/build.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
- nightly
|
- nightly
|
||||||
- "1.60"
|
- "1.64"
|
||||||
target:
|
target:
|
||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
- x86_64-unknown-linux-musl
|
- x86_64-unknown-linux-musl
|
||||||
@@ -34,22 +34,28 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
|
|
||||||
- name: Build (default features)
|
- name: Build (default features)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings
|
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Build (kvm)
|
- name: Build (kvm)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings
|
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Build (default features + tdx)
|
- name: Build (default features + tdx)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings
|
run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
|
- name: Build (default features + dbus_api)
|
||||||
|
run: cargo rustc --locked --bin cloud-hypervisor --features "dbus_api" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Build (default features + guest_debug)
|
- name: Build (default features + guest_debug)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings
|
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Build (mshv)
|
- name: Build (mshv)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings
|
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
|
- name: Build (sev_snp)
|
||||||
|
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Build (mshv + kvm)
|
- name: Build (mshv + kvm)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings
|
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Release Build (default features)
|
- name: Release Build (default features)
|
||||||
run: cargo build --locked --all --release --target=${{ matrix.target }}
|
run: cargo build --locked --all --release --target=${{ matrix.target }}
|
||||||
|
|||||||
20
.github/workflows/docker-image.yaml
vendored
20
.github/workflows/docker-image.yaml
vendored
@@ -7,6 +7,10 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths: resources/Dockerfile
|
paths: resources/Dockerfile
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -20,19 +24,19 @@ jobs:
|
|||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to ghcr
|
||||||
if: ${{ github.event_name == 'push' }}
|
uses: docker/login-action@v2
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
registry: ${{ env.REGISTRY }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v3
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
# list of Docker images to use as base name for tags
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
images: cloudhypervisor/dev
|
|
||||||
# generate Docker tags based on the following events/attributes
|
# generate Docker tags based on the following events/attributes
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value={{date 'YYYYMMDD'}}-0
|
type=raw,value={{date 'YYYYMMDD'}}-0
|
||||||
|
|||||||
26
.github/workflows/hadolint.yaml
vendored
Normal file
26
.github/workflows/hadolint.yaml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Lint Dockerfile
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- resources/Dockerfile
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- resources/Dockerfile
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
hadolint:
|
||||||
|
name: Run Hadolint Dockerfile Linter
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Lint Dockerfile
|
||||||
|
uses: hadolint/hadolint-action@master
|
||||||
|
with:
|
||||||
|
dockerfile: ./resources/Dockerfile
|
||||||
|
format: tty
|
||||||
|
no-fail: false
|
||||||
|
verbose: true
|
||||||
|
failure-threshold: info
|
||||||
46
.github/workflows/quality.yaml
vendored
46
.github/workflows/quality.yaml
vendored
@@ -13,15 +13,24 @@ jobs:
|
|||||||
rust:
|
rust:
|
||||||
- stable
|
- stable
|
||||||
target:
|
target:
|
||||||
- x86_64-unknown-linux-gnu
|
|
||||||
- aarch64-unknown-linux-gnu
|
- aarch64-unknown-linux-gnu
|
||||||
|
- aarch64-unknown-linux-musl
|
||||||
|
- x86_64-unknown-linux-gnu
|
||||||
|
- x86_64-unknown-linux-musl
|
||||||
|
|
||||||
experimental: [false]
|
experimental: [false]
|
||||||
include:
|
include:
|
||||||
|
- rust: beta
|
||||||
|
target: aarch64-unknown-linux-gnu
|
||||||
|
experimental: true
|
||||||
|
- rust: beta
|
||||||
|
target: aarch64-unknown-linux-musl
|
||||||
|
experimental: true
|
||||||
- rust: beta
|
- rust: beta
|
||||||
target: x86_64-unknown-linux-gnu
|
target: x86_64-unknown-linux-gnu
|
||||||
experimental: true
|
experimental: true
|
||||||
- rust: beta
|
- rust: beta
|
||||||
target: aarch64-unknown-linux-gnu
|
target: x86_64-unknown-linux-musl
|
||||||
experimental: true
|
experimental: true
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
@@ -42,7 +51,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
commits=$(git rev-list origin/${{ github.base_ref }}..${{ github.sha }})
|
commits=$(git rev-list origin/${{ github.base_ref }}..${{ github.sha }})
|
||||||
for commit in $commits; do git checkout $commit; cargo check --tests --all --target=${{ matrix.target }}; done
|
for commit in $commits; do git checkout $commit; cargo check --tests --examples --all --target=${{ matrix.target }}; done
|
||||||
git checkout ${{ github.sha }}
|
git checkout ${{ github.sha }}
|
||||||
|
|
||||||
- name: Formatting (rustfmt)
|
- name: Formatting (rustfmt)
|
||||||
@@ -53,28 +62,28 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --locked --all --all-targets --no-default-features --tests --features "kvm" -- -D warnings
|
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Clippy (default features)
|
- name: Clippy (default features)
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --locked --all --all-targets --tests -- -D warnings
|
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Clippy (default features + guest_debug)
|
- name: Clippy (default features + guest_debug)
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --locked --all --all-targets --tests --features "guest_debug" -- -D warnings
|
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Clippy (default features + tracing)
|
- name: Clippy (default features + tracing)
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --locked --all --all-targets --tests --features "tracing" -- -D warnings
|
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Clippy (mshv)
|
- name: Clippy (mshv)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
@@ -82,7 +91,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --locked --all --all-targets --no-default-features --tests --features "mshv" -- -D warnings
|
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Clippy (mshv + kvm)
|
- name: Clippy (mshv + kvm)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
@@ -90,7 +99,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --locked --all --all-targets --no-default-features --tests --features "mshv,kvm" -- -D warnings
|
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
|
- name: Clippy (sev_snp)
|
||||||
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
|
command: clippy
|
||||||
|
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Clippy (kvm + tdx)
|
- name: Clippy (kvm + tdx)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
@@ -98,7 +115,16 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --locked --all --all-targets --no-default-features --tests --features "tdx,kvm" -- -D warnings
|
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Check build did not modify any files
|
- name: Check build did not modify any files
|
||||||
run: test -z "$(git status --porcelain)"
|
run: test -z "$(git status --porcelain)"
|
||||||
|
|
||||||
|
typos:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
name: Typos / Spellcheck
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
# Executes "typos ."
|
||||||
|
- uses: crate-ci/typos@v1.16.11
|
||||||
|
|||||||
98
.github/workflows/release.yaml
vendored
98
.github/workflows/release.yaml
vendored
@@ -1,62 +1,48 @@
|
|||||||
name: Cloud Hypervisor Release
|
name: Cloud Hypervisor Release
|
||||||
on: [create]
|
on: [pull_request, create]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
if: (github.event_name == 'create' && github.event.ref_type == 'tag') || github.event_name == 'pull_request'
|
||||||
name: Release
|
name: Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Install musl-gcc
|
- name: Install musl-gcc
|
||||||
run: sudo apt install -y musl-tools
|
run: sudo apt install -y musl-tools
|
||||||
- name: Create release directory
|
- name: Create release directory
|
||||||
run: rsync -rv --exclude=.git . ../cloud-hypervisor-${{ github.event.ref }}
|
run: rsync -rv --exclude=.git . ../cloud-hypervisor-${{ github.event.ref }}
|
||||||
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
|
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: "1.62"
|
toolchain: "1.67.1"
|
||||||
target: x86_64-unknown-linux-gnu
|
target: x86_64-unknown-linux-gnu
|
||||||
- name: Install Rust toolchain (x86_64-unknown-linux-musl)
|
- name: Install Rust toolchain (x86_64-unknown-linux-musl)
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: "1.62"
|
toolchain: "1.67.1"
|
||||||
target: x86_64-unknown-linux-musl
|
target: x86_64-unknown-linux-musl
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
toolchain: "1.62"
|
toolchain: "1.67.1"
|
||||||
command: build
|
command: build
|
||||||
args: --all --release --target=x86_64-unknown-linux-gnu
|
args: --all --release --features mshv --target=x86_64-unknown-linux-gnu
|
||||||
- name: Static Build
|
- name: Static Build
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
toolchain: "1.62"
|
toolchain: "1.67.1"
|
||||||
command: build
|
command: build
|
||||||
args: --all --release --target=x86_64-unknown-linux-musl
|
args: --all --release --features mshv --target=x86_64-unknown-linux-musl
|
||||||
- name: Strip cloud-hypervisor binaries
|
|
||||||
run: strip target/*/release/cloud-hypervisor
|
|
||||||
- name: Install Rust toolchain (aarch64-unknown-linux-musl)
|
- name: Install Rust toolchain (aarch64-unknown-linux-musl)
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: "1.62"
|
toolchain: "1.67.1"
|
||||||
target: aarch64-unknown-linux-musl
|
target: aarch64-unknown-linux-musl
|
||||||
override: true
|
override: true
|
||||||
- name: Static Build (AArch64)
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
use-cross: true
|
|
||||||
command: build
|
|
||||||
args: --all --release --target=aarch64-unknown-linux-musl
|
|
||||||
- name: Vendor
|
|
||||||
working-directory: ../cloud-hypervisor-${{ github.event.ref }}
|
|
||||||
run: |
|
|
||||||
mkdir ../vendor-cargo-home
|
|
||||||
export CARGO_HOME=$(realpath ../vendor-cargo-home)
|
|
||||||
mkdir .cargo
|
|
||||||
cargo vendor > .cargo/config.toml
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: actions/create-release@v1
|
uses: actions/create-release@v1
|
||||||
env:
|
env:
|
||||||
@@ -66,20 +52,8 @@ jobs:
|
|||||||
release_name: ${{ github.ref }}
|
release_name: ${{ github.ref }}
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: true
|
prerelease: true
|
||||||
- name: Create vendored source archive
|
|
||||||
working-directory: ../
|
|
||||||
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz cloud-hypervisor-${{ github.event.ref }}
|
|
||||||
- name: Upload cloud-hypervisor vendored source archive
|
|
||||||
id: upload-release-cloud-hypervisor-vendored-sources
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
asset_path: ../cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
|
||||||
asset_name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
|
||||||
asset_content_type: application/x-xz
|
|
||||||
- name: Upload cloud-hypervisor
|
- name: Upload cloud-hypervisor
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
id: upload-release-cloud-hypervisor
|
id: upload-release-cloud-hypervisor
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
@@ -90,6 +64,7 @@ jobs:
|
|||||||
asset_name: cloud-hypervisor
|
asset_name: cloud-hypervisor
|
||||||
asset_content_type: application/octet-stream
|
asset_content_type: application/octet-stream
|
||||||
- name: Upload static cloud-hypervisor
|
- name: Upload static cloud-hypervisor
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
id: upload-release-static-cloud-hypervisor
|
id: upload-release-static-cloud-hypervisor
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
@@ -100,6 +75,7 @@ jobs:
|
|||||||
asset_name: cloud-hypervisor-static
|
asset_name: cloud-hypervisor-static
|
||||||
asset_content_type: application/octet-stream
|
asset_content_type: application/octet-stream
|
||||||
- name: Upload ch-remote
|
- name: Upload ch-remote
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
id: upload-release-ch-remote
|
id: upload-release-ch-remote
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
@@ -110,6 +86,7 @@ jobs:
|
|||||||
asset_name: ch-remote
|
asset_name: ch-remote
|
||||||
asset_content_type: application/octet-stream
|
asset_content_type: application/octet-stream
|
||||||
- name: Upload static-ch-remote
|
- name: Upload static-ch-remote
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
id: upload-release-static-ch-remote
|
id: upload-release-static-ch-remote
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
@@ -119,7 +96,18 @@ jobs:
|
|||||||
asset_path: target/x86_64-unknown-linux-musl/release/ch-remote
|
asset_path: target/x86_64-unknown-linux-musl/release/ch-remote
|
||||||
asset_name: ch-remote-static
|
asset_name: ch-remote-static
|
||||||
asset_content_type: application/octet-stream
|
asset_content_type: application/octet-stream
|
||||||
|
- name: Clean build tree ahead of cross build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: clean
|
||||||
|
- name: Static Build (AArch64)
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: true
|
||||||
|
command: build
|
||||||
|
args: --all --release --target=aarch64-unknown-linux-musl
|
||||||
- name: Upload static AArch64 cloud-hypervisor
|
- name: Upload static AArch64 cloud-hypervisor
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
id: upload-release-static-aarch64-cloud-hypervisor
|
id: upload-release-static-aarch64-cloud-hypervisor
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
@@ -130,6 +118,7 @@ jobs:
|
|||||||
asset_name: cloud-hypervisor-static-aarch64
|
asset_name: cloud-hypervisor-static-aarch64
|
||||||
asset_content_type: application/octet-stream
|
asset_content_type: application/octet-stream
|
||||||
- name: Upload static AArch64 ch-remote
|
- name: Upload static AArch64 ch-remote
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
id: upload-release-static-aarch64-ch-remote
|
id: upload-release-static-aarch64-ch-remote
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
@@ -139,3 +128,24 @@ jobs:
|
|||||||
asset_path: target/aarch64-unknown-linux-musl/release/ch-remote
|
asset_path: target/aarch64-unknown-linux-musl/release/ch-remote
|
||||||
asset_name: ch-remote-static-aarch64
|
asset_name: ch-remote-static-aarch64
|
||||||
asset_content_type: application/octet-stream
|
asset_content_type: application/octet-stream
|
||||||
|
- name: Vendor
|
||||||
|
working-directory: ../cloud-hypervisor-${{ github.event.ref }}
|
||||||
|
run: |
|
||||||
|
mkdir ../vendor-cargo-home
|
||||||
|
export CARGO_HOME=$(realpath ../vendor-cargo-home)
|
||||||
|
mkdir .cargo
|
||||||
|
cargo vendor > .cargo/config.toml
|
||||||
|
- name: Create vendored source archive
|
||||||
|
working-directory: ../
|
||||||
|
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz cloud-hypervisor-${{ github.event.ref }}
|
||||||
|
- name: Upload cloud-hypervisor vendored source archive
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
id: upload-release-cloud-hypervisor-vendored-sources
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: ../cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||||
|
asset_name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||||
|
asset_content_type: application/x-xz
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@
|
|||||||
**/Cargo.lock
|
**/Cargo.lock
|
||||||
**/rusty-tags.vi
|
**/rusty-tags.vi
|
||||||
/rpm/SOURCES
|
/rpm/SOURCES
|
||||||
|
/.vscode
|
||||||
|
|||||||
21
.typos.toml
Normal file
21
.typos.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Configuration for https://github.com/crate-ci/typos
|
||||||
|
|
||||||
|
[files]
|
||||||
|
extend-exclude = [
|
||||||
|
"hypervisor/src/kvm/x86_64/mod.rs",
|
||||||
|
"resources/linux-config-*",
|
||||||
|
]
|
||||||
|
|
||||||
|
[default.extend-words]
|
||||||
|
ba = "ba"
|
||||||
|
CLASSE = "CLASSE"
|
||||||
|
conectix = "conectix"
|
||||||
|
Dake = "Dake"
|
||||||
|
EXTINT = "EXTINT"
|
||||||
|
INOUT = "INOUT"
|
||||||
|
liness = "liness"
|
||||||
|
outout = "outout"
|
||||||
|
|
||||||
|
[default.extend-identifiers]
|
||||||
|
fo = "fo"
|
||||||
|
fpr = "fpr"
|
||||||
@@ -5,7 +5,7 @@ License](https://opensource.org/licenses/Apache-2.0) and the [BSD 3
|
|||||||
Clause](https://opensource.org/licenses/BSD-3-Clause) license. Individual files
|
Clause](https://opensource.org/licenses/BSD-3-Clause) license. Individual files
|
||||||
contain details of their licensing and changes to that file are under the same
|
contain details of their licensing and changes to that file are under the same
|
||||||
license unless the contribution changes the license of the file. When importing
|
license unless the contribution changes the license of the file. When importing
|
||||||
code from a third party project (e.g. Firecracker or CrosVM) please respect the
|
code from a third party project (e.g. Firecracker or crosvm) please respect the
|
||||||
license of those projects.
|
license of those projects.
|
||||||
|
|
||||||
New code should be under the [Apache v2
|
New code should be under the [Apache v2
|
||||||
|
|||||||
1772
Cargo.lock
generated
1772
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
59
Cargo.toml
59
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cloud-hypervisor"
|
name = "cloud-hypervisor"
|
||||||
version = "28.0.0"
|
version = "35.0.0"
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "cloud-hypervisor"
|
default-run = "cloud-hypervisor"
|
||||||
@@ -10,65 +10,77 @@ description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM
|
|||||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||||
# Minimum buildable version:
|
# Minimum buildable version:
|
||||||
# Keep in sync with version in .github/workflows/build.yaml
|
# Keep in sync with version in .github/workflows/build.yaml
|
||||||
rust-version = "1.60"
|
# Policy on MSRV (see #4318):
|
||||||
|
# Can only be bumped by:
|
||||||
|
# a.) A dependency requires it,
|
||||||
|
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
||||||
|
# c.) There is a security issue that is addressed by the toolchain update.
|
||||||
|
rust-version = "1.64"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
|
strip = true
|
||||||
|
|
||||||
|
[profile.profiling]
|
||||||
|
inherits = "release"
|
||||||
|
strip = false
|
||||||
|
debug = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.66"
|
anyhow = "1.0.75"
|
||||||
api_client = { path = "api_client" }
|
api_client = { path = "api_client" }
|
||||||
clap = { version = "4.0.18", features = ["wrap_help","cargo","string"] }
|
argh = "0.1.9"
|
||||||
epoll = "4.3.1"
|
dhat = { version = "0.3.2", optional = true }
|
||||||
|
epoll = "4.3.3"
|
||||||
event_monitor = { path = "event_monitor" }
|
event_monitor = { path = "event_monitor" }
|
||||||
hypervisor = { path = "hypervisor" }
|
hypervisor = { path = "hypervisor" }
|
||||||
libc = "0.2.137"
|
libc = "0.2.147"
|
||||||
log = { version = "0.4.17", features = ["std"] }
|
log = { version = "0.4.17", features = ["std"] }
|
||||||
option_parser = { path = "option_parser" }
|
option_parser = { path = "option_parser" }
|
||||||
seccompiler = "0.3.0"
|
seccompiler = "0.3.0"
|
||||||
serde_json = "1.0.87"
|
serde_json = "1.0.107"
|
||||||
signal-hook = "0.3.14"
|
signal-hook = "0.3.17"
|
||||||
thiserror = "1.0.37"
|
thiserror = "1.0.40"
|
||||||
tpm = { path = "tpm"}
|
tpm = { path = "tpm"}
|
||||||
tracer = { path = "tracer" }
|
tracer = { path = "tracer" }
|
||||||
vmm = { path = "vmm" }
|
vmm = { path = "vmm" }
|
||||||
vmm-sys-util = "0.10.0"
|
vmm-sys-util = "0.11.0"
|
||||||
vm-memory = "0.9.0"
|
vm-memory = "0.12.2"
|
||||||
|
zbus = { version = "3.11.1", optional = true }
|
||||||
[build-dependencies]
|
|
||||||
clap = { version = "4.0.18", features = ["cargo"] }
|
|
||||||
|
|
||||||
# List of patched crates
|
# List of patched crates
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.5.0-tdx" }
|
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.6.0-tdx" }
|
||||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "main" }
|
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "main" }
|
||||||
versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch" }
|
versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch" }
|
||||||
vhost = { git = "https://github.com/rust-vmm/vhost", branch = "main" }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dirs = "4.0.0"
|
dirs = "5.0.0"
|
||||||
net_util = { path = "net_util" }
|
net_util = { path = "net_util" }
|
||||||
once_cell = "1.16.0"
|
once_cell = "1.18.0"
|
||||||
serde_json = "1.0.87"
|
serde_json = "1.0.107"
|
||||||
test_infra = { path = "test_infra" }
|
test_infra = { path = "test_infra" }
|
||||||
wait-timeout = "0.2.0"
|
wait-timeout = "0.2.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["kvm"]
|
default = ["kvm", "io_uring"]
|
||||||
|
dbus_api = ["zbus", "vmm/dbus_api"]
|
||||||
|
dhat-heap = ["dhat"] # For heap profiling
|
||||||
guest_debug = ["vmm/guest_debug"]
|
guest_debug = ["vmm/guest_debug"]
|
||||||
|
io_uring = ["vmm/io_uring"]
|
||||||
kvm = ["vmm/kvm"]
|
kvm = ["vmm/kvm"]
|
||||||
mshv = ["vmm/mshv"]
|
mshv = ["vmm/mshv"]
|
||||||
|
sev_snp = ["vmm/sev_snp", "mshv"]
|
||||||
tdx = ["vmm/tdx"]
|
tdx = ["vmm/tdx"]
|
||||||
tracing = ["vmm/tracing", "tracer/tracing"]
|
tracing = ["vmm/tracing", "tracer/tracing"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"acpi_tables",
|
|
||||||
"api_client",
|
"api_client",
|
||||||
"arch",
|
"arch",
|
||||||
"block_util",
|
"block",
|
||||||
"devices",
|
"devices",
|
||||||
"event_monitor",
|
"event_monitor",
|
||||||
"hypervisor",
|
"hypervisor",
|
||||||
@@ -77,13 +89,10 @@ members = [
|
|||||||
"option_parser",
|
"option_parser",
|
||||||
"pci",
|
"pci",
|
||||||
"performance-metrics",
|
"performance-metrics",
|
||||||
"qcow",
|
|
||||||
"rate_limiter",
|
"rate_limiter",
|
||||||
"serial_buffer",
|
"serial_buffer",
|
||||||
"test_infra",
|
"test_infra",
|
||||||
"tracer",
|
"tracer",
|
||||||
"vfio_user",
|
|
||||||
"vhdx",
|
|
||||||
"vhost_user_block",
|
"vhost_user_block",
|
||||||
"vhost_user_net",
|
"vhost_user_net",
|
||||||
"virtio-devices",
|
"virtio-devices",
|
||||||
|
|||||||
217
Jenkinsfile
vendored
217
Jenkinsfile
vendored
@@ -1,6 +1,9 @@
|
|||||||
def runWorkers = true
|
def runWorkers = true
|
||||||
pipeline {
|
pipeline {
|
||||||
agent none
|
agent none
|
||||||
|
options {
|
||||||
|
timeout(time: 4, unit: 'HOURS')
|
||||||
|
}
|
||||||
stages {
|
stages {
|
||||||
stage('Early checks') {
|
stage('Early checks') {
|
||||||
agent { node { label 'built-in' } }
|
agent { node { label 'built-in' } }
|
||||||
@@ -10,29 +13,16 @@ pipeline {
|
|||||||
checkout scm
|
checkout scm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Check for documentation only changes') {
|
stage('Check if worker build can be skipped') {
|
||||||
when {
|
when {
|
||||||
expression {
|
expression {
|
||||||
return docsFileOnly()
|
return skipWorkerBuild()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
runWorkers = false
|
runWorkers = false
|
||||||
echo 'Documentation only changes, no need to run the CI'
|
echo 'No changes requiring a build'
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Check for fuzzer files only changes') {
|
|
||||||
when {
|
|
||||||
expression {
|
|
||||||
return fuzzFileOnly()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
runWorkers = false
|
|
||||||
echo 'Fuzzer cargo files only changes, no need to run the CI'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,6 +117,63 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stage('Worker build - AMD') {
|
||||||
|
agent { node { label 'jammy-amd' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Prepare environment') {
|
||||||
|
steps {
|
||||||
|
sh 'scripts/prepare_vdpa.sh'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'sudo modprobe openvswitch'
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration -- -- --skip common_parallel::test_vfio'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run live-migration integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'sudo modprobe openvswitch'
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-live-migration'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'sudo modprobe openvswitch'
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration --libc musl -- -- --skip common_parallel::test_vfio'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run live-migration integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'sudo modprobe openvswitch'
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-live-migration --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
stage('AArch64 worker build') {
|
stage('AArch64 worker build') {
|
||||||
agent { node { label 'bionic-arm64' } }
|
agent { node { label 'bionic-arm64' } }
|
||||||
when {
|
when {
|
||||||
@@ -160,7 +207,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
stage('Install azure-cli') {
|
stage('Install azure-cli') {
|
||||||
steps {
|
steps {
|
||||||
installAzureCli('bionic', 'arm64')
|
installAzureCli('focal', 'arm64')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Download Windows image') {
|
stage('Download Windows image') {
|
||||||
@@ -227,7 +274,7 @@ pipeline {
|
|||||||
stage('Download assets') {
|
stage('Download assets') {
|
||||||
steps {
|
steps {
|
||||||
sh "mkdir ${env.HOME}/workloads"
|
sh "mkdir ${env.HOME}/workloads"
|
||||||
sh 'az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2019.raw" --name windows-server-2019.raw --connection-string "$AZURE_CONNECTION_STRING"'
|
sh 'az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2022-amd64-2.raw" --name windows-server-2022-amd64-2.raw --connection-string "$AZURE_CONNECTION_STRING"'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Run Windows guest integration tests') {
|
stage('Run Windows guest integration tests') {
|
||||||
@@ -281,6 +328,113 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stage('Worker build - Rate Limiter') {
|
||||||
|
agent { node { label 'focal-metrics' } }
|
||||||
|
when {
|
||||||
|
branch 'main'
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run rate-limiter integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 10, unit: 'MINUTES')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-rate-limiter'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Worker build - SGX') {
|
||||||
|
agent { node { label 'jammy-sgx' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
allOf {
|
||||||
|
branch 'main'
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run SGX integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-sgx'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run SGX integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-sgx --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
|
||||||
|
deleteDir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Worker build - VFIO') {
|
||||||
|
agent { node { label 'jammy-vfio' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
allOf {
|
||||||
|
branch 'main'
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -325,24 +479,31 @@ def installAzureCli(distro, arch) {
|
|||||||
sh 'sudo apt install -y azure-cli'
|
sh 'sudo apt install -y azure-cli'
|
||||||
}
|
}
|
||||||
|
|
||||||
def boolean docsFileOnly() {
|
def boolean skipWorkerBuild() {
|
||||||
if (env.CHANGE_TARGET == null) {
|
if (env.CHANGE_TARGET == null) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return sh(
|
if (sh(
|
||||||
returnStatus: true,
|
returnStatus: true,
|
||||||
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v '\\.md'"
|
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v '\\.md'"
|
||||||
) != 0
|
) != 0) {
|
||||||
}
|
return true
|
||||||
|
|
||||||
def boolean fuzzFileOnly() {
|
|
||||||
if (env.CHANGE_TARGET == null) {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sh(
|
if (sh(
|
||||||
returnStatus: true,
|
returnStatus: true,
|
||||||
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v -E 'fuzz/'"
|
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v -E 'fuzz/'"
|
||||||
) != 0
|
) != 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sh(
|
||||||
|
returnStatus: true,
|
||||||
|
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v -E '.github/'"
|
||||||
|
) != 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
- Sebastien Boeuf - @sboeuf
|
- Sebastien Boeuf - @sboeuf
|
||||||
- Robert Bradford - @rbradford
|
- Robert Bradford - @rbradford
|
||||||
|
- Bo Chen - @likebreath
|
||||||
- Samuel Ortiz - @sameo
|
- Samuel Ortiz - @sameo
|
||||||
- Wei Liu - @liuw
|
- Wei Liu - @liuw
|
||||||
- Michael Zhao - @michael2012z
|
- Michael Zhao - @michael2012z
|
||||||
|
|||||||
112
README.md
112
README.md
@@ -69,16 +69,18 @@ Cloud Hypervisor supports `64-bit Linux` and Windows 10/Windows Server 2019.
|
|||||||
|
|
||||||
# 2. Getting Started
|
# 2. Getting Started
|
||||||
|
|
||||||
The following sections describe how to build and run Cloud Hypervisor on the
|
The following sections describe how to build and run Cloud Hypervisor.
|
||||||
`x86-64` platform. For getting started on the `AArch64` platform, please refer
|
|
||||||
to the
|
## Prerequisites for AArch64
|
||||||
[AArch64 documentation](docs/arm64.md).
|
|
||||||
|
- AArch64 servers (recommended) or development boards equipped with the GICv3
|
||||||
|
interrupt controller.
|
||||||
|
|
||||||
## Host OS
|
## Host OS
|
||||||
|
|
||||||
For required KVM functionality the minimum host kernel version is 4.11. For
|
For required KVM functionality and adequate performance the recommended host
|
||||||
adequate performance the minimum recommended host kernel version is 5.6. The
|
kernel version is 5.13. The majority of the CI currently tests with kernel
|
||||||
majority of the CI currently tests with kernel version 5.15.
|
version 5.15.
|
||||||
|
|
||||||
## Use Pre-built Binaries
|
## Use Pre-built Binaries
|
||||||
|
|
||||||
@@ -105,16 +107,13 @@ do not wish to use the pre-built binaries.
|
|||||||
|
|
||||||
## Booting Linux
|
## Booting Linux
|
||||||
|
|
||||||
The instructions below are for the `x86-64` platform. For `AArch64` please see
|
Cloud Hypervisor supports direct kernel boot (the x86-64 kernel requires the kernel
|
||||||
the [AArch64 specific documentation](docs/arm64.md).
|
built with PVH support) or booting via a firmware (either [Rust Hypervisor
|
||||||
|
|
||||||
Cloud Hypervisor supports direct kernel boot (if the kernel is built with PVH
|
|
||||||
support) or booting via a firmware (either [Rust Hypervisor
|
|
||||||
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) or an
|
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) or an
|
||||||
edk2 UEFI firmware called `CLOUDHV`.)
|
edk2 UEFI firmware called `CLOUDHV` / `CLOUDHV_EFI`.)
|
||||||
|
|
||||||
Binary builds of the firmware files are available for the latest release of
|
Binary builds of the firmware files are available for the latest release of
|
||||||
[Rust Hyperivor
|
[Rust Hypervisor
|
||||||
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/latest)
|
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/latest)
|
||||||
and [our edk2
|
and [our edk2
|
||||||
repository](https://github.com/cloud-hypervisor/edk2/releases/latest)
|
repository](https://github.com/cloud-hypervisor/edk2/releases/latest)
|
||||||
@@ -125,7 +124,7 @@ may be required.
|
|||||||
### Firmware Booting
|
### Firmware Booting
|
||||||
|
|
||||||
Cloud Hypervisor supports booting disk images containing all needed components
|
Cloud Hypervisor supports booting disk images containing all needed components
|
||||||
to run cloud workloads, a.k.a. cloud images.
|
to run cloud workloads, a.k.a. cloud images.
|
||||||
|
|
||||||
The following sample commands will download an Ubuntu Cloud image, converting
|
The following sample commands will download an Ubuntu Cloud image, converting
|
||||||
it into a format that Cloud Hypervisor can use and a firmware to boot the image
|
it into a format that Cloud Hypervisor can use and a firmware to boot the image
|
||||||
@@ -141,14 +140,17 @@ The Ubuntu cloud images do not ship with a default password so it necessary to
|
|||||||
use a `cloud-init` disk image to customise the image on the first boot. A basic
|
use a `cloud-init` disk image to customise the image on the first boot. A basic
|
||||||
`cloud-init` image is generated by this [script](scripts/create-cloud-init.sh).
|
`cloud-init` image is generated by this [script](scripts/create-cloud-init.sh).
|
||||||
This seeds the image with a default username/password of `cloud/cloud123`. It
|
This seeds the image with a default username/password of `cloud/cloud123`. It
|
||||||
is only necessary to add this disk image on the first boot.
|
is only necessary to add this disk image on the first boot. Script also assigns
|
||||||
|
default IP address using `test_data/cloud-init/ubuntu/local/network-config` details
|
||||||
|
with `--net "mac=12:34:56:78:90:ab,tap="` option. Then the matching mac address
|
||||||
|
interface will be enabled as per `network-config` details.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
||||||
$ ./create-cloud-init.sh
|
$ ./create-cloud-init.sh
|
||||||
$ ./cloud-hypervisor \
|
$ ./cloud-hypervisor \
|
||||||
--kernel ./hypervisor-fw \
|
--kernel ./hypervisor-fw \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=focal-server-cloudimg-amd64.raw --disk path=/tmp/ubuntu-cloudinit.img \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask="
|
--net "tap=,mac=,ip=,mask="
|
||||||
@@ -161,7 +163,7 @@ GRUB) is required then it necessary to switch to the serial console instead of
|
|||||||
```shell
|
```shell
|
||||||
$ ./cloud-hypervisor \
|
$ ./cloud-hypervisor \
|
||||||
--kernel ./hypervisor-fw \
|
--kernel ./hypervisor-fw \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=focal-server-cloudimg-amd64.raw --disk path=/tmp/ubuntu-cloudinit.img \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask=" \
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
@@ -173,23 +175,31 @@ $ ./cloud-hypervisor \
|
|||||||
|
|
||||||
#### Building your Kernel
|
#### Building your Kernel
|
||||||
|
|
||||||
Cloud Hypervisor also supports direct kernel boot into a `vmlinux` ELF kernel (compiled with PVH support). In order to support development there is a custom branch; however provided the required options are enabled any recent kernel will suffice.
|
Cloud Hypervisor also supports direct kernel boot. For x86-64, a `vmlinux` ELF kernel (compiled with PVH support) is needed. In order to support development there is a custom branch; however provided the required options are enabled any recent kernel will suffice.
|
||||||
|
|
||||||
To build the kernel:
|
To build the kernel:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# Clone the Cloud Hypervisor Linux branch
|
# Clone the Cloud Hypervisor Linux branch
|
||||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-5.15.12 linux-cloud-hypervisor
|
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.2 linux-cloud-hypervisor
|
||||||
$ pushd linux-cloud-hypervisor
|
$ pushd linux-cloud-hypervisor
|
||||||
# Use the cloud-hypervisor kernel config to build your kernel
|
# Use the x86-64 cloud-hypervisor kernel config to build your kernel for x86-64
|
||||||
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-x86_64
|
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-x86_64
|
||||||
$ cp linux-config-x86_64 .config
|
# Use the AArch64 cloud-hypervisor kernel config to build your kernel for AArch64
|
||||||
|
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-aarch64
|
||||||
|
$ cp linux-config-x86_64 .config # x86-64
|
||||||
|
$ cp linux-config-aarch64 .config # AArch64
|
||||||
|
# Do native build of the x86-64 kernel
|
||||||
$ KCFLAGS="-Wa,-mx86-used-note=no" make bzImage -j `nproc`
|
$ KCFLAGS="-Wa,-mx86-used-note=no" make bzImage -j `nproc`
|
||||||
|
# Do native build of the AArch64 kernel
|
||||||
|
$ make -j `nproc`
|
||||||
$ popd
|
$ popd
|
||||||
```
|
```
|
||||||
|
|
||||||
The `vmlinux` kernel image will then be located at
|
For x86-64, the `vmlinux` kernel image will then be located at
|
||||||
`linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin`.
|
`linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin`.
|
||||||
|
For AArch64, the `Image` kernel image will then be located at
|
||||||
|
`linux-cloud-hypervisor/arch/arm64/boot/Image`.
|
||||||
|
|
||||||
#### Disk image
|
#### Disk image
|
||||||
|
|
||||||
@@ -197,8 +207,10 @@ For the disk image the same Ubuntu image as before can be used. This contains
|
|||||||
an `ext4` root filesystem.
|
an `ext4` root filesystem.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img
|
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img # x86-64
|
||||||
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw
|
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-arm64.img # AArch64
|
||||||
|
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw # x86-64
|
||||||
|
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-arm64.img focal-server-cloudimg-arm64.raw # AArch64
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Booting the guest VM
|
#### Booting the guest VM
|
||||||
@@ -206,12 +218,28 @@ $ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-serv
|
|||||||
These sample commands boot the disk image using the custom kernel whilst also
|
These sample commands boot the disk image using the custom kernel whilst also
|
||||||
supplying the desired kernel command line.
|
supplying the desired kernel command line.
|
||||||
|
|
||||||
|
- x86-64
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
||||||
$ ./create-cloud-init.sh
|
$ ./create-cloud-init.sh
|
||||||
$ ./cloud-hypervisor \
|
$ ./cloud-hypervisor \
|
||||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=focal-server-cloudimg-amd64.raw --disk path=/tmp/ubuntu-cloudinit.img \
|
||||||
|
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
||||||
|
--cpus boot=4 \
|
||||||
|
--memory size=1024M \
|
||||||
|
--net "tap=,mac=,ip=,mask="
|
||||||
|
```
|
||||||
|
|
||||||
|
- AArch64
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
||||||
|
$ ./create-cloud-init.sh
|
||||||
|
$ ./cloud-hypervisor \
|
||||||
|
--kernel ./linux-cloud-hypervisor/arch/arm64/boot/Image \
|
||||||
|
--disk path=focal-server-cloudimg-arm64.raw --disk path=/tmp/ubuntu-cloudinit.img \
|
||||||
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
@@ -220,7 +248,10 @@ $ ./cloud-hypervisor \
|
|||||||
|
|
||||||
If earlier kernel messages are required the serial console should be used instead of `virtio-console`.
|
If earlier kernel messages are required the serial console should be used instead of `virtio-console`.
|
||||||
|
|
||||||
```cloud-hypervisor \
|
- x86-64
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ ./cloud-hypervisor \
|
||||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||||
--console off \
|
--console off \
|
||||||
--serial tty \
|
--serial tty \
|
||||||
@@ -231,6 +262,20 @@ If earlier kernel messages are required the serial console should be used instea
|
|||||||
--net "tap=,mac=,ip=,mask="
|
--net "tap=,mac=,ip=,mask="
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- AArch64
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ ./cloud-hypervisor \
|
||||||
|
--kernel ./linux-cloud-hypervisor/arch/arm64/boot/Image \
|
||||||
|
--console off \
|
||||||
|
--serial tty \
|
||||||
|
--disk path=focal-server-cloudimg-arm64.raw \
|
||||||
|
--cmdline "console=ttyAMA0 root=/dev/vda1 rw" \
|
||||||
|
--cpus boot=4 \
|
||||||
|
--memory size=1024M \
|
||||||
|
--net "tap=,mac=,ip=,mask="
|
||||||
|
```
|
||||||
|
|
||||||
# 3. Status
|
# 3. Status
|
||||||
|
|
||||||
Cloud Hypervisor is under active development. The following stability
|
Cloud Hypervisor is under active development. The following stability
|
||||||
@@ -254,12 +299,11 @@ Currently the following items are **not** guaranteed across updates:
|
|||||||
|
|
||||||
Further details can be found in the [release documentation](docs/releases.md).
|
Further details can be found in the [release documentation](docs/releases.md).
|
||||||
|
|
||||||
As of 2022-10-13, the following cloud images are supported:
|
As of 2023-01-03, the following cloud images are supported:
|
||||||
|
|
||||||
- [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (bionic-server-cloudimg-amd64.img)
|
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (focal-server-cloudimg-{amd64,arm64}.img)
|
||||||
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (focal-server-cloudimg-amd64.img)
|
- [Ubuntu Jammy](https://cloud-images.ubuntu.com/jammy/current/) (jammy-server-cloudimg-{amd64,arm64}.img )
|
||||||
- [Ubuntu Jammy](https://cloud-images.ubuntu.com/jammy/current/) (jammy-server-cloudimg-amd64.img )
|
- [Fedora 36](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/) ([Fedora-Cloud-Base-36-1.5.x86_64.raw.xz](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/x86_64/images/) / [Fedora-Cloud-Base-36-1.5.aarch64.raw.xz](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/aarch64/images/))
|
||||||
- [Fedora 36](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/x86_64/images/) (Fedora-Cloud-Base-36-1.5.x86_64.raw.xz)
|
|
||||||
|
|
||||||
Direct kernel boot to userspace should work with a rootfs from most
|
Direct kernel boot to userspace should work with a rootfs from most
|
||||||
distributions although you may need to enable exotic filesystem types in the
|
distributions although you may need to enable exotic filesystem types in the
|
||||||
@@ -331,8 +375,8 @@ are all equal and welcome means of contribution. See the
|
|||||||
|
|
||||||
## Slack
|
## Slack
|
||||||
|
|
||||||
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LWQ1MTA1ZDVmODkwMWQ1MTRhYzk4ZGNlN2UwNTI3ZmFlODU0OTcwOWZjMTkwZDExYWE3YjFmNzgzY2FmNDAyMjI)
|
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/).
|
[join us on Slack](https://cloud-hypervisor.slack.com/), and [participate in our community activities](https://cloud-hypervisor.slack.com/archives/C04R5DUQVBN).
|
||||||
|
|
||||||
## Mailing list
|
## Mailing list
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "acpi_tables"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
vm-memory = "0.9.0"
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +0,0 @@
|
|||||||
// Copyright © 2019 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
pub mod aml;
|
|
||||||
pub mod rsdp;
|
|
||||||
pub mod sdt;
|
|
||||||
|
|
||||||
fn generate_checksum(data: &[u8]) -> u8 {
|
|
||||||
(255 - data.iter().fold(0u8, |acc, x| acc.wrapping_add(*x))).wrapping_add(1)
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
// Copyright © 2019 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
use vm_memory::ByteValued;
|
|
||||||
|
|
||||||
#[repr(packed)]
|
|
||||||
#[derive(Clone, Copy, Default)]
|
|
||||||
pub struct Rsdp {
|
|
||||||
pub signature: [u8; 8],
|
|
||||||
pub checksum: u8,
|
|
||||||
pub oem_id: [u8; 6],
|
|
||||||
pub revision: u8,
|
|
||||||
_rsdt_addr: u32,
|
|
||||||
pub length: u32,
|
|
||||||
pub xsdt_addr: u64,
|
|
||||||
pub extended_checksum: u8,
|
|
||||||
_reserved: [u8; 3],
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: Rsdp only contains a series of integers
|
|
||||||
unsafe impl ByteValued for Rsdp {}
|
|
||||||
|
|
||||||
impl Rsdp {
|
|
||||||
pub fn new(oem_id: [u8; 6], xsdt_addr: u64) -> Self {
|
|
||||||
let mut rsdp = Rsdp {
|
|
||||||
signature: *b"RSD PTR ",
|
|
||||||
checksum: 0,
|
|
||||||
oem_id,
|
|
||||||
revision: 2,
|
|
||||||
_rsdt_addr: 0,
|
|
||||||
length: std::mem::size_of::<Rsdp>() as u32,
|
|
||||||
xsdt_addr,
|
|
||||||
extended_checksum: 0,
|
|
||||||
_reserved: [0; 3],
|
|
||||||
};
|
|
||||||
|
|
||||||
rsdp.checksum = super::generate_checksum(&rsdp.as_slice()[0..19]);
|
|
||||||
rsdp.extended_checksum = super::generate_checksum(rsdp.as_slice());
|
|
||||||
rsdp
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len() -> usize {
|
|
||||||
std::mem::size_of::<Rsdp>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Rsdp;
|
|
||||||
use vm_memory::bytes::ByteValued;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rsdp() {
|
|
||||||
let rsdp = Rsdp::new(*b"CHYPER", 0xdead_beef);
|
|
||||||
let sum = rsdp
|
|
||||||
.as_slice()
|
|
||||||
.iter()
|
|
||||||
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
|
||||||
assert_eq!(sum, 0);
|
|
||||||
let sum: u8 = rsdp
|
|
||||||
.as_slice()
|
|
||||||
.iter()
|
|
||||||
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
|
||||||
assert_eq!(sum, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
// Copyright © 2019 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
#[repr(packed)]
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct GenericAddress {
|
|
||||||
pub address_space_id: u8,
|
|
||||||
pub register_bit_width: u8,
|
|
||||||
pub register_bit_offset: u8,
|
|
||||||
pub access_size: u8,
|
|
||||||
pub address: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GenericAddress {
|
|
||||||
pub fn io_port_address<T>(address: u16) -> Self {
|
|
||||||
GenericAddress {
|
|
||||||
address_space_id: 1,
|
|
||||||
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: 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 {
|
|
||||||
data: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::len_without_is_empty)]
|
|
||||||
impl Sdt {
|
|
||||||
pub fn new(
|
|
||||||
signature: [u8; 4],
|
|
||||||
length: u32,
|
|
||||||
revision: u8,
|
|
||||||
oem_id: [u8; 6],
|
|
||||||
oem_table: [u8; 8],
|
|
||||||
oem_revision: u32,
|
|
||||||
) -> Self {
|
|
||||||
assert!(length >= 36);
|
|
||||||
|
|
||||||
let mut data = Vec::with_capacity(length as usize);
|
|
||||||
data.extend_from_slice(&signature);
|
|
||||||
data.extend_from_slice(&length.to_le_bytes());
|
|
||||||
data.push(revision);
|
|
||||||
data.push(0); // checksum
|
|
||||||
data.extend_from_slice(&oem_id);
|
|
||||||
data.extend_from_slice(&oem_table);
|
|
||||||
data.extend_from_slice(&oem_revision.to_le_bytes());
|
|
||||||
data.extend_from_slice(b"CLDH");
|
|
||||||
data.extend_from_slice(&0u32.to_le_bytes());
|
|
||||||
assert_eq!(data.len(), 36);
|
|
||||||
|
|
||||||
data.resize(length as usize, 0);
|
|
||||||
let mut sdt = Sdt { data };
|
|
||||||
|
|
||||||
sdt.update_checksum();
|
|
||||||
sdt
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_checksum(&mut self) {
|
|
||||||
self.data[9] = 0;
|
|
||||||
let checksum = super::generate_checksum(self.data.as_slice());
|
|
||||||
self.data[9] = checksum
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_slice(&self) -> &[u8] {
|
|
||||||
self.data.as_slice()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn append<T>(&mut self, value: T) {
|
|
||||||
let orig_length = self.data.len();
|
|
||||||
let new_length = orig_length + std::mem::size_of::<T>();
|
|
||||||
self.data.resize(new_length, 0);
|
|
||||||
self.write_u32(4, new_length as u32);
|
|
||||||
self.write(orig_length, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn append_slice(&mut self, data: &[u8]) {
|
|
||||||
let orig_length = self.data.len();
|
|
||||||
let new_length = orig_length + data.len();
|
|
||||||
self.write_u32(4, new_length as u32);
|
|
||||||
self.data.extend_from_slice(data);
|
|
||||||
self.update_checksum();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write a value at the given offset
|
|
||||||
pub fn write<T>(&mut self, offset: usize, value: T) {
|
|
||||||
assert!((offset + (std::mem::size_of::<T>() - 1)) < self.data.len());
|
|
||||||
unsafe {
|
|
||||||
*(((self.data.as_mut_ptr() as usize) + offset) as *mut T) = value;
|
|
||||||
}
|
|
||||||
self.update_checksum();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_u8(&mut self, offset: usize, val: u8) {
|
|
||||||
self.write(offset, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_u16(&mut self, offset: usize, val: u16) {
|
|
||||||
self.write(offset, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_u32(&mut self, offset: usize, val: u32) {
|
|
||||||
self.write(offset, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_u64(&mut self, offset: usize, val: u64) {
|
|
||||||
self.write(offset, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.data.len()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Sdt;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sdt() {
|
|
||||||
let mut sdt = Sdt::new(*b"TEST", 40, 1, *b"CLOUDH", *b"TESTTEST", 1);
|
|
||||||
let sum: u8 = sdt
|
|
||||||
.as_slice()
|
|
||||||
.iter()
|
|
||||||
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
|
||||||
assert_eq!(sum, 0);
|
|
||||||
sdt.write_u32(36, 0x12345678);
|
|
||||||
let sum: u8 = sdt
|
|
||||||
.as_slice()
|
|
||||||
.iter()
|
|
||||||
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
|
||||||
assert_eq!(sum, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,4 +5,4 @@ authors = ["The Cloud Hypervisor Authors"]
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
vmm-sys-util = "0.10.0"
|
vmm-sys-util = "0.11.0"
|
||||||
|
|||||||
@@ -22,16 +22,16 @@ impl fmt::Display for Error {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
use Error::*;
|
use Error::*;
|
||||||
match self {
|
match self {
|
||||||
Socket(e) => write!(f, "Error writing to or reading from HTTP socket: {}", e),
|
Socket(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
|
||||||
SocketSendFds(e) => write!(f, "Error writing to or reading from HTTP socket: {}", e),
|
SocketSendFds(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
|
||||||
StatusCodeParsing(e) => write!(f, "Error parsing HTTP status code: {}", e),
|
StatusCodeParsing(e) => write!(f, "Error parsing HTTP status code: {e}"),
|
||||||
MissingProtocol => write!(f, "HTTP output is missing protocol statement"),
|
MissingProtocol => write!(f, "HTTP output is missing protocol statement"),
|
||||||
ContentLengthParsing(e) => write!(f, "Error parsing HTTP Content-Length field: {}", e),
|
ContentLengthParsing(e) => write!(f, "Error parsing HTTP Content-Length field: {e}"),
|
||||||
ServerResponse(s, o) => {
|
ServerResponse(s, o) => {
|
||||||
if let Some(o) = o {
|
if let Some(o) = o {
|
||||||
write!(f, "Server responded with an error: {:?}: {}", s, o)
|
write!(f, "Server responded with an error: {s:?}: {o}")
|
||||||
} else {
|
} else {
|
||||||
write!(f, "Server responded with an error: {:?}", s)
|
write!(f, "Server responded with an error: {s:?}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ impl StatusCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
|
fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
|
||||||
let header_str = format!("{}: ", header);
|
let header_str = format!("{header}: ");
|
||||||
res.find(&header_str)
|
res.find(&header_str)
|
||||||
.map(|o| &res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
|
.map(|o| &res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
|
||||||
}
|
}
|
||||||
@@ -153,8 +153,7 @@ pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket
|
|||||||
socket
|
socket
|
||||||
.send_with_fds(
|
.send_with_fds(
|
||||||
&[format!(
|
&[format!(
|
||||||
"{} /api/v1/{} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n",
|
"{method} /api/v1/{full_command} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n"
|
||||||
method, full_command
|
|
||||||
)
|
)
|
||||||
.as_bytes()],
|
.as_bytes()],
|
||||||
&request_fds,
|
&request_fds,
|
||||||
@@ -235,7 +234,7 @@ pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
|
|||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Create the full VM command. For VMM commands, use
|
// Create the full VM command. For VMM commands, use
|
||||||
// simple_api_full_command().
|
// simple_api_full_command().
|
||||||
let full_command = format!("vm.{}", c);
|
let full_command = format!("vm.{c}");
|
||||||
|
|
||||||
simple_api_full_command_with_fds(socket, method, &full_command, request_body, request_fds)
|
simple_api_full_command_with_fds(socket, method, &full_command, request_body, request_fds)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,23 +6,24 @@ edition = "2021"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
sev_snp = []
|
||||||
tdx = []
|
tdx = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.66"
|
anyhow = "1.0.75"
|
||||||
byteorder = "1.4.3"
|
byteorder = "1.4.3"
|
||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
libc = "0.2.137"
|
libc = "0.2.147"
|
||||||
linux-loader = { version = "0.7.0", features = ["elf", "bzimage", "pe"] }
|
linux-loader = { version = "0.9.1", features = ["elf", "bzimage", "pe"] }
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
serde = { version = "1.0.147", features = ["rc", "derive"] }
|
serde = { version = "1.0.168", features = ["rc", "derive"] }
|
||||||
thiserror = "1.0.37"
|
thiserror = "1.0.40"
|
||||||
uuid = "1.2.1"
|
uuid = "1.3.4"
|
||||||
versionize = "0.1.6"
|
versionize = "0.1.10"
|
||||||
versionize_derive = "0.1.4"
|
versionize_derive = "0.1.4"
|
||||||
vm-memory = { version = "0.9.0", features = ["backend-mmap", "backend-bitmap"] }
|
vm-memory = { version = "0.12.2", features = ["backend-mmap", "backend-bitmap"] }
|
||||||
vm-migration = { path = "../vm-migration" }
|
vm-migration = { path = "../vm-migration" }
|
||||||
vmm-sys-util = { version = "0.10.0", features = ["with-serde"] }
|
vmm-sys-util = { version = "0.11.0", features = ["with-serde"] }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||||
fdt_parser = { version = "0.1.4", package = "fdt" }
|
fdt_parser = { version = "0.1.4", package = "fdt" }
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ use super::layout::{
|
|||||||
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
||||||
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
||||||
};
|
};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
||||||
|
|
||||||
@@ -40,8 +42,12 @@ const VIRTIO_IOMMU_PHANDLE: u32 = 5;
|
|||||||
// NOTE: Keep FIRST_VCPU_PHANDLE the last PHANDLE defined.
|
// NOTE: Keep FIRST_VCPU_PHANDLE the last PHANDLE defined.
|
||||||
// This is a value for uniquely identifying the FDT node containing the first vCPU.
|
// This is a value for uniquely identifying the FDT node containing the first vCPU.
|
||||||
// The last number of vCPU phandle depends on the number of vCPUs.
|
// The last number of vCPU phandle depends on the number of vCPUs.
|
||||||
const FIRST_VCPU_PHANDLE: u32 = 6;
|
const FIRST_VCPU_PHANDLE: u32 = 8;
|
||||||
|
|
||||||
|
// This is a value for uniquely identifying the FDT node containing the L2 cache info
|
||||||
|
const L2_CACHE_PHANDLE: u32 = 6;
|
||||||
|
// This is a value for uniquely identifying the FDT node containing the L3 cache info
|
||||||
|
const L3_CACHE_PHANDLE: u32 = 7;
|
||||||
// Read the documentation specified when appending the root node to the FDT.
|
// Read the documentation specified when appending the root node to the FDT.
|
||||||
const ADDRESS_CELLS: u32 = 0x2;
|
const ADDRESS_CELLS: u32 = 0x2;
|
||||||
const SIZE_CELLS: u32 = 0x2;
|
const SIZE_CELLS: u32 = 0x2;
|
||||||
@@ -81,6 +87,132 @@ pub enum Error {
|
|||||||
}
|
}
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
|
pub enum CacheLevel {
|
||||||
|
/// L1 data cache
|
||||||
|
L1D = 0,
|
||||||
|
/// L1 instruction cache
|
||||||
|
L1I = 1,
|
||||||
|
/// L2 cache
|
||||||
|
L2 = 2,
|
||||||
|
/// L3 cache
|
||||||
|
L3 = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NOTE: cache size file directory example,
|
||||||
|
/// "/sys/devices/system/cpu/cpu0/cache/index0/size".
|
||||||
|
pub fn get_cache_size(cache_level: CacheLevel) -> u32 {
|
||||||
|
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
||||||
|
match cache_level {
|
||||||
|
CacheLevel::L1D => file_directory += "/index0/size",
|
||||||
|
CacheLevel::L1I => file_directory += "/index1/size",
|
||||||
|
CacheLevel::L2 => file_directory += "/index2/size",
|
||||||
|
CacheLevel::L3 => file_directory += "/index3/size",
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_path = Path::new(&file_directory);
|
||||||
|
if !file_path.exists() {
|
||||||
|
warn!("File: {} does not exist.", file_directory);
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
info!("File: {} exist.", file_directory);
|
||||||
|
|
||||||
|
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||||
|
// The content of the file is as simple as a size, like: "32K"
|
||||||
|
let src = src.trim();
|
||||||
|
let src_digits: u32 = src[0..src.len() - 1].parse().unwrap();
|
||||||
|
let src_unit = &src[src.len() - 1..];
|
||||||
|
|
||||||
|
src_digits
|
||||||
|
* match src_unit {
|
||||||
|
"K" => 1024,
|
||||||
|
"M" => 1024u32.pow(2),
|
||||||
|
"G" => 1024u32.pow(3),
|
||||||
|
_ => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NOTE: coherency_line_size file directory example,
|
||||||
|
/// "/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size".
|
||||||
|
pub fn get_cache_coherency_line_size(cache_level: CacheLevel) -> u32 {
|
||||||
|
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
||||||
|
match cache_level {
|
||||||
|
CacheLevel::L1D => file_directory += "/index0/coherency_line_size",
|
||||||
|
CacheLevel::L1I => file_directory += "/index1/coherency_line_size",
|
||||||
|
CacheLevel::L2 => file_directory += "/index2/coherency_line_size",
|
||||||
|
CacheLevel::L3 => file_directory += "/index3/coherency_line_size",
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_path = Path::new(&file_directory);
|
||||||
|
if !file_path.exists() {
|
||||||
|
warn!("File: {} does not exist.", file_directory);
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
info!("File: {} exist.", file_directory);
|
||||||
|
|
||||||
|
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||||
|
src.trim().parse::<u32>().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NOTE: number_of_sets file directory example,
|
||||||
|
/// "/sys/devices/system/cpu/cpu0/cache/index0/number_of_sets".
|
||||||
|
pub fn get_cache_number_of_sets(cache_level: CacheLevel) -> u32 {
|
||||||
|
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
||||||
|
match cache_level {
|
||||||
|
CacheLevel::L1D => file_directory += "/index0/number_of_sets",
|
||||||
|
CacheLevel::L1I => file_directory += "/index1/number_of_sets",
|
||||||
|
CacheLevel::L2 => file_directory += "/index2/number_of_sets",
|
||||||
|
CacheLevel::L3 => file_directory += "/index3/number_of_sets",
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_path = Path::new(&file_directory);
|
||||||
|
if !file_path.exists() {
|
||||||
|
warn!("File: {} does not exist.", file_directory);
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
info!("File: {} exist.", file_directory);
|
||||||
|
|
||||||
|
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||||
|
src.trim().parse::<u32>().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NOTE: shared_cpu_list file directory example,
|
||||||
|
/// "/sys/devices/system/cpu/cpu0/cache/index0/shared_cpu_list".
|
||||||
|
pub fn get_cache_shared(cache_level: CacheLevel) -> bool {
|
||||||
|
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
||||||
|
let mut result = true;
|
||||||
|
|
||||||
|
match cache_level {
|
||||||
|
CacheLevel::L1D | CacheLevel::L1I => result = false,
|
||||||
|
CacheLevel::L2 => file_directory += "/index2/shared_cpu_list",
|
||||||
|
CacheLevel::L3 => file_directory += "/index3/shared_cpu_list",
|
||||||
|
}
|
||||||
|
|
||||||
|
if !result {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_path = Path::new(&file_directory);
|
||||||
|
if !file_path.exists() {
|
||||||
|
warn!("File: {} does not exist.", file_directory);
|
||||||
|
result = false;
|
||||||
|
} else {
|
||||||
|
info!("File: {} exist.", file_directory);
|
||||||
|
|
||||||
|
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||||
|
let src = src.trim();
|
||||||
|
if src.is_empty() {
|
||||||
|
result = false;
|
||||||
|
} else {
|
||||||
|
result = src.contains('-') || src.contains(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates the flattened device tree for this aarch64 VM.
|
/// Creates the flattened device tree for this aarch64 VM.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||||
@@ -158,9 +290,74 @@ fn create_cpu_nodes(
|
|||||||
fdt.property_u32("#size-cells", 0x0)?;
|
fdt.property_u32("#size-cells", 0x0)?;
|
||||||
|
|
||||||
let num_cpus = vcpu_mpidr.len();
|
let num_cpus = vcpu_mpidr.len();
|
||||||
|
let threads_per_core = vcpu_topology.unwrap_or_default().0;
|
||||||
|
let cores_per_package = vcpu_topology.unwrap_or_default().1;
|
||||||
|
let packages = vcpu_topology.unwrap_or_default().2;
|
||||||
|
|
||||||
|
let max_cpus: u32 = (threads_per_core * cores_per_package * packages).into();
|
||||||
|
|
||||||
|
// Add cache info.
|
||||||
|
// L1 Data Cache Info.
|
||||||
|
let mut l1_d_cache_size: u32 = 0;
|
||||||
|
let mut l1_d_cache_line_size: u32 = 0;
|
||||||
|
let mut l1_d_cache_sets: u32 = 0;
|
||||||
|
|
||||||
|
// L1 Instruction Cache Info.
|
||||||
|
let mut l1_i_cache_size: u32 = 0;
|
||||||
|
let mut l1_i_cache_line_size: u32 = 0;
|
||||||
|
let mut l1_i_cache_sets: u32 = 0;
|
||||||
|
|
||||||
|
// L2 Cache Info.
|
||||||
|
let mut l2_cache_size: u32 = 0;
|
||||||
|
let mut l2_cache_line_size: u32 = 0;
|
||||||
|
let mut l2_cache_sets: u32 = 0;
|
||||||
|
|
||||||
|
// L3 Cache Info.
|
||||||
|
let mut l3_cache_size: u32 = 0;
|
||||||
|
let mut l3_cache_line_size: u32 = 0;
|
||||||
|
let mut l3_cache_sets: u32 = 0;
|
||||||
|
|
||||||
|
// Cache Shared Info.
|
||||||
|
let mut l2_cache_shared: bool = false;
|
||||||
|
let mut l3_cache_shared: bool = false;
|
||||||
|
|
||||||
|
let cache_path = Path::new("/sys/devices/system/cpu/cpu0/cache");
|
||||||
|
let cache_exist: bool = cache_path.exists();
|
||||||
|
if !cache_exist {
|
||||||
|
warn!("cache sysfs system does not exist.");
|
||||||
|
} else {
|
||||||
|
info!("cache sysfs system exists.");
|
||||||
|
// L1 Data Cache Info.
|
||||||
|
l1_d_cache_size = get_cache_size(CacheLevel::L1D);
|
||||||
|
l1_d_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1D);
|
||||||
|
l1_d_cache_sets = get_cache_number_of_sets(CacheLevel::L1D);
|
||||||
|
|
||||||
|
// L1 Instruction Cache Info.
|
||||||
|
l1_i_cache_size = get_cache_size(CacheLevel::L1I);
|
||||||
|
l1_i_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1I);
|
||||||
|
l1_i_cache_sets = get_cache_number_of_sets(CacheLevel::L1I);
|
||||||
|
|
||||||
|
// L2 Cache Info.
|
||||||
|
l2_cache_size = get_cache_size(CacheLevel::L2);
|
||||||
|
l2_cache_line_size = get_cache_coherency_line_size(CacheLevel::L2);
|
||||||
|
l2_cache_sets = get_cache_number_of_sets(CacheLevel::L2);
|
||||||
|
|
||||||
|
// L3 Cache Info.
|
||||||
|
l3_cache_size = get_cache_size(CacheLevel::L3);
|
||||||
|
l3_cache_line_size = get_cache_coherency_line_size(CacheLevel::L3);
|
||||||
|
l3_cache_sets = get_cache_number_of_sets(CacheLevel::L3);
|
||||||
|
|
||||||
|
// Cache Shared Info.
|
||||||
|
if l2_cache_size != 0 {
|
||||||
|
l2_cache_shared = get_cache_shared(CacheLevel::L2);
|
||||||
|
}
|
||||||
|
if l3_cache_size != 0 {
|
||||||
|
l3_cache_shared = get_cache_shared(CacheLevel::L3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
|
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
|
||||||
let cpu_name = format!("cpu@{:x}", cpu_id);
|
let cpu_name = format!("cpu@{cpu_id:x}");
|
||||||
let cpu_node = fdt.begin_node(&cpu_name)?;
|
let cpu_node = fdt.begin_node(&cpu_name)?;
|
||||||
fdt.property_string("device_type", "cpu")?;
|
fdt.property_string("device_type", "cpu")?;
|
||||||
fdt.property_string("compatible", "arm,arm-v8")?;
|
fdt.property_string("compatible", "arm,arm-v8")?;
|
||||||
@@ -183,26 +380,112 @@ fn create_cpu_nodes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cache_exist && l1_d_cache_size != 0 && l1_i_cache_size != 0 {
|
||||||
|
// Add cache info.
|
||||||
|
fdt.property_u32("d-cache-size", l1_d_cache_size)?;
|
||||||
|
fdt.property_u32("d-cache-line-size", l1_d_cache_line_size)?;
|
||||||
|
fdt.property_u32("d-cache-sets", l1_d_cache_sets)?;
|
||||||
|
|
||||||
|
fdt.property_u32("i-cache-size", l1_i_cache_size)?;
|
||||||
|
fdt.property_u32("i-cache-line-size", l1_i_cache_line_size)?;
|
||||||
|
fdt.property_u32("i-cache-sets", l1_i_cache_sets)?;
|
||||||
|
|
||||||
|
if l2_cache_size != 0 && !l2_cache_shared {
|
||||||
|
fdt.property_u32(
|
||||||
|
"next-level-cache",
|
||||||
|
cpu_id as u32 + max_cpus + FIRST_VCPU_PHANDLE + L2_CACHE_PHANDLE,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let l2_cache_name = "l2-cache0";
|
||||||
|
let l2_cache_node = fdt.begin_node(l2_cache_name)?;
|
||||||
|
// PHANDLE is used to mark device node, and PHANDLE is unique. To avoid phandle
|
||||||
|
// conflicts with other device nodes, consider the previous CPU PHANDLE, so the
|
||||||
|
// CPU L2 cache PHANDLE must start from the largest CPU PHANDLE plus 1.
|
||||||
|
fdt.property_u32(
|
||||||
|
"phandle",
|
||||||
|
cpu_id as u32 + max_cpus + FIRST_VCPU_PHANDLE + L2_CACHE_PHANDLE,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fdt.property_string("compatible", "cache")?;
|
||||||
|
fdt.property_u32("cache-size", l2_cache_size)?;
|
||||||
|
fdt.property_u32("cache-line-size", l2_cache_line_size)?;
|
||||||
|
fdt.property_u32("cache-sets", l2_cache_sets)?;
|
||||||
|
fdt.property_u32("cache-level", 2)?;
|
||||||
|
|
||||||
|
if l3_cache_size != 0 && l3_cache_shared {
|
||||||
|
let package_id: u32 = cpu_id as u32 / cores_per_package as u32;
|
||||||
|
fdt.property_u32(
|
||||||
|
"next-level-cache",
|
||||||
|
package_id
|
||||||
|
+ num_cpus as u32
|
||||||
|
+ max_cpus
|
||||||
|
+ FIRST_VCPU_PHANDLE
|
||||||
|
+ L2_CACHE_PHANDLE
|
||||||
|
+ L3_CACHE_PHANDLE,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fdt.end_node(l2_cache_node)?;
|
||||||
|
}
|
||||||
|
if l2_cache_size != 0 && l2_cache_shared {
|
||||||
|
warn!("L2 cache shared with other cpus");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fdt.end_node(cpu_node)?;
|
fdt.end_node(cpu_node)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cache_exist && l3_cache_size != 0 && !l2_cache_shared && l3_cache_shared {
|
||||||
|
let mut i: u32 = 0;
|
||||||
|
while i < packages.into() {
|
||||||
|
let l3_cache_name = "l3-cache0";
|
||||||
|
let l3_cache_node = fdt.begin_node(l3_cache_name)?;
|
||||||
|
// ARM L3 cache is generally shared within the package (socket), so the
|
||||||
|
// L3 cache node pointed to by the CPU in the package has the same L3
|
||||||
|
// cache PHANDLE. The L3 cache phandle must start from the largest L2
|
||||||
|
// cache PHANDLE plus 1 to avoid duplication.
|
||||||
|
fdt.property_u32(
|
||||||
|
"phandle",
|
||||||
|
i + num_cpus as u32
|
||||||
|
+ max_cpus
|
||||||
|
+ FIRST_VCPU_PHANDLE
|
||||||
|
+ L2_CACHE_PHANDLE
|
||||||
|
+ L3_CACHE_PHANDLE,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fdt.property_string("compatible", "cache")?;
|
||||||
|
fdt.property_null("cache-unified")?;
|
||||||
|
fdt.property_u32("cache-size", l3_cache_size)?;
|
||||||
|
fdt.property_u32("cache-line-size", l3_cache_line_size)?;
|
||||||
|
fdt.property_u32("cache-sets", l3_cache_sets)?;
|
||||||
|
fdt.property_u32("cache-level", 3)?;
|
||||||
|
fdt.end_node(l3_cache_node)?;
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(topology) = vcpu_topology {
|
if let Some(topology) = vcpu_topology {
|
||||||
let (threads_per_core, cores_per_package, packages) = topology;
|
let (threads_per_core, cores_per_package, packages) = topology;
|
||||||
let cpu_map_node = fdt.begin_node("cpu-map")?;
|
let cpu_map_node = fdt.begin_node("cpu-map")?;
|
||||||
|
|
||||||
// Create device tree nodes with regard of above mapping.
|
// Create device tree nodes with regard of above mapping.
|
||||||
for cluster_idx in 0..packages {
|
for package_idx in 0..packages {
|
||||||
let cluster_name = format!("cluster{:x}", cluster_idx);
|
let package_name = format!("socket{package_idx:x}");
|
||||||
let cluster_node = fdt.begin_node(&cluster_name)?;
|
let package_node = fdt.begin_node(&package_name)?;
|
||||||
|
|
||||||
|
// Cluster is the container of cores, and it is mandatory in the CPU topology.
|
||||||
|
// Add a default "cluster0" in each socket/package.
|
||||||
|
let cluster_node = fdt.begin_node("cluster0")?;
|
||||||
|
|
||||||
for core_idx in 0..cores_per_package {
|
for core_idx in 0..cores_per_package {
|
||||||
let core_name = format!("core{:x}", core_idx);
|
let core_name = format!("core{core_idx:x}");
|
||||||
let core_node = fdt.begin_node(&core_name)?;
|
let core_node = fdt.begin_node(&core_name)?;
|
||||||
|
|
||||||
for thread_idx in 0..threads_per_core {
|
for thread_idx in 0..threads_per_core {
|
||||||
let thread_name = format!("thread{:x}", thread_idx);
|
let thread_name = format!("thread{thread_idx:x}");
|
||||||
let thread_node = fdt.begin_node(&thread_name)?;
|
let thread_node = fdt.begin_node(&thread_name)?;
|
||||||
let cpu_idx = threads_per_core * cores_per_package * cluster_idx
|
let cpu_idx = threads_per_core * cores_per_package * package_idx
|
||||||
+ threads_per_core * core_idx
|
+ threads_per_core * core_idx
|
||||||
+ thread_idx;
|
+ thread_idx;
|
||||||
fdt.property_u32("cpu", cpu_idx as u32 + FIRST_VCPU_PHANDLE)?;
|
fdt.property_u32("cpu", cpu_idx as u32 + FIRST_VCPU_PHANDLE)?;
|
||||||
@@ -212,6 +495,7 @@ fn create_cpu_nodes(
|
|||||||
fdt.end_node(core_node)?;
|
fdt.end_node(core_node)?;
|
||||||
}
|
}
|
||||||
fdt.end_node(cluster_node)?;
|
fdt.end_node(cluster_node)?;
|
||||||
|
fdt.end_node(package_node)?;
|
||||||
}
|
}
|
||||||
fdt.end_node(cpu_map_node)?;
|
fdt.end_node(cpu_map_node)?;
|
||||||
} else {
|
} else {
|
||||||
@@ -247,7 +531,7 @@ fn create_memory_node(
|
|||||||
node_memory_addr = memory_region_start_addr;
|
node_memory_addr = memory_region_start_addr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let memory_node_name = format!("memory@{:x}", node_memory_addr);
|
let memory_node_name = format!("memory@{node_memory_addr:x}");
|
||||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||||
fdt.property_string("device_type", "memory")?;
|
fdt.property_string("device_type", "memory")?;
|
||||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||||
@@ -255,35 +539,86 @@ fn create_memory_node(
|
|||||||
fdt.end_node(memory_node)?;
|
fdt.end_node(memory_node)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let last_addr = guest_mem.last_addr().raw_value();
|
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
|
||||||
if last_addr < super::layout::MEM_32BIT_RESERVED_START.raw_value() {
|
let ram_regions = {
|
||||||
// Case 1: all RAM is under the hole
|
let mut ram_regions = Vec::new();
|
||||||
let mem_size = last_addr - super::layout::RAM_START.raw_value() + 1;
|
let mut current_start = guest_mem
|
||||||
let mem_reg_prop = [super::layout::RAM_START.raw_value() as u64, mem_size as u64];
|
.iter()
|
||||||
let memory_node = fdt.begin_node("memory")?;
|
.next()
|
||||||
fdt.property_string("device_type", "memory")?;
|
.map(GuestMemoryRegion::start_addr)
|
||||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
.expect("GuestMemory must have one memory region at least")
|
||||||
fdt.end_node(memory_node)?;
|
.raw_value();
|
||||||
} else {
|
let mut current_end = current_start;
|
||||||
// Case 2: RAM is split by the hole
|
|
||||||
// Region 1: RAM before the hole
|
for (start, size) in guest_mem
|
||||||
let mem_size = super::layout::MEM_32BIT_RESERVED_START.raw_value()
|
.iter()
|
||||||
- super::layout::RAM_START.raw_value();
|
.map(|m| (m.start_addr().raw_value(), m.len()))
|
||||||
let mem_reg_prop = [super::layout::RAM_START.raw_value() as u64, mem_size as u64];
|
{
|
||||||
let memory_node_name = format!("memory@{:x}", super::layout::RAM_START.raw_value());
|
if current_end == start {
|
||||||
|
// This zone is continuous with the previous one.
|
||||||
|
current_end += size;
|
||||||
|
} else {
|
||||||
|
ram_regions.push((current_start, current_end));
|
||||||
|
|
||||||
|
current_start = start;
|
||||||
|
current_end = start + size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ram_regions.push((current_start, current_end));
|
||||||
|
|
||||||
|
ram_regions
|
||||||
|
};
|
||||||
|
|
||||||
|
if ram_regions.len() > 2 {
|
||||||
|
panic!(
|
||||||
|
"There should be up to two non-continuous regions, devidided by the
|
||||||
|
gap at the end of 32bit address space."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the memory node for memory region before the gap
|
||||||
|
{
|
||||||
|
let (first_region_start, first_region_end) = ram_regions
|
||||||
|
.first()
|
||||||
|
.expect("There should be at last one memory region");
|
||||||
|
let ram_start = super::layout::RAM_START.raw_value();
|
||||||
|
let mem_32bit_reserved_start = super::layout::MEM_32BIT_RESERVED_START.raw_value();
|
||||||
|
|
||||||
|
if !((first_region_start <= &ram_start)
|
||||||
|
&& (first_region_end > &ram_start)
|
||||||
|
&& (first_region_end <= &mem_32bit_reserved_start))
|
||||||
|
{
|
||||||
|
panic!(
|
||||||
|
"Unexpected first memory region layout: (start: 0x{:08x}, end: 0x{:08x}).
|
||||||
|
ram_start: 0x{:08x}, mem_32bit_reserved_start: 0x{:08x}",
|
||||||
|
first_region_start, first_region_end, ram_start, mem_32bit_reserved_start
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mem_size = first_region_end - ram_start;
|
||||||
|
let mem_reg_prop = [ram_start, mem_size];
|
||||||
|
let memory_node_name = format!("memory@{:x}", ram_start);
|
||||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||||
fdt.property_string("device_type", "memory")?;
|
fdt.property_string("device_type", "memory")?;
|
||||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||||
fdt.end_node(memory_node)?;
|
fdt.end_node(memory_node)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Region 2: RAM after the hole
|
// Create the memory map entry for memory region after the gap if any
|
||||||
let mem_size = last_addr - super::layout::RAM_64BIT_START.raw_value() + 1;
|
if let Some((second_region_start, second_region_end)) = ram_regions.get(1) {
|
||||||
let mem_reg_prop = [
|
let ram_64bit_start = super::layout::RAM_64BIT_START.raw_value();
|
||||||
super::layout::RAM_64BIT_START.raw_value() as u64,
|
|
||||||
mem_size as u64,
|
if second_region_start != &ram_64bit_start {
|
||||||
];
|
panic!(
|
||||||
let memory_node_name =
|
"Unexpected second memory region layout: start: 0x{:08x}, ram_64bit_start: 0x{:08x}",
|
||||||
format!("memory@{:x}", super::layout::RAM_64BIT_START.raw_value());
|
second_region_start, ram_64bit_start
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mem_size = second_region_end - ram_64bit_start;
|
||||||
|
let mem_reg_prop = [ram_64bit_start, mem_size];
|
||||||
|
let memory_node_name = format!("memory@{:x}", ram_64bit_start);
|
||||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||||
fdt.property_string("device_type", "memory")?;
|
fdt.property_string("device_type", "memory")?;
|
||||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||||
@@ -303,7 +638,7 @@ fn create_chosen_node(
|
|||||||
fdt.property_string("bootargs", cmdline)?;
|
fdt.property_string("bootargs", cmdline)?;
|
||||||
|
|
||||||
if let Some(initrd_config) = initrd {
|
if let Some(initrd_config) = initrd {
|
||||||
let initrd_start = initrd_config.address.raw_value() as u64;
|
let initrd_start = initrd_config.address.raw_value();
|
||||||
let initrd_end = initrd_config.address.raw_value() + initrd_config.size 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-start", initrd_start)?;
|
||||||
fdt.property_u64("linux,initrd-end", initrd_end)?;
|
fdt.property_u64("linux,initrd-end", initrd_end)?;
|
||||||
@@ -670,7 +1005,7 @@ fn create_pci_nodes(
|
|||||||
|
|
||||||
// See kernel document Documentation/devicetree/bindings/virtio/iommu.txt
|
// See kernel document Documentation/devicetree/bindings/virtio/iommu.txt
|
||||||
// for virtio-iommu node settings.
|
// for virtio-iommu node settings.
|
||||||
let virtio_iommu_node_name = format!("virtio_iommu@{:x}", virtio_iommu_bdf);
|
let virtio_iommu_node_name = format!("virtio_iommu@{virtio_iommu_bdf:x}");
|
||||||
let virtio_iommu_node = fdt.begin_node(&virtio_iommu_node_name)?;
|
let virtio_iommu_node = fdt.begin_node(&virtio_iommu_node_name)?;
|
||||||
fdt.property_u32("#iommu-cells", 1)?;
|
fdt.property_u32("#iommu-cells", 1)?;
|
||||||
fdt.property_string("compatible", "virtio,pci-iommu")?;
|
fdt.property_string("compatible", "virtio,pci-iommu")?;
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ use std::collections::HashMap;
|
|||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestUsize};
|
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
||||||
|
|
||||||
|
pub const _NSIG: i32 = 65;
|
||||||
|
|
||||||
/// Errors thrown while configuring aarch64 system.
|
/// Errors thrown while configuring aarch64 system.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -64,9 +66,9 @@ pub struct EntryPoint {
|
|||||||
pub fn configure_vcpu(
|
pub fn configure_vcpu(
|
||||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||||
id: u8,
|
id: u8,
|
||||||
kernel_entry_point: Option<EntryPoint>,
|
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
||||||
) -> super::Result<u64> {
|
) -> super::Result<u64> {
|
||||||
if let Some(kernel_entry_point) = kernel_entry_point {
|
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
||||||
vcpu.setup_regs(
|
vcpu.setup_regs(
|
||||||
id,
|
id,
|
||||||
kernel_entry_point.entry_addr.raw_value(),
|
kernel_entry_point.entry_addr.raw_value(),
|
||||||
@@ -81,8 +83,8 @@ pub fn configure_vcpu(
|
|||||||
Ok(mpidr)
|
Ok(mpidr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
|
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
||||||
let mut regions = vec![
|
vec![
|
||||||
// 0 MiB ~ 256 MiB: UEFI, GIC and legacy devices
|
// 0 MiB ~ 256 MiB: UEFI, GIC and legacy devices
|
||||||
(
|
(
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
@@ -101,39 +103,21 @@ pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, Region
|
|||||||
layout::PCI_MMCONFIG_SIZE as usize,
|
layout::PCI_MMCONFIG_SIZE as usize,
|
||||||
RegionType::Reserved,
|
RegionType::Reserved,
|
||||||
),
|
),
|
||||||
];
|
// 1GiB ~ 4032 MiB: RAM before the gap
|
||||||
|
(
|
||||||
let ram_32bit_space_size =
|
|
||||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START);
|
|
||||||
|
|
||||||
// RAM space
|
|
||||||
// Case1: guest memory fits before the gap
|
|
||||||
if size as u64 <= ram_32bit_space_size {
|
|
||||||
regions.push((layout::RAM_START, size as usize, RegionType::Ram));
|
|
||||||
// Case2: guest memory extends beyond the gap
|
|
||||||
} else {
|
|
||||||
// Push memory before the gap
|
|
||||||
regions.push((
|
|
||||||
layout::RAM_START,
|
layout::RAM_START,
|
||||||
ram_32bit_space_size as usize,
|
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START) as usize,
|
||||||
RegionType::Ram,
|
RegionType::Ram,
|
||||||
));
|
),
|
||||||
// Other memory is placed after 4GiB
|
// 4GiB ~ inf: RAM after the gap
|
||||||
regions.push((
|
(layout::RAM_64BIT_START, usize::MAX, RegionType::Ram),
|
||||||
layout::RAM_64BIT_START,
|
// Add the 32-bit reserved memory hole as a reserved region
|
||||||
(size - ram_32bit_space_size) as usize,
|
(
|
||||||
RegionType::Ram,
|
layout::MEM_32BIT_RESERVED_START,
|
||||||
));
|
layout::MEM_32BIT_RESERVED_SIZE as usize,
|
||||||
}
|
RegionType::Reserved,
|
||||||
|
),
|
||||||
// Add the 32-bit reserved memory hole as a reserved region
|
]
|
||||||
regions.push((
|
|
||||||
layout::MEM_32BIT_RESERVED_START,
|
|
||||||
layout::MEM_32BIT_RESERVED_SIZE as usize,
|
|
||||||
RegionType::Reserved,
|
|
||||||
));
|
|
||||||
|
|
||||||
regions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
/// Configures the system and should be called once per vm before starting vcpu threads.
|
||||||
@@ -196,11 +180,8 @@ pub fn initramfs_load_addr(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_host_cpu_phys_bits() -> u8 {
|
pub fn get_host_cpu_phys_bits(hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
|
||||||
// A dummy hypervisor created only for querying the host IPA size and will
|
let host_cpu_phys_bits = hypervisor.get_host_ipa_limit().try_into().unwrap();
|
||||||
// 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 {
|
if host_cpu_phys_bits == 0 {
|
||||||
// Host kernel does not support `get_host_ipa_limit`,
|
// Host kernel does not support `get_host_ipa_limit`,
|
||||||
// we return the default value 40 here.
|
// we return the default value 40 here.
|
||||||
@@ -215,26 +196,12 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_arch_memory_regions_dram_2gb() {
|
fn test_arch_memory_regions_dram() {
|
||||||
let regions = arch_memory_regions((1usize << 31) as u64); //2GB
|
let regions = arch_memory_regions();
|
||||||
assert_eq!(5, regions.len());
|
|
||||||
assert_eq!(layout::RAM_START, regions[3].0);
|
|
||||||
assert_eq!((1usize << 31), regions[3].1);
|
|
||||||
assert_eq!(RegionType::Ram, regions[3].2);
|
|
||||||
assert_eq!(RegionType::Reserved, regions[4].2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_arch_memory_regions_dram_4gb() {
|
|
||||||
let regions = arch_memory_regions((1usize << 32) as u64); //4GB
|
|
||||||
let ram_32bit_space_size =
|
|
||||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START) as usize;
|
|
||||||
assert_eq!(6, regions.len());
|
assert_eq!(6, regions.len());
|
||||||
assert_eq!(layout::RAM_START, regions[3].0);
|
assert_eq!(layout::RAM_START, regions[3].0);
|
||||||
assert_eq!(ram_32bit_space_size as usize, regions[3].1);
|
|
||||||
assert_eq!(RegionType::Ram, regions[3].2);
|
assert_eq!(RegionType::Ram, regions[3].2);
|
||||||
assert_eq!(RegionType::Reserved, regions[5].2);
|
assert_eq!(RegionType::Reserved, regions[5].2);
|
||||||
assert_eq!(RegionType::Ram, regions[4].2);
|
assert_eq!(RegionType::Ram, regions[4].2);
|
||||||
assert_eq!(((1usize << 32) - ram_32bit_space_size), regions[4].1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
|
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
///
|
// AArch64 system register encoding:
|
||||||
/// AArch64 system register encoding:
|
// See https://developer.arm.com/documentation/ddi0487 (chapter D12)
|
||||||
/// See https://developer.arm.com/documentation/ddi0487 (chapter D12)
|
//
|
||||||
///
|
// 31 22 21 20 19 18 16 15 12 11 8 7 5 4 0
|
||||||
/// 31 22 21 20 19 18 16 15 12 11 8 7 5 4 0
|
// +----------+---+-----+-----+-----+-----+-----+----+
|
||||||
/// +----------+---+-----+-----+-----+-----+-----+----+
|
// |1101010100| L | op0 | op1 | CRn | CRm | op2 | Rt |
|
||||||
/// |1101010100| L | op0 | op1 | CRn | CRm | op2 | Rt |
|
// +----------+---+-----+-----+-----+-----+-----+----+
|
||||||
/// +----------+---+-----+-----+-----+-----+-----+----+
|
//
|
||||||
///
|
// Notes:
|
||||||
/// Notes:
|
// - L and Rt are reserved as implementation defined fields, ignored.
|
||||||
/// - L and Rt are reserved as implementation defined fields, ignored.
|
|
||||||
///
|
|
||||||
const SYSREG_HEAD: u32 = 0b1101010100u32 << 22;
|
const SYSREG_HEAD: u32 = 0b1101010100u32 << 22;
|
||||||
const SYSREG_OP0_SHIFT: u32 = 19;
|
const SYSREG_OP0_SHIFT: u32 = 19;
|
||||||
const SYSREG_OP0_MASK: u32 = 0b11u32 << 19;
|
const SYSREG_OP0_MASK: u32 = 0b11u32 << 19;
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ where
|
|||||||
if uefi_size > 0x300000 {
|
if uefi_size > 0x300000 {
|
||||||
return Err(Error::UefiTooBig);
|
return Err(Error::UefiTooBig);
|
||||||
}
|
}
|
||||||
uefi_image
|
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
|
||||||
.seek(SeekFrom::Start(0))
|
|
||||||
.map_err(|_| Error::SeekUefiStart)?;
|
|
||||||
guest_mem
|
guest_mem
|
||||||
.read_exact_from(guest_addr, uefi_image, uefi_size)
|
.read_exact_from(guest_addr, uefi_image, uefi_size)
|
||||||
.map_err(|_| Error::ReadUefiImage)
|
.map_err(|_| Error::ReadUefiImage)
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ pub mod aarch64;
|
|||||||
pub use aarch64::{
|
pub use aarch64::{
|
||||||
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt,
|
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt,
|
||||||
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
||||||
layout::IRQ_BASE, uefi, EntryPoint,
|
layout::IRQ_BASE, uefi, EntryPoint, _NSIG,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
@@ -92,14 +92,14 @@ pub mod x86_64;
|
|||||||
pub use x86_64::{
|
pub use x86_64::{
|
||||||
arch_memory_regions, configure_system, configure_vcpu, generate_common_cpuid,
|
arch_memory_regions, configure_system, configure_vcpu, generate_common_cpuid,
|
||||||
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
||||||
layout::CMDLINE_START, regs, CpuidFeatureEntry, EntryPoint,
|
layout::CMDLINE_START, regs, CpuidFeatureEntry, EntryPoint, _NSIG,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn pagesize() -> usize {
|
fn pagesize() -> usize {
|
||||||
// Trivially safe
|
// SAFETY: Trivially safe
|
||||||
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
|
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ pub const PAGE_SIZE: usize = 4096;
|
|||||||
|
|
||||||
impl fmt::Display for DeviceType {
|
impl fmt::Display for DeviceType {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{:?}", self)
|
write!(f, "{self:?}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use crate::GuestMemoryMmap;
|
|||||||
use crate::InitramfsConfig;
|
use crate::InitramfsConfig;
|
||||||
use crate::RegionType;
|
use crate::RegionType;
|
||||||
use hypervisor::arch::x86::{CpuIdEntry, CPUID_FLAG_VALID_INDEX};
|
use hypervisor::arch::x86::{CpuIdEntry, CPUID_FLAG_VALID_INDEX};
|
||||||
use hypervisor::HypervisorError;
|
use hypervisor::{CpuVendor, HypervisorCpuError, HypervisorError};
|
||||||
use linux_loader::loader::bootparam::boot_params;
|
use linux_loader::loader::bootparam::boot_params;
|
||||||
use linux_loader::loader::elf::start_info::{
|
use linux_loader::loader::elf::start_info::{
|
||||||
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
|
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
|
||||||
@@ -36,9 +36,9 @@ pub mod tdx;
|
|||||||
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
||||||
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
|
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
|
||||||
const MTRR_EDX_BIT: u8 = 12; // Hypervisor ecx bit.
|
const MTRR_EDX_BIT: u8 = 12; // Hypervisor ecx bit.
|
||||||
|
const INVARIANT_TSC_EDX_BIT: u8 = 8; // Invariant TSC bit on 0x8000_0007 EDX
|
||||||
|
|
||||||
// KVM feature bits
|
// KVM feature bits
|
||||||
const KVM_FEATURE_ASYNC_PF_INT_BIT: u8 = 14;
|
|
||||||
#[cfg(feature = "tdx")]
|
#[cfg(feature = "tdx")]
|
||||||
const KVM_FEATURE_CLOCKSOURCE_BIT: u8 = 0;
|
const KVM_FEATURE_CLOCKSOURCE_BIT: u8 = 0;
|
||||||
#[cfg(feature = "tdx")]
|
#[cfg(feature = "tdx")]
|
||||||
@@ -52,13 +52,15 @@ const KVM_FEATURE_ASYNC_PF_VMEXIT_BIT: u8 = 10;
|
|||||||
#[cfg(feature = "tdx")]
|
#[cfg(feature = "tdx")]
|
||||||
const KVM_FEATURE_STEAL_TIME_BIT: u8 = 5;
|
const KVM_FEATURE_STEAL_TIME_BIT: u8 = 5;
|
||||||
|
|
||||||
|
pub const _NSIG: i32 = 65;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
/// Specifies the entry point address where the guest must start
|
/// Specifies the entry point address where the guest must start
|
||||||
/// executing code, as well as which of the supported boot protocols
|
/// executing code, as well as which of the supported boot protocols
|
||||||
/// is to be used to configure the guest initial state.
|
/// is to be used to configure the guest initial state.
|
||||||
pub struct EntryPoint {
|
pub struct EntryPoint {
|
||||||
/// Address in guest memory where the guest must start execution
|
/// Address in guest memory where the guest must start execution
|
||||||
pub entry_addr: Option<GuestAddress>,
|
pub entry_addr: GuestAddress,
|
||||||
}
|
}
|
||||||
|
|
||||||
const E820_RAM: u32 = 1;
|
const E820_RAM: u32 = 1;
|
||||||
@@ -125,9 +127,11 @@ struct MemmapTableEntryWrapper(hvm_memmap_table_entry);
|
|||||||
#[derive(Copy, Clone, Default)]
|
#[derive(Copy, Clone, Default)]
|
||||||
struct ModlistEntryWrapper(hvm_modlist_entry);
|
struct ModlistEntryWrapper(hvm_modlist_entry);
|
||||||
|
|
||||||
// SAFETY: These data structures only contain a series of integers
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for StartInfoWrapper {}
|
unsafe impl ByteValued for StartInfoWrapper {}
|
||||||
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for MemmapTableEntryWrapper {}
|
unsafe impl ByteValued for MemmapTableEntryWrapper {}
|
||||||
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for ModlistEntryWrapper {}
|
unsafe impl ByteValued for ModlistEntryWrapper {}
|
||||||
|
|
||||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||||
@@ -191,6 +195,9 @@ pub enum Error {
|
|||||||
// Error writing EBDA address
|
// Error writing EBDA address
|
||||||
EbdaSetup(vm_memory::GuestMemoryError),
|
EbdaSetup(vm_memory::GuestMemoryError),
|
||||||
|
|
||||||
|
// Error getting CPU TSC frequency
|
||||||
|
GetTscFrequency(HypervisorCpuError),
|
||||||
|
|
||||||
/// Error retrieving TDX capabilities through the hypervisor (kvm/mshv) API
|
/// Error retrieving TDX capabilities through the hypervisor (kvm/mshv) API
|
||||||
#[cfg(feature = "tdx")]
|
#[cfg(feature = "tdx")]
|
||||||
TdxCapabilities(HypervisorError),
|
TdxCapabilities(HypervisorError),
|
||||||
@@ -202,7 +209,6 @@ impl From<Error> for super::Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum CpuidReg {
|
pub enum CpuidReg {
|
||||||
EAX,
|
EAX,
|
||||||
@@ -545,13 +551,30 @@ impl CpuidFeatureEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_common_cpuid(
|
pub fn generate_common_cpuid(
|
||||||
hypervisor: Arc<dyn hypervisor::Hypervisor>,
|
hypervisor: &Arc<dyn hypervisor::Hypervisor>,
|
||||||
topology: Option<(u8, u8, u8)>,
|
topology: Option<(u8, u8, u8)>,
|
||||||
sgx_epc_sections: Option<Vec<SgxEpcSection>>,
|
sgx_epc_sections: Option<Vec<SgxEpcSection>>,
|
||||||
phys_bits: u8,
|
phys_bits: u8,
|
||||||
kvm_hyperv: bool,
|
kvm_hyperv: bool,
|
||||||
#[cfg(feature = "tdx")] tdx_enabled: bool,
|
#[cfg(feature = "tdx")] tdx_enabled: bool,
|
||||||
) -> super::Result<Vec<CpuIdEntry>> {
|
) -> super::Result<Vec<CpuIdEntry>> {
|
||||||
|
// SAFETY: cpuid called with valid leaves
|
||||||
|
if unsafe { x86_64::__cpuid(1) }.ecx & 1 << HYPERVISOR_ECX_BIT == 1 << HYPERVISOR_ECX_BIT {
|
||||||
|
// SAFETY: cpuid called with valid leaves
|
||||||
|
let hypervisor_cpuid = unsafe { x86_64::__cpuid(0x4000_0000) };
|
||||||
|
|
||||||
|
let mut identifier: [u8; 12] = [0; 12];
|
||||||
|
identifier[0..4].copy_from_slice(&hypervisor_cpuid.ebx.to_le_bytes()[..]);
|
||||||
|
identifier[4..8].copy_from_slice(&hypervisor_cpuid.ecx.to_le_bytes()[..]);
|
||||||
|
identifier[8..12].copy_from_slice(&hypervisor_cpuid.edx.to_le_bytes()[..]);
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Running under nested virtualisation. Hypervisor string: {}",
|
||||||
|
String::from_utf8_lossy(&identifier)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Generating guest CPUID for with physical address size: {phys_bits}");
|
||||||
let cpuid_patches = vec![
|
let cpuid_patches = vec![
|
||||||
// Patch tsc deadline timer bit
|
// Patch tsc deadline timer bit
|
||||||
CpuidPatch {
|
CpuidPatch {
|
||||||
@@ -586,7 +609,9 @@ pub fn generate_common_cpuid(
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Supported CPUID
|
// Supported CPUID
|
||||||
let mut cpuid = hypervisor.get_cpuid().map_err(Error::CpuidGetSupported)?;
|
let mut cpuid = hypervisor
|
||||||
|
.get_supported_cpuid()
|
||||||
|
.map_err(Error::CpuidGetSupported)?;
|
||||||
|
|
||||||
CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches);
|
CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches);
|
||||||
|
|
||||||
@@ -631,18 +656,25 @@ pub fn generate_common_cpuid(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Copy host L2 cache details if not populated by KVM
|
||||||
|
0x8000_0006 => {
|
||||||
|
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
||||||
|
// SAFETY: cpuid called with valid leaves
|
||||||
|
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0006 {
|
||||||
|
// SAFETY: cpuid called with valid leaves
|
||||||
|
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0006) };
|
||||||
|
entry.eax = leaf.eax;
|
||||||
|
entry.ebx = leaf.ebx;
|
||||||
|
entry.ecx = leaf.ecx;
|
||||||
|
entry.edx = leaf.edx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Set CPU physical bits
|
// Set CPU physical bits
|
||||||
0x8000_0008 => {
|
0x8000_0008 => {
|
||||||
entry.eax = (entry.eax & 0xffff_ff00) | (phys_bits as u32 & 0xff);
|
entry.eax = (entry.eax & 0xffff_ff00) | (phys_bits as u32 & 0xff);
|
||||||
}
|
}
|
||||||
// Disable KVM_FEATURE_ASYNC_PF_INT
|
|
||||||
// This is required until we find out why the asynchronous page
|
|
||||||
// fault is generating unexpected behavior when using interrupt
|
|
||||||
// mechanism.
|
|
||||||
// TODO: Re-enable KVM_FEATURE_ASYNC_PF_INT (#2277)
|
|
||||||
0x4000_0001 => {
|
0x4000_0001 => {
|
||||||
entry.eax &= !(1 << KVM_FEATURE_ASYNC_PF_INT_BIT);
|
|
||||||
|
|
||||||
// These features are not supported by TDX
|
// These features are not supported by TDX
|
||||||
#[cfg(feature = "tdx")]
|
#[cfg(feature = "tdx")]
|
||||||
if tdx_enabled {
|
if tdx_enabled {
|
||||||
@@ -661,6 +693,7 @@ pub fn generate_common_cpuid(
|
|||||||
// Copy CPU identification string
|
// Copy CPU identification string
|
||||||
for i in 0x8000_0002..=0x8000_0004 {
|
for i in 0x8000_0002..=0x8000_0004 {
|
||||||
cpuid.retain(|c| c.function != i);
|
cpuid.retain(|c| c.function != i);
|
||||||
|
// SAFETY: call cpuid with valid leaves
|
||||||
let leaf = unsafe { std::arch::x86_64::__cpuid(i) };
|
let leaf = unsafe { std::arch::x86_64::__cpuid(i) };
|
||||||
cpuid.push(CpuIdEntry {
|
cpuid.push(CpuIdEntry {
|
||||||
function: i,
|
function: i,
|
||||||
@@ -725,8 +758,7 @@ pub fn generate_common_cpuid(
|
|||||||
pub fn configure_vcpu(
|
pub fn configure_vcpu(
|
||||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||||
id: u8,
|
id: u8,
|
||||||
kernel_entry_point: Option<EntryPoint>,
|
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
||||||
vm_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
|
|
||||||
cpuid: Vec<CpuIdEntry>,
|
cpuid: Vec<CpuIdEntry>,
|
||||||
kvm_hyperv: bool,
|
kvm_hyperv: bool,
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
@@ -735,6 +767,41 @@ pub fn configure_vcpu(
|
|||||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0xb, None, CpuidReg::EDX, u32::from(id));
|
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));
|
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x1f, None, CpuidReg::EDX, u32::from(id));
|
||||||
|
|
||||||
|
// Set ApicId in cpuid for each vcpu
|
||||||
|
// SAFETY: get host cpuid when eax=1
|
||||||
|
let mut cpu_ebx = unsafe { core::arch::x86_64::__cpuid(1) }.ebx;
|
||||||
|
cpu_ebx &= 0xffffff;
|
||||||
|
cpu_ebx |= (id as u32) << 24;
|
||||||
|
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x1, None, CpuidReg::EBX, cpu_ebx);
|
||||||
|
|
||||||
|
// The TSC frequency CPUID leaf should not be included when running with HyperV emulation
|
||||||
|
if !kvm_hyperv {
|
||||||
|
if let Some(tsc_khz) = vcpu.tsc_khz().map_err(Error::GetTscFrequency)? {
|
||||||
|
// Need to check that the TSC doesn't vary with dynamic frequency
|
||||||
|
// SAFETY: cpuid called with valid leaves
|
||||||
|
if unsafe { std::arch::x86_64::__cpuid(0x8000_0007) }.edx
|
||||||
|
& (1u32 << INVARIANT_TSC_EDX_BIT)
|
||||||
|
> 0
|
||||||
|
{
|
||||||
|
CpuidPatch::set_cpuid_reg(
|
||||||
|
&mut cpuid,
|
||||||
|
0x4000_0000,
|
||||||
|
None,
|
||||||
|
CpuidReg::EAX,
|
||||||
|
0x4000_0010,
|
||||||
|
);
|
||||||
|
cpuid.retain(|c| c.function != 0x4000_0010);
|
||||||
|
cpuid.push(CpuIdEntry {
|
||||||
|
function: 0x4000_0010,
|
||||||
|
eax: tsc_khz,
|
||||||
|
ebx: 1000000, /* LAPIC resolution of 1ns (freq: 1GHz) is hardcoded in KVM's
|
||||||
|
* APIC_BUS_CYCLE_NS */
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
vcpu.set_cpuid2(&cpuid)
|
vcpu.set_cpuid2(&cpuid)
|
||||||
.map_err(|e| Error::SetSupportedCpusFailed(e.into()))?;
|
.map_err(|e| Error::SetSupportedCpusFailed(e.into()))?;
|
||||||
|
|
||||||
@@ -743,13 +810,11 @@ pub fn configure_vcpu(
|
|||||||
}
|
}
|
||||||
|
|
||||||
regs::setup_msrs(vcpu).map_err(Error::MsrsConfiguration)?;
|
regs::setup_msrs(vcpu).map_err(Error::MsrsConfiguration)?;
|
||||||
if let Some(kernel_entry_point) = kernel_entry_point {
|
if let Some((kernel_entry_point, guest_memory)) = boot_setup {
|
||||||
if let Some(entry_addr) = kernel_entry_point.entry_addr {
|
regs::setup_regs(vcpu, kernel_entry_point.entry_addr.raw_value())
|
||||||
// Safe to unwrap because this method is called after the VM is configured
|
.map_err(Error::RegsConfiguration)?;
|
||||||
regs::setup_regs(vcpu, entry_addr.raw_value()).map_err(Error::RegsConfiguration)?;
|
regs::setup_fpu(vcpu).map_err(Error::FpuConfiguration)?;
|
||||||
regs::setup_fpu(vcpu).map_err(Error::FpuConfiguration)?;
|
regs::setup_sregs(&guest_memory.memory(), vcpu).map_err(Error::SregsConfiguration)?;
|
||||||
regs::setup_sregs(&vm_memory.memory(), vcpu).map_err(Error::SregsConfiguration)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
interrupts::set_lint(vcpu).map_err(|e| Error::LocalIntConfiguration(e.into()))?;
|
interrupts::set_lint(vcpu).map_err(|e| Error::LocalIntConfiguration(e.into()))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -759,47 +824,29 @@ pub fn configure_vcpu(
|
|||||||
/// These should be used to configure the GuestMemory structure for the platform.
|
/// These should be used to configure the GuestMemory structure for the platform.
|
||||||
/// For x86_64 all addresses are valid from the start of the kernel except a
|
/// For x86_64 all addresses are valid from the start of the kernel except a
|
||||||
/// carve out at the end of 32bit address space.
|
/// carve out at the end of 32bit address space.
|
||||||
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
|
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
||||||
let reserved_memory_gap_start = layout::MEM_32BIT_RESERVED_START
|
vec![
|
||||||
.checked_add(layout::MEM_32BIT_DEVICES_SIZE)
|
// 0 GiB ~ 3GiB: memory before the gap
|
||||||
.expect("32-bit reserved region is too large");
|
(
|
||||||
|
|
||||||
let requested_memory_size = GuestAddress(size);
|
|
||||||
let mut regions = Vec::new();
|
|
||||||
|
|
||||||
// case1: guest memory fits before the gap
|
|
||||||
if size <= layout::MEM_32BIT_RESERVED_START.raw_value() {
|
|
||||||
regions.push((GuestAddress(0), size as usize, RegionType::Ram));
|
|
||||||
// case2: guest memory extends beyond the gap
|
|
||||||
} else {
|
|
||||||
// push memory before the gap
|
|
||||||
regions.push((
|
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
layout::MEM_32BIT_RESERVED_START.raw_value() as usize,
|
layout::MEM_32BIT_RESERVED_START.raw_value() as usize,
|
||||||
RegionType::Ram,
|
RegionType::Ram,
|
||||||
));
|
),
|
||||||
regions.push((
|
// 4 GiB ~ inf: memory after the gap
|
||||||
layout::RAM_64BIT_START,
|
(layout::RAM_64BIT_START, usize::MAX, RegionType::Ram),
|
||||||
requested_memory_size.unchecked_offset_from(layout::MEM_32BIT_RESERVED_START) as usize,
|
// 3 GiB ~ 3712 MiB: 32-bit device memory hole
|
||||||
RegionType::Ram,
|
(
|
||||||
));
|
layout::MEM_32BIT_RESERVED_START,
|
||||||
}
|
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
||||||
|
RegionType::SubRegion,
|
||||||
// Add the 32-bit device memory hole as a sub region.
|
),
|
||||||
regions.push((
|
// 3712 MiB ~ 3968 MiB: 32-bit reserved memory hole
|
||||||
layout::MEM_32BIT_RESERVED_START,
|
(
|
||||||
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
layout::MEM_32BIT_RESERVED_START.unchecked_add(layout::MEM_32BIT_DEVICES_SIZE),
|
||||||
RegionType::SubRegion,
|
(layout::MEM_32BIT_RESERVED_SIZE - layout::MEM_32BIT_DEVICES_SIZE) as usize,
|
||||||
));
|
RegionType::Reserved,
|
||||||
|
),
|
||||||
// Add the 32-bit reserved memory hole as a sub region.
|
]
|
||||||
regions.push((
|
|
||||||
reserved_memory_gap_start,
|
|
||||||
(layout::MEM_32BIT_RESERVED_SIZE - layout::MEM_32BIT_DEVICES_SIZE) as usize,
|
|
||||||
RegionType::Reserved,
|
|
||||||
));
|
|
||||||
|
|
||||||
regions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
/// Configures the system and should be called once per vm before starting vcpu threads.
|
||||||
@@ -897,30 +944,102 @@ fn configure_pvh(
|
|||||||
// Create the memory map entries.
|
// 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();
|
// Merge continuous memory regions into one region.
|
||||||
|
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
|
||||||
|
let ram_regions = {
|
||||||
|
let mut ram_regions = Vec::new();
|
||||||
|
let mut current_start = guest_mem
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.map(GuestMemoryRegion::start_addr)
|
||||||
|
.expect("GuestMemory must have one memory region at least")
|
||||||
|
.raw_value();
|
||||||
|
let mut current_end = current_start;
|
||||||
|
|
||||||
if mem_end < layout::MEM_32BIT_RESERVED_START {
|
for (start, size) in guest_mem
|
||||||
add_memmap_entry(
|
.iter()
|
||||||
&mut memmap,
|
.map(|m| (m.start_addr().raw_value(), m.len()))
|
||||||
layout::HIGH_RAM_START.raw_value(),
|
{
|
||||||
mem_end.unchecked_offset_from(layout::HIGH_RAM_START) + 1,
|
if current_end == start {
|
||||||
E820_RAM,
|
// This zone is continuous with the previous one.
|
||||||
);
|
current_end += size;
|
||||||
} else {
|
} else {
|
||||||
add_memmap_entry(
|
ram_regions.push((current_start, current_end));
|
||||||
&mut memmap,
|
|
||||||
layout::HIGH_RAM_START.raw_value(),
|
current_start = start;
|
||||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::HIGH_RAM_START),
|
current_end = start + size;
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ram_regions.push((current_start, current_end));
|
||||||
|
|
||||||
|
ram_regions
|
||||||
|
};
|
||||||
|
|
||||||
|
if ram_regions.len() > 2 {
|
||||||
|
error!(
|
||||||
|
"There should be up to two non-continuous regions, devidided by the
|
||||||
|
gap at the end of 32bit address space (e.g. between 3G and 4G)."
|
||||||
|
);
|
||||||
|
return Err(super::Error::MemmapTableSetup);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the memory map entry for memory region before the gap
|
||||||
|
{
|
||||||
|
let (first_region_start, first_region_end) =
|
||||||
|
ram_regions.first().ok_or(super::Error::MemmapTableSetup)?;
|
||||||
|
let high_ram_start = layout::HIGH_RAM_START.raw_value();
|
||||||
|
let mem_32bit_reserved_start = layout::MEM_32BIT_RESERVED_START.raw_value();
|
||||||
|
|
||||||
|
if !((first_region_start <= &high_ram_start)
|
||||||
|
&& (first_region_end > &high_ram_start)
|
||||||
|
&& (first_region_end <= &mem_32bit_reserved_start))
|
||||||
|
{
|
||||||
|
error!(
|
||||||
|
"Unexpected first memory region layout: (start: 0x{:08x}, end: 0x{:08x}).
|
||||||
|
high_ram_start: 0x{:08x}, mem_32bit_reserved_start: 0x{:08x}",
|
||||||
|
first_region_start, first_region_end, high_ram_start, mem_32bit_reserved_start
|
||||||
|
);
|
||||||
|
|
||||||
|
return Err(super::Error::MemmapTableSetup);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"create_memmap_entry, start: 0x{:08x}, end: 0x{:08x}",
|
||||||
|
high_ram_start, first_region_end
|
||||||
|
);
|
||||||
|
|
||||||
|
add_memmap_entry(
|
||||||
|
&mut memmap,
|
||||||
|
high_ram_start,
|
||||||
|
first_region_end - high_ram_start,
|
||||||
|
E820_RAM,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the memory map entry for memory region after the gap if any
|
||||||
|
if let Some((second_region_start, second_region_end)) = ram_regions.get(1) {
|
||||||
|
let ram_64bit_start = layout::RAM_64BIT_START.raw_value();
|
||||||
|
|
||||||
|
if second_region_start != &ram_64bit_start {
|
||||||
|
error!(
|
||||||
|
"Unexpected second memory region layout: start: 0x{:08x}, ram_64bit_start: 0x{:08x}",
|
||||||
|
second_region_start, ram_64bit_start
|
||||||
|
);
|
||||||
|
|
||||||
|
return Err(super::Error::MemmapTableSetup);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"create_memmap_entry, start: 0x{:08x}, end: 0x{:08x}",
|
||||||
|
ram_64bit_start, second_region_end
|
||||||
|
);
|
||||||
|
add_memmap_entry(
|
||||||
|
&mut memmap,
|
||||||
|
ram_64bit_start,
|
||||||
|
second_region_end - ram_64bit_start,
|
||||||
|
E820_RAM,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
add_memmap_entry(
|
add_memmap_entry(
|
||||||
@@ -1010,7 +1129,8 @@ pub fn initramfs_load_addr(
|
|||||||
Ok(aligned_addr)
|
Ok(aligned_addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_host_cpu_phys_bits() -> u8 {
|
pub fn get_host_cpu_phys_bits(hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
|
||||||
|
// SAFETY: call cpuid with valid leaves
|
||||||
unsafe {
|
unsafe {
|
||||||
let leaf = x86_64::__cpuid(0x8000_0000);
|
let leaf = x86_64::__cpuid(0x8000_0000);
|
||||||
|
|
||||||
@@ -1018,9 +1138,7 @@ pub fn get_host_cpu_phys_bits() -> u8 {
|
|||||||
// Some physical address bits may become reserved when the feature is enabled.
|
// Some physical address bits may become reserved when the feature is enabled.
|
||||||
// See AMD64 Architecture Programmer's Manual Volume 2, Section 7.10.1
|
// See AMD64 Architecture Programmer's Manual Volume 2, Section 7.10.1
|
||||||
let reduced = if leaf.eax >= 0x8000_001f
|
let reduced = if leaf.eax >= 0x8000_001f
|
||||||
&& leaf.ebx == 0x6874_7541 // Vendor ID: AuthenticAMD
|
&& matches!(hypervisor.get_cpu_vendor(), CpuVendor::AMD)
|
||||||
&& leaf.ecx == 0x444d_4163
|
|
||||||
&& leaf.edx == 0x6974_6e65
|
|
||||||
&& x86_64::__cpuid(0x8000_001f).eax & 0x1 != 0
|
&& x86_64::__cpuid(0x8000_001f).eax & 0x1 != 0
|
||||||
{
|
{
|
||||||
(x86_64::__cpuid(0x8000_001f).ebx >> 6) & 0x3f
|
(x86_64::__cpuid(0x8000_001f).ebx >> 6) & 0x3f
|
||||||
@@ -1121,6 +1239,7 @@ fn update_cpuid_sgx(
|
|||||||
|
|
||||||
// Get host CPUID for leaf 0x12, subleaf 0x2. This is to retrieve EPC
|
// Get host CPUID for leaf 0x12, subleaf 0x2. This is to retrieve EPC
|
||||||
// properties such as confidentiality and integrity.
|
// properties such as confidentiality and integrity.
|
||||||
|
// SAFETY: call cpuid with valid leaves
|
||||||
let leaf = unsafe { std::arch::x86_64::__cpuid_count(0x12, 0x2) };
|
let leaf = unsafe { std::arch::x86_64::__cpuid_count(0x12, 0x2) };
|
||||||
|
|
||||||
for (i, epc_section) in epc_sections.iter().enumerate() {
|
for (i, epc_section) in epc_sections.iter().enumerate() {
|
||||||
@@ -1154,16 +1273,8 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn regions_lt_4gb() {
|
fn regions_base_addr() {
|
||||||
let regions = arch_memory_regions(1 << 29);
|
let regions = arch_memory_regions();
|
||||||
assert_eq!(3, regions.len());
|
|
||||||
assert_eq!(GuestAddress(0), regions[0].0);
|
|
||||||
assert_eq!(1usize << 29, regions[0].1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn regions_gt_4gb() {
|
|
||||||
let regions = arch_memory_regions((1 << 32) + 0x8000);
|
|
||||||
assert_eq!(4, regions.len());
|
assert_eq!(4, regions.len());
|
||||||
assert_eq!(GuestAddress(0), regions[0].0);
|
assert_eq!(GuestAddress(0), regions[0].0);
|
||||||
assert_eq!(GuestAddress(1 << 32), regions[1].0);
|
assert_eq!(GuestAddress(1 << 32), regions[1].0);
|
||||||
@@ -1187,11 +1298,10 @@ mod tests {
|
|||||||
assert!(config_err.is_err());
|
assert!(config_err.is_err());
|
||||||
|
|
||||||
// Now assigning some memory that falls before the 32bit memory hole.
|
// Now assigning some memory that falls before the 32bit memory hole.
|
||||||
let mem_size = 128 << 20;
|
let arch_mem_regions = arch_memory_regions();
|
||||||
let arch_mem_regions = arch_memory_regions(mem_size);
|
|
||||||
let ram_regions: Vec<(GuestAddress, usize)> = arch_mem_regions
|
let ram_regions: Vec<(GuestAddress, usize)> = arch_mem_regions
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|r| r.2 == RegionType::Ram)
|
.filter(|r| r.2 == RegionType::Ram && r.1 != usize::MAX)
|
||||||
.map(|r| (r.0, r.1))
|
.map(|r| (r.0, r.1))
|
||||||
.collect();
|
.collect();
|
||||||
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
||||||
@@ -1209,48 +1319,18 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Now assigning some memory that is equal to the start of the 32bit memory hole.
|
|
||||||
let mem_size = 3328 << 20;
|
|
||||||
let arch_mem_regions = arch_memory_regions(mem_size);
|
|
||||||
let ram_regions: Vec<(GuestAddress, usize)> = arch_mem_regions
|
|
||||||
.iter()
|
|
||||||
.filter(|r| r.2 == RegionType::Ram)
|
|
||||||
.map(|r| (r.0, r.1))
|
|
||||||
.collect();
|
|
||||||
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
|
||||||
configure_system(
|
|
||||||
&gm,
|
|
||||||
GuestAddress(0),
|
|
||||||
&None,
|
|
||||||
no_vcpus,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
configure_system(
|
|
||||||
&gm,
|
|
||||||
GuestAddress(0),
|
|
||||||
&None,
|
|
||||||
no_vcpus,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Now assigning some memory that falls after the 32bit memory hole.
|
// Now assigning some memory that falls after the 32bit memory hole.
|
||||||
let mem_size = 3330 << 20;
|
let arch_mem_regions = arch_memory_regions();
|
||||||
let arch_mem_regions = arch_memory_regions(mem_size);
|
|
||||||
let ram_regions: Vec<(GuestAddress, usize)> = arch_mem_regions
|
let ram_regions: Vec<(GuestAddress, usize)> = arch_mem_regions
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|r| r.2 == RegionType::Ram)
|
.filter(|r| r.2 == RegionType::Ram)
|
||||||
.map(|r| (r.0, r.1))
|
.map(|r| {
|
||||||
|
if r.1 == usize::MAX {
|
||||||
|
(r.0, 128 << 20)
|
||||||
|
} else {
|
||||||
|
(r.0, r.1)
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
||||||
configure_system(
|
configure_system(
|
||||||
@@ -1302,6 +1382,6 @@ mod tests {
|
|||||||
add_memmap_entry(&mut memmap, 0, 0x1000, E820_RAM);
|
add_memmap_entry(&mut memmap, 0, 0x1000, E820_RAM);
|
||||||
add_memmap_entry(&mut memmap, 0x10000, 0xa000, E820_RESERVED);
|
add_memmap_entry(&mut memmap, 0x10000, 0xa000, E820_RESERVED);
|
||||||
|
|
||||||
assert_eq!(format!("{:?}", memmap), format!("{:?}", expected_memmap));
|
assert_eq!(format!("{memmap:?}"), format!("{expected_memmap:?}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,11 +37,17 @@ struct MpfIntelWrapper(mpspec::mpf_intel);
|
|||||||
|
|
||||||
// SAFETY: These `mpspec` wrapper types are only data, reading them from data is a safe initialization.
|
// SAFETY: These `mpspec` wrapper types are only data, reading them from data is a safe initialization.
|
||||||
unsafe impl ByteValued for MpcBusWrapper {}
|
unsafe impl ByteValued for MpcBusWrapper {}
|
||||||
|
// SAFETY: see above
|
||||||
unsafe impl ByteValued for MpcCpuWrapper {}
|
unsafe impl ByteValued for MpcCpuWrapper {}
|
||||||
|
// SAFETY: see above
|
||||||
unsafe impl ByteValued for MpcIntsrcWrapper {}
|
unsafe impl ByteValued for MpcIntsrcWrapper {}
|
||||||
|
// SAFETY: see above
|
||||||
unsafe impl ByteValued for MpcIoapicWrapper {}
|
unsafe impl ByteValued for MpcIoapicWrapper {}
|
||||||
|
// SAFETY: see above
|
||||||
unsafe impl ByteValued for MpcTableWrapper {}
|
unsafe impl ByteValued for MpcTableWrapper {}
|
||||||
|
// SAFETY: see above
|
||||||
unsafe impl ByteValued for MpcLintsrcWrapper {}
|
unsafe impl ByteValued for MpcLintsrcWrapper {}
|
||||||
|
// SAFETY: see above
|
||||||
unsafe impl ByteValued for MpfIntelWrapper {}
|
unsafe impl ByteValued for MpfIntelWrapper {}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -95,7 +101,7 @@ const CPU_FEATURE_APIC: u32 = 0x200;
|
|||||||
const CPU_FEATURE_FPU: u32 = 0x001;
|
const CPU_FEATURE_FPU: u32 = 0x001;
|
||||||
|
|
||||||
fn compute_checksum<T: Copy>(v: &T) -> u8 {
|
fn compute_checksum<T: Copy>(v: &T) -> u8 {
|
||||||
// Safe because we are only reading the bytes within the size of the `T` reference `v`.
|
// SAFETY: we are only reading the bytes within the size of the `T` reference `v`.
|
||||||
let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
|
let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
|
||||||
let mut checksum: u8 = 0;
|
let mut checksum: u8 = 0;
|
||||||
for i in v_slice.iter() {
|
for i in v_slice.iter() {
|
||||||
@@ -294,7 +300,7 @@ mod tests {
|
|||||||
mpspec::MP_IOAPIC => mem::size_of::<MpcIoapicWrapper>(),
|
mpspec::MP_IOAPIC => mem::size_of::<MpcIoapicWrapper>(),
|
||||||
mpspec::MP_INTSRC => mem::size_of::<MpcIntsrcWrapper>(),
|
mpspec::MP_INTSRC => mem::size_of::<MpcIntsrcWrapper>(),
|
||||||
mpspec::MP_LINTSRC => mem::size_of::<MpcLintsrcWrapper>(),
|
mpspec::MP_LINTSRC => mem::size_of::<MpcLintsrcWrapper>(),
|
||||||
_ => panic!("unrecognized mpc table entry type: {}", type_),
|
_ => panic!("unrecognized mpc table entry type: {type_}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ impl Display for Error {
|
|||||||
Clear => "Failure while zeroing out the memory for the SMBIOS table".to_string(),
|
Clear => "Failure while zeroing out the memory for the SMBIOS table".to_string(),
|
||||||
WriteSmbiosEp => "Failure to write SMBIOS entrypoint structure".to_string(),
|
WriteSmbiosEp => "Failure to write SMBIOS entrypoint structure".to_string(),
|
||||||
WriteData => "Failure to write additional data to memory".to_string(),
|
WriteData => "Failure to write additional data to memory".to_string(),
|
||||||
ParseUuid(e) => format!("Failure to parse uuid: {}", e),
|
ParseUuid(e) => format!("Failure to parse uuid: {e}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
write!(f, "SMBIOS error: {}", description)
|
write!(f, "SMBIOS error: {description}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ const PCI_SUPPORTED: u64 = 1 << 7;
|
|||||||
const IS_VIRTUAL_MACHINE: u8 = 1 << 4;
|
const IS_VIRTUAL_MACHINE: u8 = 1 << 4;
|
||||||
|
|
||||||
fn compute_checksum<T: Copy>(v: &T) -> u8 {
|
fn compute_checksum<T: Copy>(v: &T) -> u8 {
|
||||||
// Safe because we are only reading the bytes within the size of the `T` reference `v`.
|
// SAFETY: we are only reading the bytes within the size of the `T` reference `v`.
|
||||||
let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
|
let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
|
||||||
let mut checksum: u8 = 0;
|
let mut checksum: u8 = 0;
|
||||||
for i in v_slice.iter() {
|
for i in v_slice.iter() {
|
||||||
@@ -145,11 +145,15 @@ struct SmbiosEndOfTable {
|
|||||||
handle: u16,
|
handle: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: These data structures only contain a series of integers
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for Smbios30Entrypoint {}
|
unsafe impl ByteValued for Smbios30Entrypoint {}
|
||||||
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for SmbiosBiosInfo {}
|
unsafe impl ByteValued for SmbiosBiosInfo {}
|
||||||
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for SmbiosSysInfo {}
|
unsafe impl ByteValued for SmbiosSysInfo {}
|
||||||
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for SmbiosOemStrings {}
|
unsafe impl ByteValued for SmbiosOemStrings {}
|
||||||
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for SmbiosEndOfTable {}
|
unsafe impl ByteValued for SmbiosEndOfTable {}
|
||||||
|
|
||||||
fn write_and_incr<T: ByteValued>(
|
fn write_and_incr<T: ByteValued>(
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
use crate::GuestMemoryMmap;
|
use crate::GuestMemoryMmap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
use std::str::FromStr;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use uuid::Uuid;
|
||||||
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
|
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
@@ -13,6 +15,8 @@ pub enum TdvfError {
|
|||||||
ReadDescriptor(#[source] std::io::Error),
|
ReadDescriptor(#[source] std::io::Error),
|
||||||
#[error("Failed read TDVF descriptor offset: {0}")]
|
#[error("Failed read TDVF descriptor offset: {0}")]
|
||||||
ReadDescriptorOffset(#[source] std::io::Error),
|
ReadDescriptorOffset(#[source] std::io::Error),
|
||||||
|
#[error("Failed read GUID table: {0}")]
|
||||||
|
ReadGuidTable(#[source] std::io::Error),
|
||||||
#[error("Invalid descriptor signature")]
|
#[error("Invalid descriptor signature")]
|
||||||
InvalidDescriptorSignature,
|
InvalidDescriptorSignature,
|
||||||
#[error("Invalid descriptor size")]
|
#[error("Invalid descriptor size")]
|
||||||
@@ -21,8 +25,13 @@ pub enum TdvfError {
|
|||||||
InvalidDescriptorVersion,
|
InvalidDescriptorVersion,
|
||||||
#[error("Failed to write HOB details to guest memory: {0}")]
|
#[error("Failed to write HOB details to guest memory: {0}")]
|
||||||
GuestMemoryWriteHob(#[source] GuestMemoryError),
|
GuestMemoryWriteHob(#[source] GuestMemoryError),
|
||||||
|
#[error("Failed to create Uuid: {0}")]
|
||||||
|
UuidCreation(#[source] uuid::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TABLE_FOOTER_GUID: &str = "96b582de-1fb2-45f7-baea-a366c55a082d";
|
||||||
|
const TDVF_METADATA_OFFSET_GUID: &str = "e47a6535-984a-4798-865e-4685a7bf8ec2";
|
||||||
|
|
||||||
// TDVF_DESCRIPTOR
|
// TDVF_DESCRIPTOR
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -46,7 +55,7 @@ pub struct TdvfSection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub enum TdvfSectionType {
|
pub enum TdvfSectionType {
|
||||||
Bfv,
|
Bfv,
|
||||||
Cfv,
|
Cfv,
|
||||||
@@ -55,16 +64,76 @@ pub enum TdvfSectionType {
|
|||||||
PermMem,
|
PermMem,
|
||||||
Payload,
|
Payload,
|
||||||
PayloadParam,
|
PayloadParam,
|
||||||
|
#[default]
|
||||||
Reserved = 0xffffffff,
|
Reserved = 0xffffffff,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TdvfSectionType {
|
fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError> {
|
||||||
fn default() -> Self {
|
// Let's first try to identify the presence of the table footer GUID
|
||||||
TdvfSectionType::Reserved
|
file.seek(SeekFrom::End(-0x30))
|
||||||
}
|
.map_err(TdvfError::ReadGuidTable)?;
|
||||||
}
|
let mut table_footer_guid: [u8; 16] = [0; 16];
|
||||||
|
file.read_exact(&mut table_footer_guid)
|
||||||
|
.map_err(TdvfError::ReadGuidTable)?;
|
||||||
|
let uuid =
|
||||||
|
Uuid::from_slice_le(table_footer_guid.as_slice()).map_err(TdvfError::UuidCreation)?;
|
||||||
|
let expected_uuid = Uuid::from_str(TABLE_FOOTER_GUID).map_err(TdvfError::UuidCreation)?;
|
||||||
|
if uuid == expected_uuid {
|
||||||
|
// Retrieve the table size
|
||||||
|
file.seek(SeekFrom::End(-0x32))
|
||||||
|
.map_err(TdvfError::ReadGuidTable)?;
|
||||||
|
let mut table_size: [u8; 2] = [0; 2];
|
||||||
|
file.read_exact(&mut table_size)
|
||||||
|
.map_err(TdvfError::ReadGuidTable)?;
|
||||||
|
let table_size = u16::from_le_bytes(table_size) as usize;
|
||||||
|
let mut table: Vec<u8> = vec![0; table_size];
|
||||||
|
|
||||||
|
// Read the entire table
|
||||||
|
file.seek(SeekFrom::End(-(table_size as i64 + 0x20)))
|
||||||
|
.map_err(TdvfError::ReadGuidTable)?;
|
||||||
|
file.read_exact(table.as_mut_slice())
|
||||||
|
.map_err(TdvfError::ReadGuidTable)?;
|
||||||
|
|
||||||
|
// Let's start from the top and go backward down the table.
|
||||||
|
// We start after the footer GUID and the table length.
|
||||||
|
let mut offset = table_size - 18;
|
||||||
|
|
||||||
|
debug!("Parsing GUID structure");
|
||||||
|
while offset >= 18 {
|
||||||
|
let entry_uuid = Uuid::from_slice_le(&table[offset - 16..offset])
|
||||||
|
.map_err(TdvfError::UuidCreation)?;
|
||||||
|
let entry_size =
|
||||||
|
u16::from_le_bytes(table[offset - 18..offset - 16].try_into().unwrap()) as usize;
|
||||||
|
debug!(
|
||||||
|
"Entry GUID = {}, size = {}",
|
||||||
|
entry_uuid.hyphenated().to_string(),
|
||||||
|
entry_size
|
||||||
|
);
|
||||||
|
|
||||||
|
// Avoid going through an infinite loop if the entry size is 0
|
||||||
|
if entry_size == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset -= entry_size;
|
||||||
|
|
||||||
|
let expected_uuid =
|
||||||
|
Uuid::from_str(TDVF_METADATA_OFFSET_GUID).map_err(TdvfError::UuidCreation)?;
|
||||||
|
if entry_uuid == expected_uuid && entry_size == 22 {
|
||||||
|
return Ok((
|
||||||
|
SeekFrom::End(
|
||||||
|
-(u32::from_le_bytes(table[offset..offset + 4].try_into().unwrap()) as i64),
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we end up here, this means the firmware doesn't support the new way
|
||||||
|
// of exposing the TDVF descriptor offset through the table of GUIDs.
|
||||||
|
// That's why we fallback onto the deprecated method.
|
||||||
|
|
||||||
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 32-bit offset to the TDVF metadata is located 32 bytes from
|
||||||
// the end of the file.
|
// the end of the file.
|
||||||
// See "TDVF Metadata Pointer" in "TDX Virtual Firmware Design Guide
|
// See "TDVF Metadata Pointer" in "TDX Virtual Firmware Design Guide
|
||||||
@@ -74,13 +143,21 @@ pub fn parse_tdvf_sections(file: &mut File) -> Result<Vec<TdvfSection>, TdvfErro
|
|||||||
let mut descriptor_offset: [u8; 4] = [0; 4];
|
let mut descriptor_offset: [u8; 4] = [0; 4];
|
||||||
file.read_exact(&mut descriptor_offset)
|
file.read_exact(&mut descriptor_offset)
|
||||||
.map_err(TdvfError::ReadDescriptorOffset)?;
|
.map_err(TdvfError::ReadDescriptorOffset)?;
|
||||||
let descriptor_offset = u32::from_le_bytes(descriptor_offset) as u64;
|
|
||||||
|
|
||||||
file.seek(SeekFrom::Start(descriptor_offset))
|
Ok((
|
||||||
|
SeekFrom::Start(u32::from_le_bytes(descriptor_offset) as u64),
|
||||||
|
false,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool), TdvfError> {
|
||||||
|
let (descriptor_offset, guid_found) = tdvf_descriptor_offset(file)?;
|
||||||
|
|
||||||
|
file.seek(descriptor_offset)
|
||||||
.map_err(TdvfError::ReadDescriptor)?;
|
.map_err(TdvfError::ReadDescriptor)?;
|
||||||
|
|
||||||
let mut descriptor: TdvfDescriptor = Default::default();
|
let mut descriptor: TdvfDescriptor = Default::default();
|
||||||
// Safe as we read exactly the size of the descriptor header
|
// SAFETY: we read exactly the size of the descriptor header
|
||||||
file.read_exact(unsafe {
|
file.read_exact(unsafe {
|
||||||
std::slice::from_raw_parts_mut(
|
std::slice::from_raw_parts_mut(
|
||||||
&mut descriptor as *mut _ as *mut u8,
|
&mut descriptor as *mut _ as *mut u8,
|
||||||
@@ -107,7 +184,7 @@ pub fn parse_tdvf_sections(file: &mut File) -> Result<Vec<TdvfSection>, TdvfErro
|
|||||||
let mut sections = Vec::new();
|
let mut sections = Vec::new();
|
||||||
sections.resize_with(descriptor.num_sections as usize, TdvfSection::default);
|
sections.resize_with(descriptor.num_sections as usize, TdvfSection::default);
|
||||||
|
|
||||||
// Safe as we read exactly the advertised sections
|
// SAFETY: we read exactly the advertised sections
|
||||||
file.read_exact(unsafe {
|
file.read_exact(unsafe {
|
||||||
std::slice::from_raw_parts_mut(
|
std::slice::from_raw_parts_mut(
|
||||||
sections.as_mut_ptr() as *mut u8,
|
sections.as_mut_ptr() as *mut u8,
|
||||||
@@ -116,25 +193,20 @@ pub fn parse_tdvf_sections(file: &mut File) -> Result<Vec<TdvfSection>, TdvfErro
|
|||||||
})
|
})
|
||||||
.map_err(TdvfError::ReadDescriptor)?;
|
.map_err(TdvfError::ReadDescriptor)?;
|
||||||
|
|
||||||
Ok(sections)
|
Ok((sections, guid_found))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
enum HobType {
|
enum HobType {
|
||||||
Handoff = 0x1,
|
Handoff = 0x1,
|
||||||
ResourceDescriptor = 0x3,
|
ResourceDescriptor = 0x3,
|
||||||
GuidExtension = 0x4,
|
GuidExtension = 0x4,
|
||||||
|
#[default]
|
||||||
Unused = 0xfffe,
|
Unused = 0xfffe,
|
||||||
EndOfHobList = 0xffff,
|
EndOfHobList = 0xffff,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HobType {
|
|
||||||
fn default() -> Self {
|
|
||||||
HobType::Unused
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobHeader {
|
struct HobHeader {
|
||||||
@@ -184,19 +256,14 @@ struct HobGuidType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub enum PayloadImageType {
|
pub enum PayloadImageType {
|
||||||
|
#[default]
|
||||||
ExecutablePayload,
|
ExecutablePayload,
|
||||||
BzImage,
|
BzImage,
|
||||||
RawVmLinux,
|
RawVmLinux,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PayloadImageType {
|
|
||||||
fn default() -> Self {
|
|
||||||
PayloadImageType::ExecutablePayload
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
pub struct PayloadInfo {
|
pub struct PayloadInfo {
|
||||||
@@ -211,12 +278,17 @@ struct TdPayload {
|
|||||||
payload_info: PayloadInfo,
|
payload_info: PayloadInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: These data structures only contain a series of integers
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for HobHeader {}
|
unsafe impl ByteValued for HobHeader {}
|
||||||
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for HobHandoffInfoTable {}
|
unsafe impl ByteValued for HobHandoffInfoTable {}
|
||||||
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for HobResourceDescriptor {}
|
unsafe impl ByteValued for HobResourceDescriptor {}
|
||||||
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for HobGuidType {}
|
unsafe impl ByteValued for HobGuidType {}
|
||||||
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for PayloadInfo {}
|
unsafe impl ByteValued for PayloadInfo {}
|
||||||
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for TdPayload {}
|
unsafe impl ByteValued for TdPayload {}
|
||||||
|
|
||||||
pub struct TdHob {
|
pub struct TdHob {
|
||||||
@@ -312,12 +384,19 @@ impl TdHob {
|
|||||||
physical_start: u64,
|
physical_start: u64,
|
||||||
resource_length: u64,
|
resource_length: u64,
|
||||||
ram: bool,
|
ram: bool,
|
||||||
|
guid_found: bool,
|
||||||
) -> Result<(), TdvfError> {
|
) -> Result<(), TdvfError> {
|
||||||
self.add_resource(
|
self.add_resource(
|
||||||
mem,
|
mem,
|
||||||
physical_start,
|
physical_start,
|
||||||
resource_length,
|
resource_length,
|
||||||
if ram {
|
if ram {
|
||||||
|
if guid_found {
|
||||||
|
0x7 /* EFI_RESOURCE_MEMORY_UNACCEPTED */
|
||||||
|
} else {
|
||||||
|
0 /* EFI_RESOURCE_SYSTEM_MEMORY */
|
||||||
|
}
|
||||||
|
} else if guid_found {
|
||||||
0 /* EFI_RESOURCE_SYSTEM_MEMORY */
|
0 /* EFI_RESOURCE_SYSTEM_MEMORY */
|
||||||
} else {
|
} else {
|
||||||
0x5 /*EFI_RESOURCE_MEMORY_RESERVED */
|
0x5 /*EFI_RESOURCE_MEMORY_RESERVED */
|
||||||
@@ -445,9 +524,9 @@ mod tests {
|
|||||||
#[ignore]
|
#[ignore]
|
||||||
fn test_parse_tdvf_sections() {
|
fn test_parse_tdvf_sections() {
|
||||||
let mut f = std::fs::File::open("tdvf.fd").unwrap();
|
let mut f = std::fs::File::open("tdvf.fd").unwrap();
|
||||||
let sections = parse_tdvf_sections(&mut f).unwrap();
|
let (sections, _) = parse_tdvf_sections(&mut f).unwrap();
|
||||||
for section in sections {
|
for section in sections {
|
||||||
eprintln!("{:x?}", section)
|
eprintln!("{section:x?}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
block/Cargo.toml
Normal file
27
block/Cargo.toml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "block"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["The Cloud Hypervisor Authors", "The Chromium OS Authors"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
io_uring = ["dep:io-uring"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
byteorder = "1.4.3"
|
||||||
|
crc32c = "0.6.4"
|
||||||
|
io-uring = { version = "0.6.1", optional = true }
|
||||||
|
libc = "0.2.147"
|
||||||
|
log = "0.4.17"
|
||||||
|
remain = "0.2.11"
|
||||||
|
smallvec = "1.11.0"
|
||||||
|
thiserror = "1.0.40"
|
||||||
|
uuid = { version = "1.3.4", features = ["v4"] }
|
||||||
|
versionize = "0.1.10"
|
||||||
|
versionize_derive = "0.1.4"
|
||||||
|
virtio-bindings = { version = "0.2.0", features = ["virtio-v5_0_0"] }
|
||||||
|
virtio-queue = "0.9.0"
|
||||||
|
vm-memory = { version = "0.12.2", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
|
||||||
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
|
vmm-sys-util = "0.11.0"
|
||||||
60
block/src/async_io.rs
Normal file
60
block/src/async_io.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright © 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
|
use crate::DiskTopology;
|
||||||
|
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 {
|
||||||
|
fn size(&mut self) -> DiskFileResult<u64>;
|
||||||
|
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
|
||||||
|
fn topology(&mut self) -> DiskTopology {
|
||||||
|
DiskTopology::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 {
|
||||||
|
fn notifier(&self) -> &EventFd;
|
||||||
|
fn read_vectored(
|
||||||
|
&mut self,
|
||||||
|
offset: libc::off_t,
|
||||||
|
iovecs: &[libc::iovec],
|
||||||
|
user_data: u64,
|
||||||
|
) -> AsyncIoResult<()>;
|
||||||
|
fn write_vectored(
|
||||||
|
&mut self,
|
||||||
|
offset: libc::off_t,
|
||||||
|
iovecs: &[libc::iovec],
|
||||||
|
user_data: u64,
|
||||||
|
) -> AsyncIoResult<()>;
|
||||||
|
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
|
||||||
|
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
|
||||||
|
}
|
||||||
90
block/src/fixed_vhd.rs
Normal file
90
block/src/fixed_vhd.rs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// Copyright © 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::vhd::VhdFooter;
|
||||||
|
use crate::BlockBackend;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FixedVhd {
|
||||||
|
file: File,
|
||||||
|
size: u64,
|
||||||
|
position: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixedVhd {
|
||||||
|
pub fn new(mut file: File) -> std::io::Result<Self> {
|
||||||
|
let footer = VhdFooter::new(&mut file)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
file,
|
||||||
|
size: footer.current_size(),
|
||||||
|
position: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for FixedVhd {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.file.as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for FixedVhd {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
match self.file.read(buf) {
|
||||||
|
Ok(r) => {
|
||||||
|
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for FixedVhd {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
|
match self.file.write(buf) {
|
||||||
|
Ok(r) => {
|
||||||
|
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
self.file.sync_all()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Seek for FixedVhd {
|
||||||
|
fn seek(&mut self, newpos: SeekFrom) -> std::io::Result<u64> {
|
||||||
|
match self.file.seek(newpos) {
|
||||||
|
Ok(pos) => {
|
||||||
|
self.position = pos;
|
||||||
|
Ok(pos)
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockBackend for FixedVhd {
|
||||||
|
fn size(&self) -> std::result::Result<u64, crate::Error> {
|
||||||
|
Ok(self.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for FixedVhd {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
file: self.file.try_clone().expect("FixedVhd cloning failed"),
|
||||||
|
size: self.size,
|
||||||
|
position: self.position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,36 +5,29 @@
|
|||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||||
};
|
};
|
||||||
|
use crate::fixed_vhd::FixedVhd;
|
||||||
use crate::raw_async::RawFileAsync;
|
use crate::raw_async::RawFileAsync;
|
||||||
use crate::vhd::VhdFooter;
|
use crate::BlockBackend;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
pub struct FixedVhdDiskAsync {
|
pub struct FixedVhdDiskAsync(FixedVhd);
|
||||||
file: File,
|
|
||||||
size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FixedVhdDiskAsync {
|
impl FixedVhdDiskAsync {
|
||||||
pub fn new(mut file: File) -> std::io::Result<Self> {
|
pub fn new(file: File) -> std::io::Result<Self> {
|
||||||
let footer = VhdFooter::new(&mut file)?;
|
Ok(Self(FixedVhd::new(file)?))
|
||||||
|
|
||||||
Ok(FixedVhdDiskAsync {
|
|
||||||
file,
|
|
||||||
size: footer.current_size(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for FixedVhdDiskAsync {
|
impl DiskFile for FixedVhdDiskAsync {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn size(&mut self) -> DiskFileResult<u64> {
|
||||||
Ok(self.size)
|
Ok(self.0.size().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
Ok(Box::new(
|
Ok(Box::new(
|
||||||
FixedVhdAsync::new(self.file.as_raw_fd(), ring_depth, self.size)
|
FixedVhdAsync::new(self.0.as_raw_fd(), ring_depth, self.0.size().unwrap())
|
||||||
.map_err(DiskFileError::NewAsyncIo)?,
|
.map_err(DiskFileError::NewAsyncIo)?,
|
||||||
) as Box<dyn AsyncIo>)
|
) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
@@ -64,7 +57,7 @@ impl AsyncIo for FixedVhdAsync {
|
|||||||
fn read_vectored(
|
fn read_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: Vec<libc::iovec>,
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
if offset as u64 >= self.size {
|
if offset as u64 >= self.size {
|
||||||
@@ -83,7 +76,7 @@ impl AsyncIo for FixedVhdAsync {
|
|||||||
fn write_vectored(
|
fn write_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: Vec<libc::iovec>,
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
if offset as u64 >= self.size {
|
if offset as u64 >= self.size {
|
||||||
@@ -104,7 +97,7 @@ impl AsyncIo for FixedVhdAsync {
|
|||||||
self.raw_file_async.fsync(user_data)
|
self.raw_file_async.fsync(user_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||||
self.raw_file_async.complete()
|
self.raw_file_async.next_completed_request()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,36 +5,29 @@
|
|||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||||
};
|
};
|
||||||
|
use crate::fixed_vhd::FixedVhd;
|
||||||
use crate::raw_sync::RawFileSync;
|
use crate::raw_sync::RawFileSync;
|
||||||
use crate::vhd::VhdFooter;
|
use crate::BlockBackend;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
pub struct FixedVhdDiskSync {
|
pub struct FixedVhdDiskSync(FixedVhd);
|
||||||
file: File,
|
|
||||||
size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FixedVhdDiskSync {
|
impl FixedVhdDiskSync {
|
||||||
pub fn new(mut file: File) -> std::io::Result<Self> {
|
pub fn new(file: File) -> std::io::Result<Self> {
|
||||||
let footer = VhdFooter::new(&mut file)?;
|
Ok(Self(FixedVhd::new(file)?))
|
||||||
|
|
||||||
Ok(FixedVhdDiskSync {
|
|
||||||
file,
|
|
||||||
size: footer.current_size(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for FixedVhdDiskSync {
|
impl DiskFile for FixedVhdDiskSync {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn size(&mut self) -> DiskFileResult<u64> {
|
||||||
Ok(self.size)
|
Ok(self.0.size().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
Ok(Box::new(
|
Ok(Box::new(
|
||||||
FixedVhdSync::new(self.file.as_raw_fd(), self.size)
|
FixedVhdSync::new(self.0.as_raw_fd(), self.0.size().unwrap())
|
||||||
.map_err(DiskFileError::NewAsyncIo)?,
|
.map_err(DiskFileError::NewAsyncIo)?,
|
||||||
) as Box<dyn AsyncIo>)
|
) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
@@ -62,7 +55,7 @@ impl AsyncIo for FixedVhdSync {
|
|||||||
fn read_vectored(
|
fn read_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: Vec<libc::iovec>,
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
if offset as u64 >= self.size {
|
if offset as u64 >= self.size {
|
||||||
@@ -81,7 +74,7 @@ impl AsyncIo for FixedVhdSync {
|
|||||||
fn write_vectored(
|
fn write_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: Vec<libc::iovec>,
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
if offset as u64 >= self.size {
|
if offset as u64 >= self.size {
|
||||||
@@ -101,7 +94,7 @@ impl AsyncIo for FixedVhdSync {
|
|||||||
self.raw_file_sync.fsync(user_data)
|
self.raw_file_sync.fsync(user_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||||
self.raw_file_sync.complete()
|
self.raw_file_sync.next_completed_request()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,30 +12,49 @@
|
|||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
pub mod async_io;
|
pub mod async_io;
|
||||||
|
pub mod fixed_vhd;
|
||||||
|
#[cfg(feature = "io_uring")]
|
||||||
|
/// Enabled with the `"io_uring"` feature
|
||||||
pub mod fixed_vhd_async;
|
pub mod fixed_vhd_async;
|
||||||
pub mod fixed_vhd_sync;
|
pub mod fixed_vhd_sync;
|
||||||
|
pub mod qcow;
|
||||||
pub mod qcow_sync;
|
pub mod qcow_sync;
|
||||||
|
#[cfg(feature = "io_uring")]
|
||||||
|
/// Async primitives based on `io-uring`
|
||||||
|
///
|
||||||
|
/// Enabled with the `"io_uring"` feature
|
||||||
pub mod raw_async;
|
pub mod raw_async;
|
||||||
pub mod raw_sync;
|
pub mod raw_sync;
|
||||||
pub mod vhd;
|
pub mod vhd;
|
||||||
|
pub mod vhdx;
|
||||||
pub mod vhdx_sync;
|
pub mod vhdx_sync;
|
||||||
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
|
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
|
||||||
|
use crate::fixed_vhd::FixedVhd;
|
||||||
|
use crate::qcow::{QcowFile, RawFile};
|
||||||
|
use crate::vhdx::{Vhdx, VhdxError};
|
||||||
|
#[cfg(feature = "io_uring")]
|
||||||
use io_uring::{opcode, IoUring, Probe};
|
use io_uring::{opcode, IoUring, Probe};
|
||||||
|
use libc::{ioctl, S_IFBLK, S_IFMT};
|
||||||
|
use smallvec::SmallVec;
|
||||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
use std::collections::VecDeque;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
use std::fmt::Debug;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
||||||
use std::os::linux::fs::MetadataExt;
|
use std::os::linux::fs::MetadataExt;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::MutexGuard;
|
use std::sync::MutexGuard;
|
||||||
|
use std::time::Instant;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
use versionize_derive::Versionize;
|
use versionize_derive::Versionize;
|
||||||
use virtio_bindings::bindings::virtio_blk::*;
|
use virtio_bindings::virtio_blk::*;
|
||||||
use virtio_queue::DescriptorChain;
|
use virtio_queue::DescriptorChain;
|
||||||
use vm_memory::{
|
use vm_memory::{
|
||||||
bitmap::AtomicBitmap, bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory,
|
bitmap::AtomicBitmap, bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory,
|
||||||
@@ -43,6 +62,7 @@ use vm_memory::{
|
|||||||
};
|
};
|
||||||
use vm_virtio::{AccessPlatform, Translatable};
|
use vm_virtio::{AccessPlatform, Translatable};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
use vmm_sys_util::{ioctl_io_nr, ioctl_ioc_nr};
|
||||||
|
|
||||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
||||||
|
|
||||||
@@ -63,12 +83,22 @@ pub enum Error {
|
|||||||
DescriptorChainTooShort,
|
DescriptorChainTooShort,
|
||||||
#[error("Guest gave us a descriptor that was too short to use")]
|
#[error("Guest gave us a descriptor that was too short to use")]
|
||||||
DescriptorLengthTooSmall,
|
DescriptorLengthTooSmall,
|
||||||
|
#[error("Failed to detect image type: {0}")]
|
||||||
|
DetectImageType(std::io::Error),
|
||||||
|
#[error("Failure in fixed vhd: {0}")]
|
||||||
|
FixedVhdError(std::io::Error),
|
||||||
#[error("Getting a block's metadata fails for any reason")]
|
#[error("Getting a block's metadata fails for any reason")]
|
||||||
GetFileMetadata,
|
GetFileMetadata,
|
||||||
#[error("The requested operation would cause a seek beyond disk end")]
|
#[error("The requested operation would cause a seek beyond disk end")]
|
||||||
InvalidOffset,
|
InvalidOffset,
|
||||||
|
#[error("Failure in qcow: {0}")]
|
||||||
|
QcowError(qcow::Error),
|
||||||
|
#[error("Failure in raw file: {0}")]
|
||||||
|
RawFileError(std::io::Error),
|
||||||
#[error("The requested operation does not support multiple descriptors")]
|
#[error("The requested operation does not support multiple descriptors")]
|
||||||
TooManyDescriptors,
|
TooManyDescriptors,
|
||||||
|
#[error("Failure in vhdx: {0}")]
|
||||||
|
VhdxError(VhdxError),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
||||||
@@ -86,8 +116,8 @@ fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
|||||||
Ok(device_id)
|
Ok(device_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_disk_image_id(disk_path: &Path) -> Vec<u8> {
|
pub fn build_serial(disk_path: &Path) -> Vec<u8> {
|
||||||
let mut default_disk_image_id = vec![0; VIRTIO_BLK_ID_BYTES as usize];
|
let mut default_serial = vec![0; VIRTIO_BLK_ID_BYTES as usize];
|
||||||
match build_device_id(disk_path) {
|
match build_device_id(disk_path) {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
warn!("Could not generate device id. We'll use a default.");
|
warn!("Could not generate device id. We'll use a default.");
|
||||||
@@ -97,17 +127,17 @@ pub fn build_disk_image_id(disk_path: &Path) -> Vec<u8> {
|
|||||||
// This will also zero out any leftover bytes.
|
// This will also zero out any leftover bytes.
|
||||||
let disk_id = m.as_bytes();
|
let disk_id = m.as_bytes();
|
||||||
let bytes_to_copy = cmp::min(disk_id.len(), VIRTIO_BLK_ID_BYTES as usize);
|
let bytes_to_copy = cmp::min(disk_id.len(), VIRTIO_BLK_ID_BYTES as usize);
|
||||||
default_disk_image_id[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy])
|
default_serial[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_disk_image_id
|
default_serial
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ExecuteError {
|
pub enum ExecuteError {
|
||||||
#[error("Bad request: {0}")]
|
#[error("Bad request: {0}")]
|
||||||
BadRequest(Error),
|
BadRequest(Error),
|
||||||
#[error("Falied to flush: {0}")]
|
#[error("Failed to flush: {0}")]
|
||||||
Flush(io::Error),
|
Flush(io::Error),
|
||||||
#[error("Failed to read: {0}")]
|
#[error("Failed to read: {0}")]
|
||||||
Read(GuestMemoryError),
|
Read(GuestMemoryError),
|
||||||
@@ -195,10 +225,11 @@ pub struct AlignedOperation {
|
|||||||
pub struct Request {
|
pub struct Request {
|
||||||
pub request_type: RequestType,
|
pub request_type: RequestType,
|
||||||
pub sector: u64,
|
pub sector: u64,
|
||||||
pub data_descriptors: Vec<(GuestAddress, u32)>,
|
pub data_descriptors: SmallVec<[(GuestAddress, u32); 1]>,
|
||||||
pub status_addr: GuestAddress,
|
pub status_addr: GuestAddress,
|
||||||
pub writeback: bool,
|
pub writeback: bool,
|
||||||
pub aligned_operations: Vec<AlignedOperation>,
|
pub aligned_operations: SmallVec<[AlignedOperation; 1]>,
|
||||||
|
pub start: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
@@ -226,10 +257,11 @@ impl Request {
|
|||||||
let mut req = Request {
|
let mut req = Request {
|
||||||
request_type: request_type(desc_chain.memory(), hdr_desc_addr)?,
|
request_type: request_type(desc_chain.memory(), hdr_desc_addr)?,
|
||||||
sector: sector(desc_chain.memory(), hdr_desc_addr)?,
|
sector: sector(desc_chain.memory(), hdr_desc_addr)?,
|
||||||
data_descriptors: Vec::new(),
|
data_descriptors: SmallVec::with_capacity(1),
|
||||||
status_addr: GuestAddress(0),
|
status_addr: GuestAddress(0),
|
||||||
writeback: true,
|
writeback: true,
|
||||||
aligned_operations: Vec::new(),
|
aligned_operations: SmallVec::with_capacity(1),
|
||||||
|
start: Instant::now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let status_desc;
|
let status_desc;
|
||||||
@@ -249,6 +281,7 @@ impl Request {
|
|||||||
return Err(Error::DescriptorChainTooShort);
|
return Err(Error::DescriptorChainTooShort);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
req.data_descriptors.reserve_exact(1);
|
||||||
while desc.has_next() {
|
while desc.has_next() {
|
||||||
if desc.is_write_only() && req.request_type == RequestType::Out {
|
if desc.is_write_only() && req.request_type == RequestType::Out {
|
||||||
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
||||||
@@ -297,7 +330,7 @@ impl Request {
|
|||||||
disk: &mut T,
|
disk: &mut T,
|
||||||
disk_nsectors: u64,
|
disk_nsectors: u64,
|
||||||
mem: &GuestMemoryMmap,
|
mem: &GuestMemoryMmap,
|
||||||
disk_id: &[u8],
|
serial: &[u8],
|
||||||
) -> result::Result<u32, ExecuteError> {
|
) -> result::Result<u32, ExecuteError> {
|
||||||
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
|
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
|
||||||
.map_err(ExecuteError::Seek)?;
|
.map_err(ExecuteError::Seek)?;
|
||||||
@@ -329,10 +362,10 @@ impl Request {
|
|||||||
}
|
}
|
||||||
RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?,
|
RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?,
|
||||||
RequestType::GetDeviceId => {
|
RequestType::GetDeviceId => {
|
||||||
if (*data_len as usize) < disk_id.len() {
|
if (*data_len as usize) < serial.len() {
|
||||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||||
}
|
}
|
||||||
mem.write_slice(disk_id, *data_addr)
|
mem.write_slice(serial, *data_addr)
|
||||||
.map_err(ExecuteError::Write)?;
|
.map_err(ExecuteError::Write)?;
|
||||||
}
|
}
|
||||||
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
||||||
@@ -346,14 +379,15 @@ impl Request {
|
|||||||
mem: &GuestMemoryMmap,
|
mem: &GuestMemoryMmap,
|
||||||
disk_nsectors: u64,
|
disk_nsectors: u64,
|
||||||
disk_image: &mut dyn AsyncIo,
|
disk_image: &mut dyn AsyncIo,
|
||||||
disk_id: &[u8],
|
serial: &[u8],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> result::Result<bool, ExecuteError> {
|
) -> result::Result<bool, ExecuteError> {
|
||||||
let sector = self.sector;
|
let sector = self.sector;
|
||||||
let request_type = self.request_type;
|
let request_type = self.request_type;
|
||||||
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
|
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
|
||||||
|
|
||||||
let mut iovecs = Vec::new();
|
let mut iovecs: SmallVec<[libc::iovec; 1]> =
|
||||||
|
SmallVec::with_capacity(self.data_descriptors.len());
|
||||||
for (data_addr, data_len) in &self.data_descriptors {
|
for (data_addr, data_len) in &self.data_descriptors {
|
||||||
if *data_len == 0 {
|
if *data_len == 0 {
|
||||||
continue;
|
continue;
|
||||||
@@ -372,16 +406,16 @@ impl Request {
|
|||||||
let origin_ptr = mem
|
let origin_ptr = mem
|
||||||
.get_slice(*data_addr, *data_len as usize)
|
.get_slice(*data_addr, *data_len as usize)
|
||||||
.map_err(ExecuteError::GetHostAddress)?
|
.map_err(ExecuteError::GetHostAddress)?
|
||||||
.as_ptr();
|
.ptr_guard();
|
||||||
|
|
||||||
// Verify the buffer alignment.
|
// Verify the buffer alignment.
|
||||||
// In case it's not properly aligned, an intermediate buffer is
|
// In case it's not properly aligned, an intermediate buffer is
|
||||||
// created with the correct alignment, and a copy from/to the
|
// created with the correct alignment, and a copy from/to the
|
||||||
// origin buffer is performed, depending on the type of operation.
|
// origin buffer is performed, depending on the type of operation.
|
||||||
let iov_base = if (origin_ptr as u64) % SECTOR_SIZE != 0 {
|
let iov_base = if (origin_ptr.as_ptr() as u64) % SECTOR_SIZE != 0 {
|
||||||
let layout =
|
let layout =
|
||||||
Layout::from_size_align(*data_len as usize, SECTOR_SIZE as usize).unwrap();
|
Layout::from_size_align(*data_len as usize, SECTOR_SIZE as usize).unwrap();
|
||||||
// Safe because layout has non-zero size
|
// SAFETY: layout has non-zero size
|
||||||
let aligned_ptr = unsafe { alloc_zeroed(layout) };
|
let aligned_ptr = unsafe { alloc_zeroed(layout) };
|
||||||
if aligned_ptr.is_null() {
|
if aligned_ptr.is_null() {
|
||||||
return Err(ExecuteError::TemporaryBufferAllocation(
|
return Err(ExecuteError::TemporaryBufferAllocation(
|
||||||
@@ -392,17 +426,15 @@ impl Request {
|
|||||||
// We need to perform the copy beforehand in case we're writing
|
// We need to perform the copy beforehand in case we're writing
|
||||||
// data out.
|
// data out.
|
||||||
if request_type == RequestType::Out {
|
if request_type == RequestType::Out {
|
||||||
// Safe because destination buffer has been allocated with
|
// SAFETY: destination buffer has been allocated with
|
||||||
// the proper size.
|
// the proper size.
|
||||||
unsafe {
|
unsafe { std::ptr::copy(origin_ptr.as_ptr(), aligned_ptr, *data_len as usize) };
|
||||||
std::ptr::copy(origin_ptr as *const u8, aligned_ptr, *data_len as usize)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store both origin and aligned pointers for complete_async()
|
// Store both origin and aligned pointers for complete_async()
|
||||||
// to process them.
|
// to process them.
|
||||||
self.aligned_operations.push(AlignedOperation {
|
self.aligned_operations.push(AlignedOperation {
|
||||||
origin_ptr: origin_ptr as u64,
|
origin_ptr: origin_ptr.as_ptr() as u64,
|
||||||
aligned_ptr: aligned_ptr as u64,
|
aligned_ptr: aligned_ptr as u64,
|
||||||
size: *data_len as usize,
|
size: *data_len as usize,
|
||||||
layout,
|
layout,
|
||||||
@@ -410,7 +442,7 @@ impl Request {
|
|||||||
|
|
||||||
aligned_ptr as *mut libc::c_void
|
aligned_ptr as *mut libc::c_void
|
||||||
} else {
|
} else {
|
||||||
origin_ptr as *mut libc::c_void
|
origin_ptr.as_ptr() as *mut libc::c_void
|
||||||
};
|
};
|
||||||
|
|
||||||
let iovec = libc::iovec {
|
let iovec = libc::iovec {
|
||||||
@@ -430,12 +462,12 @@ impl Request {
|
|||||||
.mark_dirty(0, *data_len as usize);
|
.mark_dirty(0, *data_len as usize);
|
||||||
}
|
}
|
||||||
disk_image
|
disk_image
|
||||||
.read_vectored(offset, iovecs, user_data)
|
.read_vectored(offset, &iovecs, user_data)
|
||||||
.map_err(ExecuteError::AsyncRead)?;
|
.map_err(ExecuteError::AsyncRead)?;
|
||||||
}
|
}
|
||||||
RequestType::Out => {
|
RequestType::Out => {
|
||||||
disk_image
|
disk_image
|
||||||
.write_vectored(offset, iovecs, user_data)
|
.write_vectored(offset, &iovecs, user_data)
|
||||||
.map_err(ExecuteError::AsyncWrite)?;
|
.map_err(ExecuteError::AsyncWrite)?;
|
||||||
}
|
}
|
||||||
RequestType::Flush => {
|
RequestType::Flush => {
|
||||||
@@ -449,10 +481,10 @@ impl Request {
|
|||||||
} else {
|
} else {
|
||||||
return Err(ExecuteError::BadRequest(Error::TooManyDescriptors));
|
return Err(ExecuteError::BadRequest(Error::TooManyDescriptors));
|
||||||
};
|
};
|
||||||
if (data_len as usize) < disk_id.len() {
|
if (data_len as usize) < serial.len() {
|
||||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||||
}
|
}
|
||||||
mem.write_slice(disk_id, data_addr)
|
mem.write_slice(serial, data_addr)
|
||||||
.map_err(ExecuteError::Write)?;
|
.map_err(ExecuteError::Write)?;
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
@@ -467,7 +499,7 @@ impl Request {
|
|||||||
// We need to perform the copy after the data has been read inside
|
// We need to perform the copy after the data has been read inside
|
||||||
// the aligned buffer in case we're reading data in.
|
// the aligned buffer in case we're reading data in.
|
||||||
if self.request_type == RequestType::In {
|
if self.request_type == RequestType::In {
|
||||||
// Safe because origin buffer has been allocated with the
|
// SAFETY: origin buffer has been allocated with the
|
||||||
// proper size.
|
// proper size.
|
||||||
unsafe {
|
unsafe {
|
||||||
std::ptr::copy(
|
std::ptr::copy(
|
||||||
@@ -479,7 +511,7 @@ impl Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Free the temporary aligned buffer.
|
// Free the temporary aligned buffer.
|
||||||
// Safe because aligned_ptr was allocated by alloc_zeroed with the same
|
// SAFETY: aligned_ptr was allocated by alloc_zeroed with the same
|
||||||
// layout
|
// layout
|
||||||
unsafe {
|
unsafe {
|
||||||
dealloc(
|
dealloc(
|
||||||
@@ -528,57 +560,67 @@ pub struct VirtioBlockGeometry {
|
|||||||
pub sectors: u8,
|
pub sectors: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: these data structures only contain a series of integers
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for VirtioBlockConfig {}
|
unsafe impl ByteValued for VirtioBlockConfig {}
|
||||||
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for VirtioBlockGeometry {}
|
unsafe impl ByteValued for VirtioBlockGeometry {}
|
||||||
|
|
||||||
/// Check if io_uring for block device can be used on the current system, as
|
/// Check if io_uring for block device can be used on the current system, as
|
||||||
/// it correctly supports the expected io_uring features.
|
/// it correctly supports the expected io_uring features.
|
||||||
pub fn block_io_uring_is_supported() -> bool {
|
pub fn block_io_uring_is_supported() -> bool {
|
||||||
let error_msg = "io_uring not supported:";
|
#[cfg(not(feature = "io_uring"))]
|
||||||
|
{
|
||||||
|
info!("io_uring is disabled by crate features");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
// Check we can create an io_uring instance, which effectively verifies
|
#[cfg(feature = "io_uring")]
|
||||||
// that io_uring_setup() syscall is supported.
|
{
|
||||||
let io_uring = match IoUring::new(1) {
|
let error_msg = "io_uring not supported:";
|
||||||
Ok(io_uring) => io_uring,
|
|
||||||
Err(e) => {
|
// Check we can create an io_uring instance, which effectively verifies
|
||||||
info!("{} failed to create io_uring instance: {}", error_msg, e);
|
// that io_uring_setup() syscall is supported.
|
||||||
|
let io_uring = match IoUring::new(1) {
|
||||||
|
Ok(io_uring) => io_uring,
|
||||||
|
Err(e) => {
|
||||||
|
info!("{} failed to create io_uring instance: {}", error_msg, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let submitter = io_uring.submitter();
|
||||||
|
|
||||||
|
let mut probe = Probe::new();
|
||||||
|
|
||||||
|
// Check we can register a probe to validate supported operations.
|
||||||
|
match submitter.register_probe(&mut probe) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
info!("{} failed to register a probe: {}", error_msg, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check IORING_OP_FSYNC is supported
|
||||||
|
if !probe.is_supported(opcode::Fsync::CODE) {
|
||||||
|
info!("{} IORING_OP_FSYNC operation not supported", error_msg);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let submitter = io_uring.submitter();
|
// Check IORING_OP_READV is supported
|
||||||
|
if !probe.is_supported(opcode::Readv::CODE) {
|
||||||
let mut probe = Probe::new();
|
info!("{} IORING_OP_READV operation not supported", error_msg);
|
||||||
|
|
||||||
// Check we can register a probe to validate supported operations.
|
|
||||||
match submitter.register_probe(&mut probe) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
info!("{} failed to register a probe: {}", error_msg, e);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Check IORING_OP_FSYNC is supported
|
// Check IORING_OP_WRITEV is supported
|
||||||
if !probe.is_supported(opcode::Fsync::CODE) {
|
if !probe.is_supported(opcode::Writev::CODE) {
|
||||||
info!("{} IORING_OP_FSYNC operation not supported", error_msg);
|
info!("{} IORING_OP_WRITEV operation not supported", error_msg);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check IORING_OP_READV is supported
|
true
|
||||||
if !probe.is_supported(opcode::Readv::CODE) {
|
|
||||||
info!("{} IORING_OP_READV operation not supported", error_msg);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check IORING_OP_WRITEV is supported
|
|
||||||
if !probe.is_supported(opcode::Writev::CODE) {
|
|
||||||
info!("{} IORING_OP_WRITEV operation not supported", error_msg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait AsyncAdaptor<F>
|
pub trait AsyncAdaptor<F>
|
||||||
@@ -588,14 +630,15 @@ where
|
|||||||
fn read_vectored_sync(
|
fn read_vectored_sync(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: Vec<libc::iovec>,
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
eventfd: &EventFd,
|
eventfd: &EventFd,
|
||||||
completion_list: &mut Vec<(u64, i32)>,
|
completion_list: &mut VecDeque<(u64, i32)>,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
// Convert libc::iovec into IoSliceMut
|
// Convert libc::iovec into IoSliceMut
|
||||||
let mut slices = Vec::new();
|
let mut slices: SmallVec<[IoSliceMut; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||||
for iovec in iovecs.iter() {
|
for iovec in iovecs.iter() {
|
||||||
|
// SAFETY: on Linux IoSliceMut wraps around libc::iovec
|
||||||
slices.push(IoSliceMut::new(unsafe { std::mem::transmute(*iovec) }));
|
slices.push(IoSliceMut::new(unsafe { std::mem::transmute(*iovec) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,7 +654,7 @@ where
|
|||||||
.map_err(AsyncIoError::ReadVectored)?
|
.map_err(AsyncIoError::ReadVectored)?
|
||||||
};
|
};
|
||||||
|
|
||||||
completion_list.push((user_data, result as i32));
|
completion_list.push_back((user_data, result as i32));
|
||||||
eventfd.write(1).unwrap();
|
eventfd.write(1).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -620,14 +663,15 @@ where
|
|||||||
fn write_vectored_sync(
|
fn write_vectored_sync(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: Vec<libc::iovec>,
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
eventfd: &EventFd,
|
eventfd: &EventFd,
|
||||||
completion_list: &mut Vec<(u64, i32)>,
|
completion_list: &mut VecDeque<(u64, i32)>,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
// Convert libc::iovec into IoSlice
|
// Convert libc::iovec into IoSlice
|
||||||
let mut slices = Vec::new();
|
let mut slices: SmallVec<[IoSlice; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||||
for iovec in iovecs.iter() {
|
for iovec in iovecs.iter() {
|
||||||
|
// SAFETY: on Linux IoSlice wraps around libc::iovec
|
||||||
slices.push(IoSlice::new(unsafe { std::mem::transmute(*iovec) }));
|
slices.push(IoSlice::new(unsafe { std::mem::transmute(*iovec) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -643,7 +687,7 @@ where
|
|||||||
.map_err(AsyncIoError::WriteVectored)?
|
.map_err(AsyncIoError::WriteVectored)?
|
||||||
};
|
};
|
||||||
|
|
||||||
completion_list.push((user_data, result as i32));
|
completion_list.push_back((user_data, result as i32));
|
||||||
eventfd.write(1).unwrap();
|
eventfd.write(1).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -653,7 +697,7 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
user_data: Option<u64>,
|
user_data: Option<u64>,
|
||||||
eventfd: &EventFd,
|
eventfd: &EventFd,
|
||||||
completion_list: &mut Vec<(u64, i32)>,
|
completion_list: &mut VecDeque<(u64, i32)>,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
let result: i32 = {
|
let result: i32 = {
|
||||||
let mut file = self.file();
|
let mut file = self.file();
|
||||||
@@ -665,7 +709,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(user_data) = user_data {
|
if let Some(user_data) = user_data {
|
||||||
completion_list.push((user_data, result));
|
completion_list.push_back((user_data, result));
|
||||||
eventfd.write(1).unwrap();
|
eventfd.write(1).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,24 +729,33 @@ pub enum ImageType {
|
|||||||
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
||||||
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876;
|
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876;
|
||||||
|
|
||||||
|
/// Read a block into memory aligned by the source block size (needed for O_DIRECT)
|
||||||
|
pub fn read_aligned_block_size(f: &mut File) -> std::io::Result<Vec<u8>> {
|
||||||
|
let blocksize = DiskTopology::probe(f)?.logical_block_size as usize;
|
||||||
|
// SAFETY: We are allocating memory that is naturally aligned (size = alignment) and we meet
|
||||||
|
// requirements for safety from Vec::from_raw_parts() as we are using the global allocator
|
||||||
|
// and transferring ownership of the memory.
|
||||||
|
let mut data = unsafe {
|
||||||
|
Vec::from_raw_parts(
|
||||||
|
alloc_zeroed(Layout::from_size_align_unchecked(blocksize, blocksize)),
|
||||||
|
blocksize,
|
||||||
|
blocksize,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
f.read_exact(&mut data)?;
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
/// Determine image type through file parsing.
|
/// Determine image type through file parsing.
|
||||||
pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
|
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
|
let block = read_aligned_block_size(f)?;
|
||||||
// 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
|
// 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 {
|
let image_type = if u32::from_be_bytes(block[0..4].try_into().unwrap()) == QCOW_MAGIC {
|
||||||
ImageType::Qcow2
|
ImageType::Qcow2
|
||||||
} else if vhd::is_fixed_vhd(f)? {
|
} else if vhd::is_fixed_vhd(f)? {
|
||||||
ImageType::FixedVhd
|
ImageType::FixedVhd
|
||||||
} else if u64::from_le_bytes(s.data[0..8].try_into().unwrap()) == VHDX_SIGN {
|
} else if u64::from_le_bytes(block[0..8].try_into().unwrap()) == VHDX_SIGN {
|
||||||
ImageType::Vhdx
|
ImageType::Vhdx
|
||||||
} else {
|
} else {
|
||||||
ImageType::Raw
|
ImageType::Raw
|
||||||
@@ -710,3 +763,108 @@ pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
|
|||||||
|
|
||||||
Ok(image_type)
|
Ok(image_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait BlockBackend: Read + Write + Seek + Send + Debug {
|
||||||
|
fn size(&self) -> Result<u64, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inspect the image file type and create an appropriate disk file to match it.
|
||||||
|
pub fn create_disk_file(mut file: File, direct_io: bool) -> Result<Box<dyn BlockBackend>, Error> {
|
||||||
|
let image_type = detect_image_type(&mut file).map_err(Error::DetectImageType)?;
|
||||||
|
|
||||||
|
Ok(match image_type {
|
||||||
|
ImageType::Qcow2 => {
|
||||||
|
Box::new(QcowFile::from(RawFile::new(file, direct_io)).map_err(Error::QcowError)?)
|
||||||
|
as Box<dyn BlockBackend>
|
||||||
|
}
|
||||||
|
ImageType::FixedVhd => {
|
||||||
|
Box::new(FixedVhd::new(file).map_err(Error::FixedVhdError)?) as Box<dyn BlockBackend>
|
||||||
|
}
|
||||||
|
ImageType::Vhdx => {
|
||||||
|
Box::new(Vhdx::new(file).map_err(Error::VhdxError)?) as Box<dyn BlockBackend>
|
||||||
|
}
|
||||||
|
ImageType::Raw => Box::new(RawFile::new(file, direct_io)) as Box<dyn BlockBackend>,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DiskTopology {
|
||||||
|
pub logical_block_size: u64,
|
||||||
|
pub physical_block_size: u64,
|
||||||
|
pub minimum_io_size: u64,
|
||||||
|
pub optimal_io_size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DiskTopology {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
logical_block_size: 512,
|
||||||
|
physical_block_size: 512,
|
||||||
|
minimum_io_size: 512,
|
||||||
|
optimal_io_size: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ioctl_io_nr!(BLKSSZGET, 0x12, 104);
|
||||||
|
ioctl_io_nr!(BLKPBSZGET, 0x12, 123);
|
||||||
|
ioctl_io_nr!(BLKIOMIN, 0x12, 120);
|
||||||
|
ioctl_io_nr!(BLKIOOPT, 0x12, 121);
|
||||||
|
|
||||||
|
enum BlockSize {
|
||||||
|
LogicalBlock,
|
||||||
|
PhysicalBlock,
|
||||||
|
MinimumIo,
|
||||||
|
OptimalIo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiskTopology {
|
||||||
|
fn is_block_device(f: &File) -> std::io::Result<bool> {
|
||||||
|
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
|
||||||
|
// SAFETY: FFI call with a valid fd and buffer
|
||||||
|
let ret = unsafe { libc::fstat(f.as_raw_fd(), stat.as_mut_ptr()) };
|
||||||
|
if ret != 0 {
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: stat is valid at this point
|
||||||
|
let is_block = unsafe { (*stat.as_ptr()).st_mode & S_IFMT == S_IFBLK };
|
||||||
|
Ok(is_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
// libc::ioctl() takes different types on different architectures
|
||||||
|
fn query_block_size(f: &File, block_size_type: BlockSize) -> std::io::Result<u64> {
|
||||||
|
let mut block_size = 0;
|
||||||
|
// SAFETY: FFI call with correct arguments
|
||||||
|
let ret = unsafe {
|
||||||
|
ioctl(
|
||||||
|
f.as_raw_fd(),
|
||||||
|
match block_size_type {
|
||||||
|
BlockSize::LogicalBlock => BLKSSZGET(),
|
||||||
|
BlockSize::PhysicalBlock => BLKPBSZGET(),
|
||||||
|
BlockSize::MinimumIo => BLKIOMIN(),
|
||||||
|
BlockSize::OptimalIo => BLKIOOPT(),
|
||||||
|
} as _,
|
||||||
|
&mut block_size,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ret != 0 {
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(block_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn probe(f: &File) -> std::io::Result<Self> {
|
||||||
|
if !Self::is_block_device(f)? {
|
||||||
|
return Ok(DiskTopology::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiskTopology {
|
||||||
|
logical_block_size: Self::query_block_size(f, BlockSize::LogicalBlock)?,
|
||||||
|
physical_block_size: Self::query_block_size(f, BlockSize::PhysicalBlock)?,
|
||||||
|
minimum_io_size: Self::query_block_size(f, BlockSize::MinimumIo)?,
|
||||||
|
optimal_io_size: Self::query_block_size(f, BlockSize::OptimalIo)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,40 +2,45 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
mod qcow_raw_file;
|
mod qcow_raw_file;
|
||||||
mod raw_file;
|
mod raw_file;
|
||||||
mod refcount;
|
mod refcount;
|
||||||
mod vec_cache;
|
mod vec_cache;
|
||||||
|
|
||||||
use crate::qcow_raw_file::QcowRawFile;
|
use crate::qcow::{
|
||||||
use crate::refcount::RefCount;
|
qcow_raw_file::QcowRawFile,
|
||||||
use crate::vec_cache::{CacheMap, Cacheable, VecCache};
|
refcount::RefCount,
|
||||||
|
vec_cache::{CacheMap, Cacheable, VecCache},
|
||||||
|
};
|
||||||
|
use crate::BlockBackend;
|
||||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use libc::{EINVAL, ENOSPC, ENOTSUP};
|
use libc::{EINVAL, ENOSPC, ENOTSUP};
|
||||||
use remain::sorted;
|
use remain::sorted;
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
|
use std::fs::OpenOptions;
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
use std::str;
|
||||||
use vmm_sys_util::{
|
use vmm_sys_util::{
|
||||||
file_traits::FileSetLen, file_traits::FileSync, seek_hole::SeekHole, write_zeroes::PunchHole,
|
file_traits::FileSetLen, file_traits::FileSync, seek_hole::SeekHole, write_zeroes::PunchHole,
|
||||||
write_zeroes::WriteZeroesAt,
|
write_zeroes::WriteZeroesAt,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::raw_file::RawFile;
|
pub use crate::qcow::raw_file::RawFile;
|
||||||
|
|
||||||
#[sorted]
|
#[sorted]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
BackingFilesNotSupported,
|
BackingFileIo(io::Error),
|
||||||
|
BackingFileOpen(Box<crate::Error>),
|
||||||
|
BackingFileTooLong(usize),
|
||||||
CompressedBlocksNotSupported,
|
CompressedBlocksNotSupported,
|
||||||
EvictingCache(io::Error),
|
EvictingCache(io::Error),
|
||||||
FileTooBig(u64),
|
FileTooBig(u64),
|
||||||
GettingFileSize(io::Error),
|
GettingFileSize(io::Error),
|
||||||
GettingRefcount(refcount::Error),
|
GettingRefcount(refcount::Error),
|
||||||
|
InvalidBackingFileName(str::Utf8Error),
|
||||||
InvalidClusterIndex,
|
InvalidClusterIndex,
|
||||||
InvalidClusterSize,
|
InvalidClusterSize,
|
||||||
InvalidIndex,
|
InvalidIndex,
|
||||||
@@ -78,47 +83,48 @@ impl Display for Error {
|
|||||||
|
|
||||||
#[sorted]
|
#[sorted]
|
||||||
match self {
|
match self {
|
||||||
BackingFilesNotSupported => write!(f, "backing files not supported"),
|
BackingFileIo(e) => write!(f, "backing file io error: {}", e),
|
||||||
|
BackingFileOpen(e) => write!(f, "backing file open error: {}", *e),
|
||||||
|
BackingFileTooLong(len) => {
|
||||||
|
write!(f, "backing file name is too long: {} bytes over", len)
|
||||||
|
}
|
||||||
CompressedBlocksNotSupported => write!(f, "compressed blocks not supported"),
|
CompressedBlocksNotSupported => write!(f, "compressed blocks not supported"),
|
||||||
EvictingCache(e) => write!(f, "failed to evict cache: {}", e),
|
EvictingCache(e) => write!(f, "failed to evict cache: {e}"),
|
||||||
FileTooBig(size) => write!(
|
FileTooBig(size) => write!(f, "file larger than max of {MAX_QCOW_FILE_SIZE}: {size}"),
|
||||||
f,
|
GettingFileSize(e) => write!(f, "failed to get file size: {e}"),
|
||||||
"file larger than max of {}: {}",
|
GettingRefcount(e) => write!(f, "failed to get refcount: {e}"),
|
||||||
MAX_QCOW_FILE_SIZE, size
|
InvalidBackingFileName(e) => write!(f, "failed to parse filename: {}", e),
|
||||||
),
|
|
||||||
GettingFileSize(e) => write!(f, "failed to get file size: {}", e),
|
|
||||||
GettingRefcount(e) => write!(f, "failed to get refcount: {}", e),
|
|
||||||
InvalidClusterIndex => write!(f, "invalid cluster index"),
|
InvalidClusterIndex => write!(f, "invalid cluster index"),
|
||||||
InvalidClusterSize => write!(f, "invalid cluster size"),
|
InvalidClusterSize => write!(f, "invalid cluster size"),
|
||||||
InvalidIndex => write!(f, "invalid index"),
|
InvalidIndex => write!(f, "invalid index"),
|
||||||
InvalidL1TableOffset => write!(f, "invalid L1 table offset"),
|
InvalidL1TableOffset => write!(f, "invalid L1 table offset"),
|
||||||
InvalidL1TableSize(size) => write!(f, "invalid L1 table size {}", size),
|
InvalidL1TableSize(size) => write!(f, "invalid L1 table size {size}"),
|
||||||
InvalidMagic => write!(f, "invalid magic"),
|
InvalidMagic => write!(f, "invalid magic"),
|
||||||
InvalidOffset(_) => write!(f, "invalid offset"),
|
InvalidOffset(_) => write!(f, "invalid offset"),
|
||||||
InvalidRefcountTableOffset => write!(f, "invalid refcount table offset"),
|
InvalidRefcountTableOffset => write!(f, "invalid refcount table offset"),
|
||||||
InvalidRefcountTableSize(size) => write!(f, "invalid refcount table size: {}", size),
|
InvalidRefcountTableSize(size) => write!(f, "invalid refcount table size: {size}"),
|
||||||
NoFreeClusters => write!(f, "no free clusters"),
|
NoFreeClusters => write!(f, "no free clusters"),
|
||||||
NoRefcountClusters => write!(f, "no refcount clusters"),
|
NoRefcountClusters => write!(f, "no refcount clusters"),
|
||||||
NotEnoughSpaceForRefcounts => write!(f, "not enough space for refcounts"),
|
NotEnoughSpaceForRefcounts => write!(f, "not enough space for refcounts"),
|
||||||
OpeningFile(e) => write!(f, "failed to open file: {}", e),
|
OpeningFile(e) => write!(f, "failed to open file: {e}"),
|
||||||
ReadingData(e) => write!(f, "failed to read data: {}", e),
|
ReadingData(e) => write!(f, "failed to read data: {e}"),
|
||||||
ReadingHeader(e) => write!(f, "failed to read header: {}", e),
|
ReadingHeader(e) => write!(f, "failed to read header: {e}"),
|
||||||
ReadingPointers(e) => write!(f, "failed to read pointers: {}", e),
|
ReadingPointers(e) => write!(f, "failed to read pointers: {e}"),
|
||||||
ReadingRefCountBlock(e) => write!(f, "failed to read ref count block: {}", e),
|
ReadingRefCountBlock(e) => write!(f, "failed to read ref count block: {e}"),
|
||||||
ReadingRefCounts(e) => write!(f, "failed to read ref counts: {}", e),
|
ReadingRefCounts(e) => write!(f, "failed to read ref counts: {e}"),
|
||||||
RebuildingRefCounts(e) => write!(f, "failed to rebuild ref counts: {}", e),
|
RebuildingRefCounts(e) => write!(f, "failed to rebuild ref counts: {e}"),
|
||||||
RefcountTableOffEnd => write!(f, "refcount table offset past file end"),
|
RefcountTableOffEnd => write!(f, "refcount table offset past file end"),
|
||||||
RefcountTableTooLarge => write!(f, "too many clusters specified for refcount table"),
|
RefcountTableTooLarge => write!(f, "too many clusters specified for refcount table"),
|
||||||
SeekingFile(e) => write!(f, "failed to seek file: {}", e),
|
SeekingFile(e) => write!(f, "failed to seek file: {e}"),
|
||||||
SettingFileSize(e) => write!(f, "failed to set file size: {}", e),
|
SettingFileSize(e) => write!(f, "failed to set file size: {e}"),
|
||||||
SettingRefcountRefcount(e) => write!(f, "failed to set refcount refcount: {}", e),
|
SettingRefcountRefcount(e) => write!(f, "failed to set refcount refcount: {e}"),
|
||||||
SizeTooSmallForNumberOfClusters => write!(f, "size too small for number of clusters"),
|
SizeTooSmallForNumberOfClusters => write!(f, "size too small for number of clusters"),
|
||||||
TooManyL1Entries(count) => write!(f, "l1 entry table too large: {}", count),
|
TooManyL1Entries(count) => write!(f, "l1 entry table too large: {count}"),
|
||||||
TooManyRefcounts(count) => write!(f, "ref count table too large: {}", count),
|
TooManyRefcounts(count) => write!(f, "ref count table too large: {count}"),
|
||||||
UnsupportedRefcountOrder => write!(f, "unsupported refcount order"),
|
UnsupportedRefcountOrder => write!(f, "unsupported refcount order"),
|
||||||
UnsupportedVersion(v) => write!(f, "unsupported version: {}", v),
|
UnsupportedVersion(v) => write!(f, "unsupported version: {v}"),
|
||||||
WritingData(e) => write!(f, "failed to write data: {}", e),
|
WritingData(e) => write!(f, "failed to write data: {e}"),
|
||||||
WritingHeader(e) => write!(f, "failed to write header: {}", e),
|
WritingHeader(e) => write!(f, "failed to write header: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,8 +163,14 @@ const COMPRESSED_FLAG: u64 = 1 << 62;
|
|||||||
const CLUSTER_USED_FLAG: u64 = 1 << 63;
|
const CLUSTER_USED_FLAG: u64 = 1 << 63;
|
||||||
const COMPATIBLE_FEATURES_LAZY_REFCOUNTS: u64 = 1;
|
const COMPATIBLE_FEATURES_LAZY_REFCOUNTS: u64 = 1;
|
||||||
|
|
||||||
|
// The format supports a "header extension area", that crosvm does not use.
|
||||||
|
const QCOW_EMPTY_HEADER_EXTENSION_SIZE: u32 = 8;
|
||||||
|
|
||||||
|
// Defined by the specification
|
||||||
|
const MAX_BACKING_FILE_SIZE: u32 = 1023;
|
||||||
|
|
||||||
/// Contains the information from the header of a qcow file.
|
/// Contains the information from the header of a qcow file.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct QcowHeader {
|
pub struct QcowHeader {
|
||||||
pub magic: u32,
|
pub magic: u32,
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
@@ -185,12 +197,15 @@ pub struct QcowHeader {
|
|||||||
pub autoclear_features: u64,
|
pub autoclear_features: u64,
|
||||||
pub refcount_order: u32,
|
pub refcount_order: u32,
|
||||||
pub header_size: u32,
|
pub header_size: u32,
|
||||||
|
|
||||||
|
// Post-header entries
|
||||||
|
pub backing_file_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QcowHeader {
|
impl QcowHeader {
|
||||||
/// Creates a QcowHeader from a reference to a file.
|
/// Creates a QcowHeader from a reference to a file.
|
||||||
pub fn new(f: &mut RawFile) -> Result<QcowHeader> {
|
pub fn new(f: &mut RawFile) -> Result<QcowHeader> {
|
||||||
f.seek(SeekFrom::Start(0)).map_err(Error::ReadingHeader)?;
|
f.rewind().map_err(Error::ReadingHeader)?;
|
||||||
let magic = f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?;
|
let magic = f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?;
|
||||||
if magic != QCOW_MAGIC {
|
if magic != QCOW_MAGIC {
|
||||||
return Err(Error::InvalidMagic);
|
return Err(Error::InvalidMagic);
|
||||||
@@ -208,7 +223,7 @@ impl QcowHeader {
|
|||||||
|
|
||||||
let version = read_u32_from_file(f)?;
|
let version = read_u32_from_file(f)?;
|
||||||
|
|
||||||
Ok(QcowHeader {
|
let mut header = QcowHeader {
|
||||||
magic,
|
magic,
|
||||||
version,
|
version,
|
||||||
backing_file_offset: read_u64_from_file(f)?,
|
backing_file_offset: read_u64_from_file(f)?,
|
||||||
@@ -247,24 +262,58 @@ impl QcowHeader {
|
|||||||
} else {
|
} else {
|
||||||
read_u32_from_file(f)?
|
read_u32_from_file(f)?
|
||||||
},
|
},
|
||||||
})
|
backing_file_path: None,
|
||||||
|
};
|
||||||
|
if header.backing_file_size > MAX_BACKING_FILE_SIZE {
|
||||||
|
return Err(Error::BackingFileTooLong(header.backing_file_size as usize));
|
||||||
|
}
|
||||||
|
if header.backing_file_offset != 0 {
|
||||||
|
f.seek(SeekFrom::Start(header.backing_file_offset))
|
||||||
|
.map_err(Error::ReadingHeader)?;
|
||||||
|
let mut backing_file_name_bytes = vec![0u8; header.backing_file_size as usize];
|
||||||
|
f.read_exact(&mut backing_file_name_bytes)
|
||||||
|
.map_err(Error::ReadingHeader)?;
|
||||||
|
header.backing_file_path = Some(
|
||||||
|
String::from_utf8(backing_file_name_bytes)
|
||||||
|
.map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(header)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a header for the given `size`.
|
pub fn create_for_size_and_path(
|
||||||
pub fn create_for_size(version: u32, size: u64) -> QcowHeader {
|
version: u32,
|
||||||
|
size: u64,
|
||||||
|
backing_file: Option<&str>,
|
||||||
|
) -> Result<QcowHeader> {
|
||||||
|
let header_size = if version == 2 {
|
||||||
|
V2_BARE_HEADER_SIZE
|
||||||
|
} else {
|
||||||
|
V3_BARE_HEADER_SIZE + QCOW_EMPTY_HEADER_EXTENSION_SIZE
|
||||||
|
};
|
||||||
let cluster_bits: u32 = DEFAULT_CLUSTER_BITS;
|
let cluster_bits: u32 = DEFAULT_CLUSTER_BITS;
|
||||||
let cluster_size: u32 = 0x01 << cluster_bits;
|
let cluster_size: u32 = 0x01 << cluster_bits;
|
||||||
|
let max_length: usize = (cluster_size - header_size) as usize;
|
||||||
|
if let Some(path) = backing_file {
|
||||||
|
if path.len() > max_length {
|
||||||
|
return Err(Error::BackingFileTooLong(path.len() - max_length));
|
||||||
|
}
|
||||||
|
}
|
||||||
// L2 blocks are always one cluster long. They contain cluster_size/sizeof(u64) addresses.
|
// L2 blocks are always one cluster long. They contain cluster_size/sizeof(u64) addresses.
|
||||||
let entries_per_cluster: u32 = cluster_size / size_of::<u64>() as u32;
|
let entries_per_cluster: u32 = cluster_size / size_of::<u64>() as u32;
|
||||||
let num_clusters: u32 = div_round_up_u64(size, u64::from(cluster_size)) as u32;
|
let num_clusters: u32 = div_round_up_u64(size, u64::from(cluster_size)) as u32;
|
||||||
let num_l2_clusters: u32 = div_round_up_u32(num_clusters, entries_per_cluster);
|
let num_l2_clusters: u32 = div_round_up_u32(num_clusters, entries_per_cluster);
|
||||||
let l1_clusters: u32 = div_round_up_u32(num_l2_clusters, entries_per_cluster);
|
let l1_clusters: u32 = div_round_up_u32(num_l2_clusters, entries_per_cluster);
|
||||||
let header_clusters = div_round_up_u32(size_of::<QcowHeader>() as u32, cluster_size);
|
let header_clusters = div_round_up_u32(size_of::<QcowHeader>() as u32, cluster_size);
|
||||||
QcowHeader {
|
Ok(QcowHeader {
|
||||||
magic: QCOW_MAGIC,
|
magic: QCOW_MAGIC,
|
||||||
version,
|
version,
|
||||||
backing_file_offset: 0,
|
backing_file_offset: (if backing_file.is_none() {
|
||||||
backing_file_size: 0,
|
0
|
||||||
|
} else {
|
||||||
|
header_size
|
||||||
|
}) as u64,
|
||||||
|
backing_file_size: backing_file.map_or(0, |x| x.len()) as u32,
|
||||||
cluster_bits: DEFAULT_CLUSTER_BITS,
|
cluster_bits: DEFAULT_CLUSTER_BITS,
|
||||||
size,
|
size,
|
||||||
crypt_method: 0,
|
crypt_method: 0,
|
||||||
@@ -293,12 +342,9 @@ impl QcowHeader {
|
|||||||
compatible_features: 0,
|
compatible_features: 0,
|
||||||
autoclear_features: 0,
|
autoclear_features: 0,
|
||||||
refcount_order: DEFAULT_REFCOUNT_ORDER,
|
refcount_order: DEFAULT_REFCOUNT_ORDER,
|
||||||
header_size: if version == 2 {
|
header_size,
|
||||||
V2_BARE_HEADER_SIZE
|
backing_file_path: backing_file.map(String::from),
|
||||||
} else {
|
})
|
||||||
V3_BARE_HEADER_SIZE
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the header to `file`.
|
/// Write the header to `file`.
|
||||||
@@ -328,11 +374,20 @@ impl QcowHeader {
|
|||||||
write_u32_to_file(file, self.refcount_table_clusters)?;
|
write_u32_to_file(file, self.refcount_table_clusters)?;
|
||||||
write_u32_to_file(file, self.nb_snapshots)?;
|
write_u32_to_file(file, self.nb_snapshots)?;
|
||||||
write_u64_to_file(file, self.snapshots_offset)?;
|
write_u64_to_file(file, self.snapshots_offset)?;
|
||||||
write_u64_to_file(file, self.incompatible_features)?;
|
|
||||||
write_u64_to_file(file, self.compatible_features)?;
|
if self.version == 3 {
|
||||||
write_u64_to_file(file, self.autoclear_features)?;
|
write_u64_to_file(file, self.incompatible_features)?;
|
||||||
write_u32_to_file(file, self.refcount_order)?;
|
write_u64_to_file(file, self.compatible_features)?;
|
||||||
write_u32_to_file(file, self.header_size)?;
|
write_u64_to_file(file, self.autoclear_features)?;
|
||||||
|
write_u32_to_file(file, self.refcount_order)?;
|
||||||
|
write_u32_to_file(file, self.header_size)?;
|
||||||
|
write_u32_to_file(file, 0)?; // header extension type: end of header extension area
|
||||||
|
write_u32_to_file(file, 0)?; // length of header extension data: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(backing_file_path) = self.backing_file_path.as_ref() {
|
||||||
|
write!(file, "{}", backing_file_path).map_err(Error::WritingHeader)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the file length by seeking and writing a zero to the last byte. This avoids needing
|
// Set the file length by seeking and writing a zero to the last byte. This avoids needing
|
||||||
// a `File` instead of anything that implements seek as the `file` argument.
|
// a `File` instead of anything that implements seek as the `file` argument.
|
||||||
@@ -366,8 +421,8 @@ fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
/// # use block::qcow::{self, QcowFile, RawFile};
|
||||||
/// # use std::io::{Read, Seek, SeekFrom};
|
/// # use std::io::{Read, Seek, SeekFrom};
|
||||||
/// # use qcow::{self, QcowFile, RawFile};
|
|
||||||
/// # fn test(file: std::fs::File) -> std::io::Result<()> {
|
/// # fn test(file: std::fs::File) -> std::io::Result<()> {
|
||||||
/// let mut raw_img = RawFile::new(file, false);
|
/// let mut raw_img = RawFile::new(file, false);
|
||||||
/// let mut q = QcowFile::from(raw_img).expect("Can't open qcow file");
|
/// let mut q = QcowFile::from(raw_img).expect("Can't open qcow file");
|
||||||
@@ -377,7 +432,7 @@ fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u
|
|||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug)]
|
||||||
pub struct QcowFile {
|
pub struct QcowFile {
|
||||||
raw_file: QcowRawFile,
|
raw_file: QcowRawFile,
|
||||||
header: QcowHeader,
|
header: QcowHeader,
|
||||||
@@ -390,7 +445,7 @@ pub struct QcowFile {
|
|||||||
// List of unreferenced clusters available to be used. unref clusters become available once the
|
// List of unreferenced clusters available to be used. unref clusters become available once the
|
||||||
// removal of references to them have been synced to disk.
|
// removal of references to them have been synced to disk.
|
||||||
avail_clusters: Vec<u64>,
|
avail_clusters: Vec<u64>,
|
||||||
//TODO(dgreid) Add support for backing files. - backing_file: Option<Box<QcowFile<T>>>,
|
backing_file: Option<Box<dyn BlockBackend>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QcowFile {
|
impl QcowFile {
|
||||||
@@ -419,10 +474,20 @@ impl QcowFile {
|
|||||||
return Err(Error::FileTooBig(header.size));
|
return Err(Error::FileTooBig(header.size));
|
||||||
}
|
}
|
||||||
|
|
||||||
// No current support for backing files.
|
let direct_io = file.is_direct();
|
||||||
if header.backing_file_offset != 0 {
|
|
||||||
return Err(Error::BackingFilesNotSupported);
|
let backing_file = if let Some(backing_file_path) = header.backing_file_path.as_ref() {
|
||||||
}
|
let path = backing_file_path.clone();
|
||||||
|
let backing_raw_file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.open(path)
|
||||||
|
.map_err(Error::BackingFileIo)?;
|
||||||
|
let backing_file = crate::create_disk_file(backing_raw_file, direct_io)
|
||||||
|
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||||
|
Some(backing_file)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// Only support two byte refcounts.
|
// Only support two byte refcounts.
|
||||||
let refcount_bits: u64 = 0x01u64
|
let refcount_bits: u64 = 0x01u64
|
||||||
@@ -437,7 +502,6 @@ impl QcowFile {
|
|||||||
if header.refcount_table_clusters == 0 {
|
if header.refcount_table_clusters == 0 {
|
||||||
return Err(Error::NoRefcountClusters);
|
return Err(Error::NoRefcountClusters);
|
||||||
}
|
}
|
||||||
offset_is_cluster_boundary(header.backing_file_offset, header.cluster_bits)?;
|
|
||||||
offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)?;
|
offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)?;
|
||||||
offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)?;
|
offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)?;
|
||||||
// refcount table must be a cluster boundary, and within the file's virtual or actual size.
|
// refcount table must be a cluster boundary, and within the file's virtual or actual size.
|
||||||
@@ -470,7 +534,7 @@ impl QcowFile {
|
|||||||
let mut raw_file =
|
let mut raw_file =
|
||||||
QcowRawFile::from(file, cluster_size).ok_or(Error::InvalidClusterSize)?;
|
QcowRawFile::from(file, cluster_size).ok_or(Error::InvalidClusterSize)?;
|
||||||
if refcount_rebuild_required {
|
if refcount_rebuild_required {
|
||||||
QcowFile::rebuild_refcounts(&mut raw_file, header)?;
|
QcowFile::rebuild_refcounts(&mut raw_file, header.clone())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let entries_per_cluster = cluster_size / size_of::<u64>() as u64;
|
let entries_per_cluster = cluster_size / size_of::<u64>() as u64;
|
||||||
@@ -539,6 +603,7 @@ impl QcowFile {
|
|||||||
current_offset: 0,
|
current_offset: 0,
|
||||||
unref_clusters: Vec::new(),
|
unref_clusters: Vec::new(),
|
||||||
avail_clusters: Vec::new(),
|
avail_clusters: Vec::new(),
|
||||||
|
backing_file,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check that the L1 and refcount tables fit in a 64bit address space.
|
// Check that the L1 and refcount tables fit in a 64bit address space.
|
||||||
@@ -557,9 +622,35 @@ impl QcowFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new QcowFile at the given path.
|
/// Creates a new QcowFile at the given path.
|
||||||
pub fn new(mut file: RawFile, version: u32, virtual_size: u64) -> Result<QcowFile> {
|
pub fn new(file: RawFile, version: u32, virtual_size: u64) -> Result<QcowFile> {
|
||||||
let header = QcowHeader::create_for_size(version, virtual_size);
|
let header = QcowHeader::create_for_size_and_path(version, virtual_size, None)?;
|
||||||
file.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
|
QcowFile::new_from_header(file, header)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new QcowFile at the given path.
|
||||||
|
pub fn new_from_backing(
|
||||||
|
file: RawFile,
|
||||||
|
version: u32,
|
||||||
|
backing_file_name: &str,
|
||||||
|
) -> Result<QcowFile> {
|
||||||
|
let direct_io = file.is_direct();
|
||||||
|
let backing_raw_file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.open(backing_file_name)
|
||||||
|
.map_err(Error::BackingFileIo)?;
|
||||||
|
let backing_file = crate::create_disk_file(backing_raw_file, direct_io)
|
||||||
|
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||||
|
let size = backing_file
|
||||||
|
.size()
|
||||||
|
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||||
|
let header = QcowHeader::create_for_size_and_path(version, size, Some(backing_file_name))?;
|
||||||
|
let mut result = QcowFile::new_from_header(file, header)?;
|
||||||
|
result.backing_file = Some(backing_file);
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_from_header(mut file: RawFile, header: QcowHeader) -> Result<QcowFile> {
|
||||||
|
file.rewind().map_err(Error::SeekingFile)?;
|
||||||
header.write_to(&mut file)?;
|
header.write_to(&mut file)?;
|
||||||
|
|
||||||
let mut qcow = Self::from(file)?;
|
let mut qcow = Self::from(file)?;
|
||||||
@@ -582,6 +673,10 @@ impl QcowFile {
|
|||||||
Ok(qcow)
|
Ok(qcow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_backing_file(&mut self, backing: Option<Box<dyn BlockBackend>>) {
|
||||||
|
self.backing_file = backing;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the `QcowHeader` for this file.
|
/// Returns the `QcowHeader` for this file.
|
||||||
pub fn header(&self) -> &QcowHeader {
|
pub fn header(&self) -> &QcowHeader {
|
||||||
&self.header
|
&self.header
|
||||||
@@ -777,10 +872,8 @@ impl QcowFile {
|
|||||||
refcounts: &mut [u16],
|
refcounts: &mut [u16],
|
||||||
cluster_size: u64,
|
cluster_size: u64,
|
||||||
refblock_clusters: u64,
|
refblock_clusters: u64,
|
||||||
pointers_per_cluster: u64,
|
|
||||||
) -> Result<Vec<u64>> {
|
) -> Result<Vec<u64>> {
|
||||||
let refcount_table_entries = div_round_up_u64(refblock_clusters, pointers_per_cluster);
|
let mut ref_table = vec![0; refblock_clusters as usize];
|
||||||
let mut ref_table = vec![0; refcount_table_entries as usize];
|
|
||||||
let mut first_free_cluster: u64 = 0;
|
let mut first_free_cluster: u64 = 0;
|
||||||
for refblock_addr in &mut ref_table {
|
for refblock_addr in &mut ref_table {
|
||||||
loop {
|
loop {
|
||||||
@@ -812,10 +905,7 @@ impl QcowFile {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Rewrite the header with lazy refcounts enabled while we are rebuilding the tables.
|
// Rewrite the header with lazy refcounts enabled while we are rebuilding the tables.
|
||||||
header.compatible_features |= COMPATIBLE_FEATURES_LAZY_REFCOUNTS;
|
header.compatible_features |= COMPATIBLE_FEATURES_LAZY_REFCOUNTS;
|
||||||
raw_file
|
raw_file.file_mut().rewind().map_err(Error::SeekingFile)?;
|
||||||
.file_mut()
|
|
||||||
.seek(SeekFrom::Start(0))
|
|
||||||
.map_err(Error::SeekingFile)?;
|
|
||||||
header.write_to(raw_file.file_mut())?;
|
header.write_to(raw_file.file_mut())?;
|
||||||
|
|
||||||
for (i, refblock_addr) in ref_table.iter().enumerate() {
|
for (i, refblock_addr) in ref_table.iter().enumerate() {
|
||||||
@@ -850,10 +940,7 @@ impl QcowFile {
|
|||||||
|
|
||||||
// Rewrite the header again, now with lazy refcounts disabled.
|
// Rewrite the header again, now with lazy refcounts disabled.
|
||||||
header.compatible_features &= !COMPATIBLE_FEATURES_LAZY_REFCOUNTS;
|
header.compatible_features &= !COMPATIBLE_FEATURES_LAZY_REFCOUNTS;
|
||||||
raw_file
|
raw_file.file_mut().rewind().map_err(Error::SeekingFile)?;
|
||||||
.file_mut()
|
|
||||||
.seek(SeekFrom::Start(0))
|
|
||||||
.map_err(Error::SeekingFile)?;
|
|
||||||
header.write_to(raw_file.file_mut())?;
|
header.write_to(raw_file.file_mut())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -902,17 +989,12 @@ impl QcowFile {
|
|||||||
|
|
||||||
// Find all references clusters and rebuild refcounts.
|
// Find all references clusters and rebuild refcounts.
|
||||||
set_header_refcount(&mut refcounts, cluster_size)?;
|
set_header_refcount(&mut refcounts, cluster_size)?;
|
||||||
set_l1_refcounts(&mut refcounts, header, cluster_size)?;
|
set_l1_refcounts(&mut refcounts, header.clone(), cluster_size)?;
|
||||||
set_data_refcounts(&mut refcounts, header, cluster_size, raw_file)?;
|
set_data_refcounts(&mut refcounts, header.clone(), cluster_size, raw_file)?;
|
||||||
set_refcount_table_refcounts(&mut refcounts, header, cluster_size)?;
|
set_refcount_table_refcounts(&mut refcounts, header.clone(), cluster_size)?;
|
||||||
|
|
||||||
// Allocate clusters to store the new reference count blocks.
|
// Allocate clusters to store the new reference count blocks.
|
||||||
let ref_table = alloc_refblocks(
|
let ref_table = alloc_refblocks(&mut refcounts, cluster_size, refblock_clusters)?;
|
||||||
&mut refcounts,
|
|
||||||
cluster_size,
|
|
||||||
refblock_clusters,
|
|
||||||
pointers_per_cluster,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Write updated reference counts and point the reftable at them.
|
// Write updated reference counts and point the reftable at them.
|
||||||
write_refblocks(
|
write_refblocks(
|
||||||
@@ -1024,7 +1106,7 @@ impl QcowFile {
|
|||||||
let l2_table = if l2_addr_disk == 0 {
|
let l2_table = if l2_addr_disk == 0 {
|
||||||
// Allocate a new cluster to store the L2 table and update the L1 table to point
|
// Allocate a new cluster to store the L2 table and update the L1 table to point
|
||||||
// to the new table.
|
// to the new table.
|
||||||
let new_addr: u64 = self.get_new_cluster()?;
|
let new_addr: u64 = self.get_new_cluster(None)?;
|
||||||
// The cluster refcount starts at one meaning it is used but doesn't need COW.
|
// The cluster refcount starts at one meaning it is used but doesn't need COW.
|
||||||
set_refcounts.push((new_addr, 1));
|
set_refcounts.push((new_addr, 1));
|
||||||
self.l1_table[l1_index] = new_addr;
|
self.l1_table[l1_index] = new_addr;
|
||||||
@@ -1045,8 +1127,18 @@ impl QcowFile {
|
|||||||
|
|
||||||
let cluster_addr = match self.l2_cache.get(l1_index).unwrap()[l2_index] {
|
let cluster_addr = match self.l2_cache.get(l1_index).unwrap()[l2_index] {
|
||||||
0 => {
|
0 => {
|
||||||
|
let initial_data = if let Some(backing) = self.backing_file.as_mut() {
|
||||||
|
let cluster_size = self.raw_file.cluster_size();
|
||||||
|
let cluster_begin = address - (address % cluster_size);
|
||||||
|
let mut cluster_data = vec![0u8; cluster_size as usize];
|
||||||
|
backing.seek(SeekFrom::Start(cluster_begin))?;
|
||||||
|
backing.read_exact(&mut cluster_data)?;
|
||||||
|
Some(cluster_data)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
// Need to allocate a data cluster
|
// Need to allocate a data cluster
|
||||||
let cluster_addr = self.append_data_cluster()?;
|
let cluster_addr = self.append_data_cluster(initial_data)?;
|
||||||
self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?;
|
self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?;
|
||||||
cluster_addr
|
cluster_addr
|
||||||
}
|
}
|
||||||
@@ -1083,7 +1175,7 @@ impl QcowFile {
|
|||||||
// Allocate a new cluster to store the L2 table and update the L1 table to point
|
// Allocate a new cluster to store the L2 table and update the L1 table to point
|
||||||
// to the new table. The cluster will be written when the cache is flushed, no
|
// to the new table. The cluster will be written when the cache is flushed, no
|
||||||
// need to copy the data now.
|
// need to copy the data now.
|
||||||
let new_addr: u64 = self.get_new_cluster()?;
|
let new_addr: u64 = self.get_new_cluster(None)?;
|
||||||
// The cluster refcount starts at one indicating it is used but doesn't need
|
// The cluster refcount starts at one indicating it is used but doesn't need
|
||||||
// COW.
|
// COW.
|
||||||
set_refcounts.push((new_addr, 1));
|
set_refcounts.push((new_addr, 1));
|
||||||
@@ -1095,15 +1187,22 @@ impl QcowFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Allocate a new cluster and return its offset within the raw file.
|
// Allocate a new cluster and return its offset within the raw file.
|
||||||
fn get_new_cluster(&mut self) -> std::io::Result<u64> {
|
fn get_new_cluster(&mut self, initial_data: Option<Vec<u8>>) -> std::io::Result<u64> {
|
||||||
// First use a pre allocated cluster if one is available.
|
// First use a pre allocated cluster if one is available.
|
||||||
if let Some(free_cluster) = self.avail_clusters.pop() {
|
if let Some(free_cluster) = self.avail_clusters.pop() {
|
||||||
self.raw_file.zero_cluster(free_cluster)?;
|
if let Some(initial_data) = initial_data {
|
||||||
|
self.raw_file.write_cluster(free_cluster, initial_data)?;
|
||||||
|
} else {
|
||||||
|
self.raw_file.zero_cluster(free_cluster)?;
|
||||||
|
}
|
||||||
return Ok(free_cluster);
|
return Ok(free_cluster);
|
||||||
}
|
}
|
||||||
|
|
||||||
let max_valid_cluster_offset = self.refcounts.max_valid_cluster_offset();
|
let max_valid_cluster_offset = self.refcounts.max_valid_cluster_offset();
|
||||||
if let Some(new_cluster) = self.raw_file.add_cluster_end(max_valid_cluster_offset)? {
|
if let Some(new_cluster) = self.raw_file.add_cluster_end(max_valid_cluster_offset)? {
|
||||||
|
if let Some(initial_data) = initial_data {
|
||||||
|
self.raw_file.write_cluster(new_cluster, initial_data)?;
|
||||||
|
}
|
||||||
Ok(new_cluster)
|
Ok(new_cluster)
|
||||||
} else {
|
} else {
|
||||||
error!("No free clusters in get_new_cluster()");
|
error!("No free clusters in get_new_cluster()");
|
||||||
@@ -1113,8 +1212,8 @@ impl QcowFile {
|
|||||||
|
|
||||||
// Allocate and initialize a new data cluster. Returns the offset of the
|
// Allocate and initialize a new data cluster. Returns the offset of the
|
||||||
// cluster in to the file on success.
|
// cluster in to the file on success.
|
||||||
fn append_data_cluster(&mut self) -> std::io::Result<u64> {
|
fn append_data_cluster(&mut self, initial_data: Option<Vec<u8>>) -> std::io::Result<u64> {
|
||||||
let new_addr: u64 = self.get_new_cluster()?;
|
let new_addr: u64 = self.get_new_cluster(initial_data)?;
|
||||||
// The cluster refcount starts at one indicating it is used but doesn't need COW.
|
// The cluster refcount starts at one indicating it is used but doesn't need COW.
|
||||||
let mut newly_unref = self.set_cluster_refcount(new_addr, 1)?;
|
let mut newly_unref = self.set_cluster_refcount(new_addr, 1)?;
|
||||||
self.unref_clusters.append(&mut newly_unref);
|
self.unref_clusters.append(&mut newly_unref);
|
||||||
@@ -1240,7 +1339,7 @@ impl QcowFile {
|
|||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
io::Error::new(
|
io::Error::new(
|
||||||
io::ErrorKind::InvalidData,
|
io::ErrorKind::InvalidData,
|
||||||
format!("failed to get cluster refcount: {}", e),
|
format!("failed to get cluster refcount: {e}"),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
if refcount == 0 {
|
if refcount == 0 {
|
||||||
@@ -1348,7 +1447,7 @@ impl QcowFile {
|
|||||||
}
|
}
|
||||||
Err(refcount::Error::NeedNewCluster) => {
|
Err(refcount::Error::NeedNewCluster) => {
|
||||||
// Allocate the cluster and call set_cluster_refcount again.
|
// Allocate the cluster and call set_cluster_refcount again.
|
||||||
let addr = self.get_new_cluster()?;
|
let addr = self.get_new_cluster(None)?;
|
||||||
added_clusters.push(addr);
|
added_clusters.push(addr);
|
||||||
new_cluster = Some((
|
new_cluster = Some((
|
||||||
addr,
|
addr,
|
||||||
@@ -1431,6 +1530,9 @@ impl Read for QcowFile {
|
|||||||
self.raw_file
|
self.raw_file
|
||||||
.file_mut()
|
.file_mut()
|
||||||
.read_exact(&mut buf[nread..(nread + count)])?;
|
.read_exact(&mut buf[nread..(nread + count)])?;
|
||||||
|
} else if let Some(backing) = self.backing_file.as_mut() {
|
||||||
|
backing.seek(SeekFrom::Start(curr_addr))?;
|
||||||
|
backing.read_exact(&mut buf[nread..(nread + count)])?;
|
||||||
} else {
|
} else {
|
||||||
// Previously unwritten region, return zeros
|
// Previously unwritten region, return zeros
|
||||||
for b in &mut buf[nread..(nread + count)] {
|
for b in &mut buf[nread..(nread + count)] {
|
||||||
@@ -1573,6 +1675,12 @@ impl SeekHole for QcowFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BlockBackend for QcowFile {
|
||||||
|
fn size(&self) -> std::result::Result<u64, crate::Error> {
|
||||||
|
Ok(self.virtual_size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Returns an Error if the given offset doesn't align to a cluster boundary.
|
// Returns an Error if the given offset doesn't align to a cluster boundary.
|
||||||
fn offset_is_cluster_boundary(offset: u64, cluster_bits: u32) -> Result<()> {
|
fn offset_is_cluster_boundary(offset: u64, cluster_bits: u32) -> Result<()> {
|
||||||
if offset & ((0x01 << cluster_bits) - 1) != 0 {
|
if offset & ((0x01 << cluster_bits) - 1) != 0 {
|
||||||
@@ -1656,9 +1764,7 @@ where
|
|||||||
R: Read + Seek + SeekHole,
|
R: Read + Seek + SeekHole,
|
||||||
{
|
{
|
||||||
let src_size = reader.seek(SeekFrom::End(0)).map_err(Error::SeekingFile)?;
|
let src_size = reader.seek(SeekFrom::End(0)).map_err(Error::SeekingFile)?;
|
||||||
reader
|
reader.rewind().map_err(Error::SeekingFile)?;
|
||||||
.seek(SeekFrom::Start(0))
|
|
||||||
.map_err(Error::SeekingFile)?;
|
|
||||||
|
|
||||||
// Ensure the destination file is empty before writing to it.
|
// Ensure the destination file is empty before writing to it.
|
||||||
dst_file.set_len(0).map_err(Error::SettingFileSize)?;
|
dst_file.set_len(0).map_err(Error::SettingFileSize)?;
|
||||||
@@ -1700,10 +1806,8 @@ pub fn convert(mut src_file: RawFile, dst_file: RawFile, dst_type: ImageType) ->
|
|||||||
|
|
||||||
/// Detect the type of an image file by checking for a valid qcow2 header.
|
/// Detect the type of an image file by checking for a valid qcow2 header.
|
||||||
pub fn detect_image_type(file: &mut RawFile) -> Result<ImageType> {
|
pub fn detect_image_type(file: &mut RawFile) -> Result<ImageType> {
|
||||||
let orig_seek = file
|
let orig_seek = file.stream_position().map_err(Error::SeekingFile)?;
|
||||||
.seek(SeekFrom::Current(0))
|
file.rewind().map_err(Error::SeekingFile)?;
|
||||||
.map_err(Error::SeekingFile)?;
|
|
||||||
file.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
|
|
||||||
let magic = file.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?;
|
let magic = file.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?;
|
||||||
let image_type = if magic == QCOW_MAGIC {
|
let image_type = if magic == QCOW_MAGIC {
|
||||||
ImageType::Qcow2
|
ImageType::Qcow2
|
||||||
@@ -1787,16 +1891,19 @@ mod tests {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn basic_file(header: &[u8]) -> RawFile {
|
||||||
|
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
||||||
|
disk_file.write_all(header).unwrap();
|
||||||
|
disk_file.set_len(0x1_0000_0000).unwrap();
|
||||||
|
disk_file.rewind().unwrap();
|
||||||
|
disk_file
|
||||||
|
}
|
||||||
|
|
||||||
fn with_basic_file<F>(header: &[u8], mut testfn: F)
|
fn with_basic_file<F>(header: &[u8], mut testfn: F)
|
||||||
where
|
where
|
||||||
F: FnMut(RawFile),
|
F: FnMut(RawFile),
|
||||||
{
|
{
|
||||||
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
testfn(basic_file(header)); // File closed when the function exits.
|
||||||
disk_file.write_all(header).unwrap();
|
|
||||||
disk_file.set_len(0x1_0000_0000).unwrap();
|
|
||||||
disk_file.seek(SeekFrom::Start(0)).unwrap();
|
|
||||||
|
|
||||||
testfn(disk_file); // File closed when the function exits.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_default_file<F>(file_size: u64, direct: bool, mut testfn: F)
|
fn with_default_file<F>(file_size: u64, direct: bool, mut testfn: F)
|
||||||
@@ -1809,25 +1916,59 @@ mod tests {
|
|||||||
testfn(qcow_file); // File closed when the function exits.
|
testfn(qcow_file); // File closed when the function exits.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_read_start_backing_v2() {
|
||||||
|
let disk_file = basic_file(&valid_header_v2());
|
||||||
|
let mut backing = QcowFile::from(disk_file).unwrap();
|
||||||
|
backing
|
||||||
|
.write_all(b"test first bytes")
|
||||||
|
.expect("Failed to write test string.");
|
||||||
|
let mut buf = [0u8; 4];
|
||||||
|
let wrapping_disk_file = basic_file(&valid_header_v2());
|
||||||
|
let mut wrapping = QcowFile::from(wrapping_disk_file).unwrap();
|
||||||
|
wrapping.set_backing_file(Some(Box::new(backing)));
|
||||||
|
wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek.");
|
||||||
|
wrapping.read_exact(&mut buf).expect("Failed to read.");
|
||||||
|
assert_eq!(&buf, b"test");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_read_start_backing_v3() {
|
||||||
|
let disk_file = basic_file(&valid_header_v3());
|
||||||
|
let mut backing = QcowFile::from(disk_file).unwrap();
|
||||||
|
backing
|
||||||
|
.write_all(b"test first bytes")
|
||||||
|
.expect("Failed to write test string.");
|
||||||
|
let mut buf = [0u8; 4];
|
||||||
|
let wrapping_disk_file = basic_file(&valid_header_v3());
|
||||||
|
let mut wrapping = QcowFile::from(wrapping_disk_file).unwrap();
|
||||||
|
wrapping.set_backing_file(Some(Box::new(backing)));
|
||||||
|
wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek.");
|
||||||
|
wrapping.read_exact(&mut buf).expect("Failed to read.");
|
||||||
|
assert_eq!(&buf, b"test");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_header_v2() {
|
fn default_header_v2() {
|
||||||
let header = QcowHeader::create_for_size(2, 0x10_0000);
|
let header = QcowHeader::create_for_size_and_path(2, 0x10_0000, None);
|
||||||
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
||||||
header
|
header
|
||||||
|
.expect("Failed to create header.")
|
||||||
.write_to(&mut disk_file)
|
.write_to(&mut disk_file)
|
||||||
.expect("Failed to write header to temporary file.");
|
.expect("Failed to write header to temporary file.");
|
||||||
disk_file.seek(SeekFrom::Start(0)).unwrap();
|
disk_file.rewind().unwrap();
|
||||||
QcowFile::from(disk_file).expect("Failed to create Qcow from default Header");
|
QcowFile::from(disk_file).expect("Failed to create Qcow from default Header");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_header_v3() {
|
fn default_header_v3() {
|
||||||
let header = QcowHeader::create_for_size(3, 0x10_0000);
|
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, None);
|
||||||
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
||||||
header
|
header
|
||||||
|
.expect("Failed to create header.")
|
||||||
.write_to(&mut disk_file)
|
.write_to(&mut disk_file)
|
||||||
.expect("Failed to write header to temporary file.");
|
.expect("Failed to write header to temporary file.");
|
||||||
disk_file.seek(SeekFrom::Start(0)).unwrap();
|
disk_file.rewind().unwrap();
|
||||||
QcowFile::from(disk_file).expect("Failed to create Qcow from default Header");
|
QcowFile::from(disk_file).expect("Failed to create Qcow from default Header");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1847,6 +1988,40 @@ mod tests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn header_v2_with_backing() {
|
||||||
|
let header = QcowHeader::create_for_size_and_path(2, 0x10_0000, Some("/my/path/to/a/file"))
|
||||||
|
.expect("Failed to create header.");
|
||||||
|
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
||||||
|
header
|
||||||
|
.write_to(&mut disk_file)
|
||||||
|
.expect("Failed to write header to shm.");
|
||||||
|
disk_file.rewind().unwrap();
|
||||||
|
let read_header = QcowHeader::new(&mut disk_file).expect("Failed to create header.");
|
||||||
|
assert_eq!(
|
||||||
|
header.backing_file_path,
|
||||||
|
Some(String::from("/my/path/to/a/file"))
|
||||||
|
);
|
||||||
|
assert_eq!(read_header.backing_file_path, header.backing_file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn header_v3_with_backing() {
|
||||||
|
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, Some("/my/path/to/a/file"))
|
||||||
|
.expect("Failed to create header.");
|
||||||
|
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
||||||
|
header
|
||||||
|
.write_to(&mut disk_file)
|
||||||
|
.expect("Failed to write header to shm.");
|
||||||
|
disk_file.rewind().unwrap();
|
||||||
|
let read_header = QcowHeader::new(&mut disk_file).expect("Failed to create header.");
|
||||||
|
assert_eq!(
|
||||||
|
header.backing_file_path,
|
||||||
|
Some(String::from("/my/path/to/a/file"))
|
||||||
|
);
|
||||||
|
assert_eq!(read_header.backing_file_path, header.backing_file_path);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_magic() {
|
fn invalid_magic() {
|
||||||
let invalid_header = vec![0x51u8, 0x46, 0x4a, 0xfb];
|
let invalid_header = vec![0x51u8, 0x46, 0x4a, 0xfb];
|
||||||
@@ -1955,12 +2130,32 @@ mod tests {
|
|||||||
q.write_all(b"test first bytes")
|
q.write_all(b"test first bytes")
|
||||||
.expect("Failed to write test string.");
|
.expect("Failed to write test string.");
|
||||||
let mut buf = [0u8; 4];
|
let mut buf = [0u8; 4];
|
||||||
q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
|
q.rewind().expect("Failed to seek.");
|
||||||
q.read_exact(&mut buf).expect("Failed to read.");
|
q.read_exact(&mut buf).expect("Failed to read.");
|
||||||
assert_eq!(&buf, b"test");
|
assert_eq!(&buf, b"test");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_read_start_backing_overlap() {
|
||||||
|
let disk_file = basic_file(&valid_header_v3());
|
||||||
|
let mut backing = QcowFile::from(disk_file).unwrap();
|
||||||
|
backing
|
||||||
|
.write_all(b"test first bytes")
|
||||||
|
.expect("Failed to write test string.");
|
||||||
|
let wrapping_disk_file = basic_file(&valid_header_v3());
|
||||||
|
let mut wrapping = QcowFile::from(wrapping_disk_file).unwrap();
|
||||||
|
wrapping.set_backing_file(Some(Box::new(backing)));
|
||||||
|
wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek.");
|
||||||
|
wrapping
|
||||||
|
.write_all(b"TEST")
|
||||||
|
.expect("Failed to write second test string.");
|
||||||
|
let mut buf = [0u8; 10];
|
||||||
|
wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek.");
|
||||||
|
wrapping.read_exact(&mut buf).expect("Failed to read.");
|
||||||
|
assert_eq!(&buf, b"TEST first");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn offset_write_read() {
|
fn offset_write_read() {
|
||||||
with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
|
with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
|
||||||
@@ -2007,15 +2202,15 @@ mod tests {
|
|||||||
let mut q = QcowFile::from(disk_file).unwrap();
|
let mut q = QcowFile::from(disk_file).unwrap();
|
||||||
// Write some test data.
|
// Write some test data.
|
||||||
let b = [0x55u8; CHUNK_SIZE];
|
let b = [0x55u8; CHUNK_SIZE];
|
||||||
q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
|
q.rewind().expect("Failed to seek.");
|
||||||
q.write_all(&b).expect("Failed to write test string.");
|
q.write_all(&b).expect("Failed to write test string.");
|
||||||
// Overwrite the full cluster with zeroes.
|
// Overwrite the full cluster with zeroes.
|
||||||
q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
|
q.rewind().expect("Failed to seek.");
|
||||||
let nwritten = q.write_zeroes(CHUNK_SIZE).expect("Failed to write zeroes.");
|
let nwritten = q.write_zeroes(CHUNK_SIZE).expect("Failed to write zeroes.");
|
||||||
assert_eq!(nwritten, CHUNK_SIZE);
|
assert_eq!(nwritten, CHUNK_SIZE);
|
||||||
// Verify that the data was zeroed out.
|
// Verify that the data was zeroed out.
|
||||||
let mut buf = [0u8; CHUNK_SIZE];
|
let mut buf = [0u8; CHUNK_SIZE];
|
||||||
q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
|
q.rewind().expect("Failed to seek.");
|
||||||
q.read_exact(&mut buf).expect("Failed to read.");
|
q.read_exact(&mut buf).expect("Failed to read.");
|
||||||
assert_eq!(buf[0], 0);
|
assert_eq!(buf[0], 0);
|
||||||
assert_eq!(buf[CHUNK_SIZE - 1], 0);
|
assert_eq!(buf[CHUNK_SIZE - 1], 0);
|
||||||
@@ -2457,7 +2652,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check that address 0 is still zeros.
|
// Check that address 0 is still zeros.
|
||||||
qcow_file.seek(SeekFrom::Start(0)).expect("Failed to seek.");
|
qcow_file.rewind().expect("Failed to seek.");
|
||||||
let nread = qcow_file.read(&mut readback).expect("Failed to read.");
|
let nread = qcow_file.read(&mut readback).expect("Failed to read.");
|
||||||
assert_eq!(nread, BLOCK_SIZE);
|
assert_eq!(nread, BLOCK_SIZE);
|
||||||
for read in readback.iter() {
|
for read in readback.iter() {
|
||||||
@@ -2481,7 +2676,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn seek_cur(file: &mut QcowFile) -> u64 {
|
fn seek_cur(file: &mut QcowFile) -> u64 {
|
||||||
file.seek(SeekFrom::Current(0)).unwrap()
|
file.stream_position().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2545,7 +2740,7 @@ mod tests {
|
|||||||
assert_eq!(seek_cur(&mut file), 0xFFFF);
|
assert_eq!(seek_cur(&mut file), 0xFFFF);
|
||||||
|
|
||||||
// seek_hole at or after the end of the file should return None
|
// seek_hole at or after the end of the file should return None
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.rewind().unwrap();
|
||||||
assert_eq!(file.seek_hole(0x30000).unwrap(), None);
|
assert_eq!(file.seek_hole(0x30000).unwrap(), None);
|
||||||
assert_eq!(seek_cur(&mut file), 0);
|
assert_eq!(seek_cur(&mut file), 0);
|
||||||
assert_eq!(file.seek_hole(0x30001).unwrap(), None);
|
assert_eq!(file.seek_hole(0x30001).unwrap(), None);
|
||||||
@@ -2563,33 +2758,33 @@ mod tests {
|
|||||||
assert_eq!(seek_cur(&mut file), 0xFFFF);
|
assert_eq!(seek_cur(&mut file), 0xFFFF);
|
||||||
|
|
||||||
// seek_hole within data should return the next hole
|
// seek_hole within data should return the next hole
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.rewind().unwrap();
|
||||||
assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
|
assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
|
||||||
assert_eq!(seek_cur(&mut file), 0x20000);
|
assert_eq!(seek_cur(&mut file), 0x20000);
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.rewind().unwrap();
|
||||||
assert_eq!(file.seek_hole(0x10001).unwrap(), Some(0x20000));
|
assert_eq!(file.seek_hole(0x10001).unwrap(), Some(0x20000));
|
||||||
assert_eq!(seek_cur(&mut file), 0x20000);
|
assert_eq!(seek_cur(&mut file), 0x20000);
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.rewind().unwrap();
|
||||||
assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
|
assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
|
||||||
assert_eq!(seek_cur(&mut file), 0x20000);
|
assert_eq!(seek_cur(&mut file), 0x20000);
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.rewind().unwrap();
|
||||||
assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
|
assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
|
||||||
assert_eq!(seek_cur(&mut file), 0xFFFF);
|
assert_eq!(seek_cur(&mut file), 0xFFFF);
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.rewind().unwrap();
|
||||||
assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
|
assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
|
||||||
assert_eq!(seek_cur(&mut file), 0x20000);
|
assert_eq!(seek_cur(&mut file), 0x20000);
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.rewind().unwrap();
|
||||||
assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
|
assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
|
||||||
assert_eq!(seek_cur(&mut file), 0x20000);
|
assert_eq!(seek_cur(&mut file), 0x20000);
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.rewind().unwrap();
|
||||||
assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x20000));
|
assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x20000));
|
||||||
assert_eq!(seek_cur(&mut file), 0x20000);
|
assert_eq!(seek_cur(&mut file), 0x20000);
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.rewind().unwrap();
|
||||||
assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x20001));
|
assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x20001));
|
||||||
assert_eq!(seek_cur(&mut file), 0x20001);
|
assert_eq!(seek_cur(&mut file), 0x20001);
|
||||||
|
|
||||||
// seek_hole at EOF should return None
|
// seek_hole at EOF should return None
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.rewind().unwrap();
|
||||||
assert_eq!(file.seek_hole(0x30000).unwrap(), None);
|
assert_eq!(file.seek_hole(0x30000).unwrap(), None);
|
||||||
assert_eq!(seek_cur(&mut file), 0);
|
assert_eq!(seek_cur(&mut file), 0);
|
||||||
|
|
||||||
@@ -2600,10 +2795,10 @@ mod tests {
|
|||||||
// seek_hole within [0x20000, 0x30000) should now find the hole at EOF
|
// seek_hole within [0x20000, 0x30000) should now find the hole at EOF
|
||||||
assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x30000));
|
assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x30000));
|
||||||
assert_eq!(seek_cur(&mut file), 0x30000);
|
assert_eq!(seek_cur(&mut file), 0x30000);
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.rewind().unwrap();
|
||||||
assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x30000));
|
assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x30000));
|
||||||
assert_eq!(seek_cur(&mut file), 0x30000);
|
assert_eq!(seek_cur(&mut file), 0x30000);
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.rewind().unwrap();
|
||||||
assert_eq!(file.seek_hole(0x30000).unwrap(), None);
|
assert_eq!(file.seek_hole(0x30000).unwrap(), None);
|
||||||
assert_eq!(seek_cur(&mut file), 0);
|
assert_eq!(seek_cur(&mut file), 0);
|
||||||
});
|
});
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
use super::RawFile;
|
use super::RawFile;
|
||||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use std::io::{self, BufWriter, Seek, SeekFrom};
|
use std::io::{self, BufWriter, Seek, SeekFrom, Write};
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use vmm_sys_util::write_zeroes::WriteZeroes;
|
use vmm_sys_util::write_zeroes::WriteZeroes;
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ impl QcowRawFile {
|
|||||||
non_zero_flags: u64,
|
non_zero_flags: u64,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
self.file.seek(SeekFrom::Start(offset))?;
|
self.file.seek(SeekFrom::Start(offset))?;
|
||||||
let mut buffer = BufWriter::with_capacity(table.len() * size_of::<u64>(), &mut self.file);
|
let mut buffer = BufWriter::with_capacity(std::mem::size_of_val(table), &mut self.file);
|
||||||
for addr in table {
|
for addr in table {
|
||||||
let val = if *addr == 0 {
|
let val = if *addr == 0 {
|
||||||
0
|
0
|
||||||
@@ -91,7 +91,8 @@ impl QcowRawFile {
|
|||||||
/// Writes a refcount block to the file.
|
/// Writes a refcount block to the file.
|
||||||
pub fn write_refcount_block(&mut self, offset: u64, table: &[u16]) -> io::Result<()> {
|
pub fn write_refcount_block(&mut self, offset: u64, table: &[u16]) -> io::Result<()> {
|
||||||
self.file.seek(SeekFrom::Start(offset))?;
|
self.file.seek(SeekFrom::Start(offset))?;
|
||||||
let mut buffer = BufWriter::with_capacity(table.len() * size_of::<u16>(), &mut self.file);
|
let mut buffer = BufWriter::with_capacity(std::mem::size_of_val(table), &mut self.file);
|
||||||
|
|
||||||
for count in table {
|
for count in table {
|
||||||
buffer.write_u16::<BigEndian>(*count)?;
|
buffer.write_u16::<BigEndian>(*count)?;
|
||||||
}
|
}
|
||||||
@@ -136,6 +137,13 @@ impl QcowRawFile {
|
|||||||
self.file.write_zeroes(cluster_size)?;
|
self.file.write_zeroes(cluster_size)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes
|
||||||
|
pub fn write_cluster(&mut self, address: u64, data: Vec<u8>) -> io::Result<()> {
|
||||||
|
let cluster_size = self.cluster_size as usize;
|
||||||
|
self.file.seek(SeekFrom::Start(address))?;
|
||||||
|
self.file.write_all(&data[0..cluster_size])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for QcowRawFile {
|
impl Clone for QcowRawFile {
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
|
use crate::BlockBackend;
|
||||||
use libc::c_void;
|
use libc::c_void;
|
||||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
@@ -22,6 +23,7 @@ pub struct RawFile {
|
|||||||
file: File,
|
file: File,
|
||||||
alignment: usize,
|
alignment: usize,
|
||||||
position: u64,
|
position: u64,
|
||||||
|
direct_io: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const BLK_ALIGNMENTS: [usize; 2] = [512, 4096];
|
const BLK_ALIGNMENTS: [usize; 2] = [512, 4096];
|
||||||
@@ -64,6 +66,7 @@ impl RawFile {
|
|||||||
file,
|
file,
|
||||||
alignment,
|
alignment,
|
||||||
position: 0,
|
position: 0,
|
||||||
|
direct_io,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +105,7 @@ impl RawFile {
|
|||||||
file: self.file.try_clone().expect("RawFile cloning failed"),
|
file: self.file.try_clone().expect("RawFile cloning failed"),
|
||||||
alignment: self.alignment,
|
alignment: self.alignment,
|
||||||
position: self.position,
|
position: self.position,
|
||||||
|
direct_io: self.direct_io,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +116,10 @@ impl RawFile {
|
|||||||
pub fn sync_data(&self) -> std::io::Result<()> {
|
pub fn sync_data(&self) -> std::io::Result<()> {
|
||||||
self.file.sync_data()
|
self.file.sync_data()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_direct(&self) -> bool {
|
||||||
|
self.direct_io
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Read for RawFile {
|
impl Read for RawFile {
|
||||||
@@ -231,6 +239,7 @@ impl Write for RawFile {
|
|||||||
return Err(io::Error::last_os_error());
|
return Err(io::Error::last_os_error());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SAFETY: tmp_ptr is at least rounded_len long
|
||||||
let tmp_buf = unsafe { slice::from_raw_parts_mut(tmp_ptr, rounded_len) };
|
let tmp_buf = unsafe { slice::from_raw_parts_mut(tmp_ptr, rounded_len) };
|
||||||
|
|
||||||
// This can eventually replaced with read_at once its interface
|
// This can eventually replaced with read_at once its interface
|
||||||
@@ -342,12 +351,19 @@ impl SeekHole for RawFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BlockBackend for RawFile {
|
||||||
|
fn size(&self) -> std::result::Result<u64, crate::Error> {
|
||||||
|
Ok(self.metadata().map_err(crate::Error::RawFileError)?.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Clone for RawFile {
|
impl Clone for RawFile {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
RawFile {
|
RawFile {
|
||||||
file: self.file.try_clone().expect("RawFile cloning failed"),
|
file: self.file.try_clone().expect("RawFile cloning failed"),
|
||||||
alignment: self.alignment,
|
alignment: self.alignment,
|
||||||
position: self.position,
|
position: self.position,
|
||||||
|
direct_io: self.direct_io,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,8 +7,10 @@ use std::io;
|
|||||||
|
|
||||||
use libc::EINVAL;
|
use libc::EINVAL;
|
||||||
|
|
||||||
use crate::qcow_raw_file::QcowRawFile;
|
use crate::qcow::{
|
||||||
use crate::vec_cache::{CacheMap, Cacheable, VecCache};
|
qcow_raw_file::QcowRawFile,
|
||||||
|
vec_cache::{CacheMap, Cacheable, VecCache},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@@ -31,16 +33,14 @@ impl Display for Error {
|
|||||||
use self::Error::*;
|
use self::Error::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
EvictingRefCounts(e) => write!(
|
EvictingRefCounts(e) => {
|
||||||
f,
|
write!(f, "failed to write a refblock from the cache to disk: {e}")
|
||||||
"failed to write a refblock from the cache to disk: {}",
|
}
|
||||||
e
|
|
||||||
),
|
|
||||||
InvalidIndex => write!(f, "address requested is not within the range of the disk"),
|
InvalidIndex => write!(f, "address requested is not within the range of the disk"),
|
||||||
NeedCluster(addr) => write!(f, "cluster with addr={} needs to be read", addr),
|
NeedCluster(addr) => write!(f, "cluster with addr={addr} needs to be read"),
|
||||||
NeedNewCluster => write!(f, "new cluster needs to be allocated for refcounts"),
|
NeedNewCluster => write!(f, "new cluster needs to be allocated for refcounts"),
|
||||||
ReadingRefCounts(e) => {
|
ReadingRefCounts(e) => {
|
||||||
write!(f, "failed to read the file into the refcount cache: {}", e)
|
write!(f, "failed to read the file into the refcount cache: {e}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,9 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
||||||
|
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
|
||||||
use crate::AsyncAdaptor;
|
use crate::AsyncAdaptor;
|
||||||
use qcow::{QcowFile, RawFile, Result as QcowResult};
|
use std::collections::VecDeque;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
@@ -37,7 +38,7 @@ impl DiskFile for QcowDiskSync {
|
|||||||
pub struct QcowSync {
|
pub struct QcowSync {
|
||||||
qcow_file: Arc<Mutex<QcowFile>>,
|
qcow_file: Arc<Mutex<QcowFile>>,
|
||||||
eventfd: EventFd,
|
eventfd: EventFd,
|
||||||
completion_list: Vec<(u64, i32)>,
|
completion_list: VecDeque<(u64, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QcowSync {
|
impl QcowSync {
|
||||||
@@ -46,7 +47,7 @@ impl QcowSync {
|
|||||||
qcow_file,
|
qcow_file,
|
||||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
||||||
.expect("Failed creating EventFd for QcowSync"),
|
.expect("Failed creating EventFd for QcowSync"),
|
||||||
completion_list: Vec::new(),
|
completion_list: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +66,7 @@ impl AsyncIo for QcowSync {
|
|||||||
fn read_vectored(
|
fn read_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: Vec<libc::iovec>,
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
self.qcow_file.read_vectored_sync(
|
self.qcow_file.read_vectored_sync(
|
||||||
@@ -80,7 +81,7 @@ impl AsyncIo for QcowSync {
|
|||||||
fn write_vectored(
|
fn write_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: Vec<libc::iovec>,
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
self.qcow_file.write_vectored_sync(
|
self.qcow_file.write_vectored_sync(
|
||||||
@@ -97,7 +98,7 @@ impl AsyncIo for QcowSync {
|
|||||||
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
|
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||||
self.completion_list.drain(..).collect()
|
self.completion_list.pop_front()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,9 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, DiskTopology,
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||||
};
|
};
|
||||||
|
use crate::DiskTopology;
|
||||||
use io_uring::{opcode, squeue, types, IoUring};
|
use io_uring::{opcode, squeue, types, IoUring};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
@@ -36,7 +37,7 @@ impl DiskFile for RawFileDisk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn topology(&mut self) -> DiskTopology {
|
fn topology(&mut self) -> DiskTopology {
|
||||||
if let Ok(topology) = DiskTopology::probe(&mut self.file) {
|
if let Ok(topology) = DiskTopology::probe(&self.file) {
|
||||||
topology
|
topology
|
||||||
} else {
|
} else {
|
||||||
warn!("Unable to get device topology. Using default topology");
|
warn!("Unable to get device topology. Using default topology");
|
||||||
@@ -76,17 +77,17 @@ impl AsyncIo for RawFileAsync {
|
|||||||
fn read_vectored(
|
fn read_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: Vec<libc::iovec>,
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
let (submitter, mut sq, _) = self.io_uring.split();
|
let (submitter, mut sq, _) = self.io_uring.split();
|
||||||
|
|
||||||
// Safe because we know the file descriptor is valid and we
|
// SAFETY: we know the file descriptor is valid and we
|
||||||
// relied on vm-memory to provide the buffer address.
|
// relied on vm-memory to provide the buffer address.
|
||||||
let _ = unsafe {
|
let _ = unsafe {
|
||||||
sq.push(
|
sq.push(
|
||||||
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||||
.offset(offset)
|
.offset(offset.try_into().unwrap())
|
||||||
.build()
|
.build()
|
||||||
.flags(squeue::Flags::ASYNC)
|
.flags(squeue::Flags::ASYNC)
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
@@ -104,17 +105,17 @@ impl AsyncIo for RawFileAsync {
|
|||||||
fn write_vectored(
|
fn write_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: Vec<libc::iovec>,
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
let (submitter, mut sq, _) = self.io_uring.split();
|
let (submitter, mut sq, _) = self.io_uring.split();
|
||||||
|
|
||||||
// Safe because we know the file descriptor is valid and we
|
// SAFETY: we know the file descriptor is valid and we
|
||||||
// relied on vm-memory to provide the buffer address.
|
// relied on vm-memory to provide the buffer address.
|
||||||
let _ = unsafe {
|
let _ = unsafe {
|
||||||
sq.push(
|
sq.push(
|
||||||
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||||
.offset(offset)
|
.offset(offset.try_into().unwrap())
|
||||||
.build()
|
.build()
|
||||||
.flags(squeue::Flags::ASYNC)
|
.flags(squeue::Flags::ASYNC)
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
@@ -133,7 +134,7 @@ impl AsyncIo for RawFileAsync {
|
|||||||
if let Some(user_data) = user_data {
|
if let Some(user_data) = user_data {
|
||||||
let (submitter, mut sq, _) = self.io_uring.split();
|
let (submitter, mut sq, _) = self.io_uring.split();
|
||||||
|
|
||||||
// Safe because we know the file descriptor is valid.
|
// SAFETY: we know the file descriptor is valid.
|
||||||
let _ = unsafe {
|
let _ = unsafe {
|
||||||
sq.push(
|
sq.push(
|
||||||
&opcode::Fsync::new(types::Fd(self.fd))
|
&opcode::Fsync::new(types::Fd(self.fd))
|
||||||
@@ -148,20 +149,17 @@ impl AsyncIo for RawFileAsync {
|
|||||||
sq.sync();
|
sq.sync();
|
||||||
submitter.submit().map_err(AsyncIoError::Fsync)?;
|
submitter.submit().map_err(AsyncIoError::Fsync)?;
|
||||||
} else {
|
} else {
|
||||||
|
// SAFETY: FFI call with a valid fd
|
||||||
unsafe { libc::fsync(self.fd) };
|
unsafe { libc::fsync(self.fd) };
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||||
let mut completion_list = Vec::new();
|
self.io_uring
|
||||||
|
.completion()
|
||||||
let cq = self.io_uring.completion();
|
.next()
|
||||||
for cq_entry in cq {
|
.map(|entry| (entry.user_data(), entry.result()))
|
||||||
completion_list.push((cq_entry.user_data(), cq_entry.result()));
|
|
||||||
}
|
|
||||||
|
|
||||||
completion_list
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,10 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, DiskTopology,
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||||
};
|
};
|
||||||
|
use crate::DiskTopology;
|
||||||
|
use std::collections::VecDeque;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
@@ -32,7 +34,7 @@ impl DiskFile for RawFileDiskSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn topology(&mut self) -> DiskTopology {
|
fn topology(&mut self) -> DiskTopology {
|
||||||
if let Ok(topology) = DiskTopology::probe(&mut self.file) {
|
if let Ok(topology) = DiskTopology::probe(&self.file) {
|
||||||
topology
|
topology
|
||||||
} else {
|
} else {
|
||||||
warn!("Unable to get device topology. Using default topology");
|
warn!("Unable to get device topology. Using default topology");
|
||||||
@@ -44,7 +46,7 @@ impl DiskFile for RawFileDiskSync {
|
|||||||
pub struct RawFileSync {
|
pub struct RawFileSync {
|
||||||
fd: RawFd,
|
fd: RawFd,
|
||||||
eventfd: EventFd,
|
eventfd: EventFd,
|
||||||
completion_list: Vec<(u64, i32)>,
|
completion_list: VecDeque<(u64, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RawFileSync {
|
impl RawFileSync {
|
||||||
@@ -52,7 +54,7 @@ impl RawFileSync {
|
|||||||
RawFileSync {
|
RawFileSync {
|
||||||
fd,
|
fd,
|
||||||
eventfd: EventFd::new(libc::EFD_NONBLOCK).expect("Failed creating EventFd for RawFile"),
|
eventfd: EventFd::new(libc::EFD_NONBLOCK).expect("Failed creating EventFd for RawFile"),
|
||||||
completion_list: Vec::new(),
|
completion_list: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,13 +67,14 @@ impl AsyncIo for RawFileSync {
|
|||||||
fn read_vectored(
|
fn read_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: Vec<libc::iovec>,
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
|
// SAFETY: FFI call with valid arguments
|
||||||
let result = unsafe {
|
let result = unsafe {
|
||||||
libc::preadv(
|
libc::preadv(
|
||||||
self.fd as libc::c_int,
|
self.fd as libc::c_int,
|
||||||
iovecs.as_ptr() as *const libc::iovec,
|
iovecs.as_ptr(),
|
||||||
iovecs.len() as libc::c_int,
|
iovecs.len() as libc::c_int,
|
||||||
offset,
|
offset,
|
||||||
)
|
)
|
||||||
@@ -80,7 +83,7 @@ impl AsyncIo for RawFileSync {
|
|||||||
return Err(AsyncIoError::ReadVectored(std::io::Error::last_os_error()));
|
return Err(AsyncIoError::ReadVectored(std::io::Error::last_os_error()));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.completion_list.push((user_data, result as i32));
|
self.completion_list.push_back((user_data, result as i32));
|
||||||
self.eventfd.write(1).unwrap();
|
self.eventfd.write(1).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -89,13 +92,14 @@ impl AsyncIo for RawFileSync {
|
|||||||
fn write_vectored(
|
fn write_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: Vec<libc::iovec>,
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
|
// SAFETY: FFI call with valid arguments
|
||||||
let result = unsafe {
|
let result = unsafe {
|
||||||
libc::pwritev(
|
libc::pwritev(
|
||||||
self.fd as libc::c_int,
|
self.fd as libc::c_int,
|
||||||
iovecs.as_ptr() as *const libc::iovec,
|
iovecs.as_ptr(),
|
||||||
iovecs.len() as libc::c_int,
|
iovecs.len() as libc::c_int,
|
||||||
offset,
|
offset,
|
||||||
)
|
)
|
||||||
@@ -104,27 +108,28 @@ impl AsyncIo for RawFileSync {
|
|||||||
return Err(AsyncIoError::WriteVectored(std::io::Error::last_os_error()));
|
return Err(AsyncIoError::WriteVectored(std::io::Error::last_os_error()));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.completion_list.push((user_data, result as i32));
|
self.completion_list.push_back((user_data, result as i32));
|
||||||
self.eventfd.write(1).unwrap();
|
self.eventfd.write(1).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||||
|
// SAFETY: FFI call
|
||||||
let result = unsafe { libc::fsync(self.fd as libc::c_int) };
|
let result = unsafe { libc::fsync(self.fd as libc::c_int) };
|
||||||
if result < 0 {
|
if result < 0 {
|
||||||
return Err(AsyncIoError::Fsync(std::io::Error::last_os_error()));
|
return Err(AsyncIoError::Fsync(std::io::Error::last_os_error()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(user_data) = user_data {
|
if let Some(user_data) = user_data {
|
||||||
self.completion_list.push((user_data, result));
|
self.completion_list.push_back((user_data, result));
|
||||||
self.eventfd.write(1).unwrap();
|
self.eventfd.write(1).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||||
self.completion_list.drain(..).collect()
|
self.completion_list.pop_front()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,9 +2,10 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::{read_aligned_block_size, DiskTopology};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct VhdFooter {
|
pub struct VhdFooter {
|
||||||
@@ -27,37 +28,33 @@ pub struct VhdFooter {
|
|||||||
|
|
||||||
impl VhdFooter {
|
impl VhdFooter {
|
||||||
pub fn new(file: &mut File) -> std::io::Result<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
|
let blocksize = DiskTopology::probe(file)?.logical_block_size as usize;
|
||||||
// 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
|
// Place the cursor in the last block of the file
|
||||||
// where the footer starts.
|
file.seek(SeekFrom::End(0 - (blocksize as i64)))?;
|
||||||
file.seek(SeekFrom::End(-512))?;
|
// Read in the last block
|
||||||
|
let data = read_aligned_block_size(file)?;
|
||||||
|
|
||||||
// Fill in the VhdFooter structure
|
// We only care about the last sector
|
||||||
file.read_exact(&mut s.data)?;
|
let offset = blocksize - 512;
|
||||||
|
let sector = &data[offset..];
|
||||||
|
|
||||||
Ok(VhdFooter {
|
Ok(VhdFooter {
|
||||||
cookie: u64::from_be_bytes(s.data[0..8].try_into().unwrap()),
|
cookie: u64::from_be_bytes(sector[0..8].try_into().unwrap()),
|
||||||
features: u32::from_be_bytes(s.data[8..12].try_into().unwrap()),
|
features: u32::from_be_bytes(sector[8..12].try_into().unwrap()),
|
||||||
file_format_version: u32::from_be_bytes(s.data[12..16].try_into().unwrap()),
|
file_format_version: u32::from_be_bytes(sector[12..16].try_into().unwrap()),
|
||||||
data_offset: u64::from_be_bytes(s.data[16..24].try_into().unwrap()),
|
data_offset: u64::from_be_bytes(sector[16..24].try_into().unwrap()),
|
||||||
time_stamp: u32::from_be_bytes(s.data[24..28].try_into().unwrap()),
|
time_stamp: u32::from_be_bytes(sector[24..28].try_into().unwrap()),
|
||||||
creator_application: u32::from_be_bytes(s.data[28..32].try_into().unwrap()),
|
creator_application: u32::from_be_bytes(sector[28..32].try_into().unwrap()),
|
||||||
creator_version: u32::from_be_bytes(s.data[32..36].try_into().unwrap()),
|
creator_version: u32::from_be_bytes(sector[32..36].try_into().unwrap()),
|
||||||
creator_host_os: u32::from_be_bytes(s.data[36..40].try_into().unwrap()),
|
creator_host_os: u32::from_be_bytes(sector[36..40].try_into().unwrap()),
|
||||||
original_size: u64::from_be_bytes(s.data[40..48].try_into().unwrap()),
|
original_size: u64::from_be_bytes(sector[40..48].try_into().unwrap()),
|
||||||
current_size: u64::from_be_bytes(s.data[48..56].try_into().unwrap()),
|
current_size: u64::from_be_bytes(sector[48..56].try_into().unwrap()),
|
||||||
disk_geometry: u32::from_be_bytes(s.data[56..60].try_into().unwrap()),
|
disk_geometry: u32::from_be_bytes(sector[56..60].try_into().unwrap()),
|
||||||
disk_type: u32::from_be_bytes(s.data[60..64].try_into().unwrap()),
|
disk_type: u32::from_be_bytes(sector[60..64].try_into().unwrap()),
|
||||||
checksum: u32::from_be_bytes(s.data[64..68].try_into().unwrap()),
|
checksum: u32::from_be_bytes(sector[64..68].try_into().unwrap()),
|
||||||
unique_id: u128::from_be_bytes(s.data[68..84].try_into().unwrap()),
|
unique_id: u128::from_be_bytes(sector[68..84].try_into().unwrap()),
|
||||||
saved_state: u8::from_be_bytes(s.data[84..85].try_into().unwrap()),
|
saved_state: u8::from_be_bytes(sector[84..85].try_into().unwrap()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2,15 +2,31 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::vhdx_bat::{BatEntry, VhdxBatError};
|
use crate::vhdx::{
|
||||||
use crate::vhdx_header::{self, RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError};
|
vhdx_bat::{BatEntry, VhdxBatError},
|
||||||
use crate::vhdx_io::{self, VhdxIoError};
|
vhdx_header::{RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError},
|
||||||
use crate::vhdx_metadata::{DiskSpec, VhdxMetadataError};
|
vhdx_io::VhdxIoError,
|
||||||
|
vhdx_metadata::{DiskSpec, VhdxMetadataError},
|
||||||
|
};
|
||||||
|
use crate::BlockBackend;
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use remain::sorted;
|
use remain::sorted;
|
||||||
use std::collections::btree_map::BTreeMap;
|
use std::collections::btree_map::BTreeMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
macro_rules! div_round_up {
|
||||||
|
($n:expr,$d:expr) => {
|
||||||
|
($n + $d - 1) / $d
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod vhdx_bat;
|
||||||
|
mod vhdx_header;
|
||||||
|
mod vhdx_io;
|
||||||
|
mod vhdx_metadata;
|
||||||
|
|
||||||
#[sorted]
|
#[sorted]
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
@@ -105,8 +121,7 @@ impl Read for Vhdx {
|
|||||||
std::io::Error::new(
|
std::io::Error::new(
|
||||||
std::io::ErrorKind::Other,
|
std::io::ErrorKind::Other,
|
||||||
format!(
|
format!(
|
||||||
"Failed reading {} sectors from VHDx at index {}: {}",
|
"Failed reading {sector_count} sectors from VHDx at index {sector_index}: {e}"
|
||||||
sector_count, sector_index, e
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -130,7 +145,7 @@ impl Write for Vhdx {
|
|||||||
self.vhdx_header.update(&mut self.file).map_err(|e| {
|
self.vhdx_header.update(&mut self.file).map_err(|e| {
|
||||||
std::io::Error::new(
|
std::io::Error::new(
|
||||||
std::io::ErrorKind::Other,
|
std::io::ErrorKind::Other,
|
||||||
format!("Failed to update VHDx header: {}", e),
|
format!("Failed to update VHDx header: {e}"),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
@@ -148,8 +163,7 @@ impl Write for Vhdx {
|
|||||||
std::io::Error::new(
|
std::io::Error::new(
|
||||||
std::io::ErrorKind::Other,
|
std::io::ErrorKind::Other,
|
||||||
format!(
|
format!(
|
||||||
"Failed writing {} sectors on VHDx at index {}: {}",
|
"Failed writing {sector_count} sectors on VHDx at index {sector_index}: {e}"
|
||||||
sector_count, sector_index, e
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -195,6 +209,12 @@ impl Seek for Vhdx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BlockBackend for Vhdx {
|
||||||
|
fn size(&self) -> std::result::Result<u64, crate::Error> {
|
||||||
|
Ok(self.virtual_disk_size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Clone for Vhdx {
|
impl Clone for Vhdx {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Vhdx {
|
Vhdx {
|
||||||
@@ -210,3 +230,15 @@ impl Clone for Vhdx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn uuid_from_guid(buf: &[u8]) -> Uuid {
|
||||||
|
// The first 3 fields of UUID are stored in Big Endian format, and
|
||||||
|
// the last 8 bytes are stored as byte array. Therefore, we read the
|
||||||
|
// first 3 fields in Big Endian format instead of Little Endian.
|
||||||
|
Uuid::from_fields_le(
|
||||||
|
BigEndian::read_u32(&buf[0..4]),
|
||||||
|
BigEndian::read_u16(&buf[4..6]),
|
||||||
|
BigEndian::read_u16(&buf[6..8]),
|
||||||
|
buf[8..16].try_into().unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,8 +2,7 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::vhdx_header::RegionTableEntry;
|
use crate::vhdx::{vhdx_header::RegionTableEntry, vhdx_metadata::DiskSpec};
|
||||||
use crate::vhdx_metadata::DiskSpec;
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use remain::sorted;
|
use remain::sorted;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@@ -81,7 +81,7 @@ pub enum VhdxHeaderError {
|
|||||||
SeekRegionTableEntries(#[source] io::Error),
|
SeekRegionTableEntries(#[source] io::Error),
|
||||||
#[error("Failed to seek in region table header {0}")]
|
#[error("Failed to seek in region table header {0}")]
|
||||||
SeekRegionTableHeader(#[source] io::Error),
|
SeekRegionTableHeader(#[source] io::Error),
|
||||||
#[error("We do not recongize this entry")]
|
#[error("We do not recognize this entry")]
|
||||||
UnrecognizedRegionEntry,
|
UnrecognizedRegionEntry,
|
||||||
#[error("Failed to write header {0}")]
|
#[error("Failed to write header {0}")]
|
||||||
WriteHeader(#[source] io::Error),
|
WriteHeader(#[source] io::Error),
|
||||||
@@ -136,6 +136,7 @@ impl Header {
|
|||||||
f.read_exact(&mut buffer)
|
f.read_exact(&mut buffer)
|
||||||
.map_err(VhdxHeaderError::ReadHeader)?;
|
.map_err(VhdxHeaderError::ReadHeader)?;
|
||||||
|
|
||||||
|
// SAFETY: buffer is of correct size and has been successfully filled.
|
||||||
let header = unsafe { *(buffer.as_ptr() as *mut Header) };
|
let header = unsafe { *(buffer.as_ptr() as *mut Header) };
|
||||||
if header.signature != HEADER_SIGN {
|
if header.signature != HEADER_SIGN {
|
||||||
return Err(VhdxHeaderError::InvalidHeaderSign);
|
return Err(VhdxHeaderError::InvalidHeaderSign);
|
||||||
@@ -151,6 +152,7 @@ impl Header {
|
|||||||
|
|
||||||
/// Converts the header structure into a buffer
|
/// Converts the header structure into a buffer
|
||||||
fn get_header_as_buffer(&self, buffer: &mut [u8; HEADER_SIZE as usize]) {
|
fn get_header_as_buffer(&self, buffer: &mut [u8; HEADER_SIZE as usize]) {
|
||||||
|
// SAFETY: self is a valid header.
|
||||||
let reference = unsafe {
|
let reference = unsafe {
|
||||||
std::slice::from_raw_parts(self as *const Header as *const u8, HEADER_SIZE as usize)
|
std::slice::from_raw_parts(self as *const Header as *const u8, HEADER_SIZE as usize)
|
||||||
};
|
};
|
||||||
@@ -221,6 +223,7 @@ impl RegionTableHeader {
|
|||||||
f.read_exact(&mut buffer)
|
f.read_exact(&mut buffer)
|
||||||
.map_err(VhdxHeaderError::ReadRegionTableHeader)?;
|
.map_err(VhdxHeaderError::ReadRegionTableHeader)?;
|
||||||
|
|
||||||
|
// SAFETY: buffer is of correct size and has been successfully filled.
|
||||||
let region_table_header = unsafe { *(buffer.as_ptr() as *mut RegionTableHeader) };
|
let region_table_header = unsafe { *(buffer.as_ptr() as *mut RegionTableHeader) };
|
||||||
if region_table_header.signature != REGION_SIGN {
|
if region_table_header.signature != REGION_SIGN {
|
||||||
return Err(VhdxHeaderError::InvalidRegionSign);
|
return Err(VhdxHeaderError::InvalidRegionSign);
|
||||||
@@ -302,7 +305,7 @@ impl RegionInfo {
|
|||||||
|
|
||||||
if (entry.required & REGION_ENTRY_REQUIRED) == 1 {
|
if (entry.required & REGION_ENTRY_REQUIRED) == 1 {
|
||||||
// This implementation doesn't recognize this field.
|
// This implementation doesn't recognize this field.
|
||||||
// Therefore, accoding to the spec, we are throwing an error.
|
// Therefore, according to the spec, we are throwing an error.
|
||||||
return Err(VhdxHeaderError::UnrecognizedRegionEntry);
|
return Err(VhdxHeaderError::UnrecognizedRegionEntry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,9 +340,11 @@ pub struct RegionTableEntry {
|
|||||||
impl RegionTableEntry {
|
impl RegionTableEntry {
|
||||||
/// Reads one Region Entry from a Region Table index that starts from 0
|
/// Reads one Region Entry from a Region Table index that starts from 0
|
||||||
pub fn new(buffer: &[u8]) -> Result<RegionTableEntry> {
|
pub fn new(buffer: &[u8]) -> Result<RegionTableEntry> {
|
||||||
|
assert!(buffer.len() == std::mem::size_of::<RegionTableEntry>());
|
||||||
|
// SAFETY: the assertion above makes sure the buffer size is correct.
|
||||||
let mut region_table_entry = unsafe { *(buffer.as_ptr() as *mut RegionTableEntry) };
|
let mut region_table_entry = unsafe { *(buffer.as_ptr() as *mut RegionTableEntry) };
|
||||||
|
|
||||||
let uuid = crate::uuid_from_guid(buffer);
|
let uuid = crate::vhdx::uuid_from_guid(buffer);
|
||||||
region_table_entry.guid = uuid;
|
region_table_entry.guid = uuid;
|
||||||
|
|
||||||
Ok(region_table_entry)
|
Ok(region_table_entry)
|
||||||
@@ -439,7 +444,7 @@ impl VhdxHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the provided headers accoding to the spec
|
// Update the provided headers according to the spec
|
||||||
fn update_headers(
|
fn update_headers(
|
||||||
f: &mut File,
|
f: &mut File,
|
||||||
header_1: Result<Header>,
|
header_1: Result<Header>,
|
||||||
@@ -463,7 +468,7 @@ impl VhdxHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates the checksum of a buffer that itself containts its checksum
|
/// Calculates the checksum of a buffer that itself contains its checksum
|
||||||
/// Therefore, before calculating, the existing checksum is retrieved and the
|
/// Therefore, before calculating, the existing checksum is retrieved and the
|
||||||
/// corresponding field is made zero. After the calculation, the existing checksum
|
/// corresponding field is made zero. After the calculation, the existing checksum
|
||||||
/// is put back to the buffer.
|
/// is put back to the buffer.
|
||||||
@@ -2,8 +2,10 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::vhdx_bat::{self, BatEntry, VhdxBatError};
|
use crate::vhdx::{
|
||||||
use crate::vhdx_metadata::{self, DiskSpec};
|
vhdx_bat::{self, BatEntry, VhdxBatError},
|
||||||
|
vhdx_metadata::{self, DiskSpec},
|
||||||
|
};
|
||||||
use remain::sorted;
|
use remain::sorted;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::vhdx_header::RegionTableEntry;
|
use crate::vhdx::vhdx_header::RegionTableEntry;
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
use remain::sorted;
|
use remain::sorted;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@@ -96,7 +96,7 @@ pub struct DiskSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DiskSpec {
|
impl DiskSpec {
|
||||||
/// Parse all meatadata from the provided file and store info in DiskSpec
|
/// Parse all metadata from the provided file and store info in DiskSpec
|
||||||
/// structure.
|
/// structure.
|
||||||
pub fn new(f: &mut File, metadata_region: &RegionTableEntry) -> Result<DiskSpec> {
|
pub fn new(f: &mut File, metadata_region: &RegionTableEntry) -> Result<DiskSpec> {
|
||||||
let mut disk_spec = DiskSpec::default();
|
let mut disk_spec = DiskSpec::default();
|
||||||
@@ -133,7 +133,7 @@ impl DiskSpec {
|
|||||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||||
|
|
||||||
// MUST be at least 1 MiB and not greater than 256 MiB
|
// MUST be at least 1 MiB and not greater than 256 MiB
|
||||||
if disk_spec.block_size < BLOCK_SIZE_MIN && disk_spec.block_size > BLOCK_SIZE_MAX {
|
if disk_spec.block_size < BLOCK_SIZE_MIN || disk_spec.block_size > BLOCK_SIZE_MAX {
|
||||||
return Err(VhdxMetadataError::InvalidBlockSize);
|
return Err(VhdxMetadataError::InvalidBlockSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,11 +145,7 @@ impl DiskSpec {
|
|||||||
let bits = f
|
let bits = f
|
||||||
.read_u32::<LittleEndian>()
|
.read_u32::<LittleEndian>()
|
||||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||||
if bits & BLOCK_HAS_PARENT != 0 {
|
disk_spec.has_parent = bits & BLOCK_HAS_PARENT != 0;
|
||||||
disk_spec.has_parent = true;
|
|
||||||
} else {
|
|
||||||
disk_spec.has_parent = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata_presence |= METADATA_FILE_PARAMETER_PRESENT;
|
metadata_presence |= METADATA_FILE_PARAMETER_PRESENT;
|
||||||
} else if metadata_entry.item_id
|
} else if metadata_entry.item_id
|
||||||
@@ -270,6 +266,8 @@ struct MetadataTableHeader {
|
|||||||
|
|
||||||
impl MetadataTableHeader {
|
impl MetadataTableHeader {
|
||||||
pub fn new(buffer: &[u8]) -> Result<MetadataTableHeader> {
|
pub fn new(buffer: &[u8]) -> Result<MetadataTableHeader> {
|
||||||
|
assert!(buffer.len() == std::mem::size_of::<MetadataTableHeader>());
|
||||||
|
// SAFETY: the assertion above makes sure the buffer size is correct.
|
||||||
let metadata_table_header = unsafe { *(buffer.as_ptr() as *mut MetadataTableHeader) };
|
let metadata_table_header = unsafe { *(buffer.as_ptr() as *mut MetadataTableHeader) };
|
||||||
|
|
||||||
if metadata_table_header.signature != METADATA_SIGN {
|
if metadata_table_header.signature != METADATA_SIGN {
|
||||||
@@ -301,9 +299,11 @@ pub struct MetadataTableEntry {
|
|||||||
impl MetadataTableEntry {
|
impl MetadataTableEntry {
|
||||||
/// Parse one metadata entry from the buffer
|
/// Parse one metadata entry from the buffer
|
||||||
fn new(buffer: &[u8]) -> Result<MetadataTableEntry> {
|
fn new(buffer: &[u8]) -> Result<MetadataTableEntry> {
|
||||||
|
assert!(buffer.len() == std::mem::size_of::<MetadataTableEntry>());
|
||||||
|
// SAFETY: the assertion above makes sure the buffer size is correct.
|
||||||
let mut metadata_table_entry = unsafe { *(buffer.as_ptr() as *mut MetadataTableEntry) };
|
let mut metadata_table_entry = unsafe { *(buffer.as_ptr() as *mut MetadataTableEntry) };
|
||||||
|
|
||||||
let uuid = crate::uuid_from_guid(buffer);
|
let uuid = crate::vhdx::uuid_from_guid(buffer);
|
||||||
metadata_table_entry.item_id = uuid;
|
metadata_table_entry.item_id = uuid;
|
||||||
|
|
||||||
if metadata_table_entry.length > METADATA_LENGTH_MAX {
|
if metadata_table_entry.length > METADATA_LENGTH_MAX {
|
||||||
@@ -3,10 +3,11 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
||||||
|
use crate::vhdx::{Result as VhdxResult, Vhdx};
|
||||||
use crate::AsyncAdaptor;
|
use crate::AsyncAdaptor;
|
||||||
|
use std::collections::VecDeque;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
use vhdx::vhdx::{Result as VhdxResult, Vhdx};
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
pub struct VhdxDiskSync {
|
pub struct VhdxDiskSync {
|
||||||
@@ -37,7 +38,7 @@ impl DiskFile for VhdxDiskSync {
|
|||||||
pub struct VhdxSync {
|
pub struct VhdxSync {
|
||||||
vhdx_file: Arc<Mutex<Vhdx>>,
|
vhdx_file: Arc<Mutex<Vhdx>>,
|
||||||
eventfd: EventFd,
|
eventfd: EventFd,
|
||||||
completion_list: Vec<(u64, i32)>,
|
completion_list: VecDeque<(u64, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VhdxSync {
|
impl VhdxSync {
|
||||||
@@ -45,7 +46,7 @@ impl VhdxSync {
|
|||||||
Ok(VhdxSync {
|
Ok(VhdxSync {
|
||||||
vhdx_file,
|
vhdx_file,
|
||||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)?,
|
eventfd: EventFd::new(libc::EFD_NONBLOCK)?,
|
||||||
completion_list: Vec::new(),
|
completion_list: VecDeque::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +65,7 @@ impl AsyncIo for VhdxSync {
|
|||||||
fn read_vectored(
|
fn read_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: Vec<libc::iovec>,
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
self.vhdx_file.read_vectored_sync(
|
self.vhdx_file.read_vectored_sync(
|
||||||
@@ -79,7 +80,7 @@ impl AsyncIo for VhdxSync {
|
|||||||
fn write_vectored(
|
fn write_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: Vec<libc::iovec>,
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
self.vhdx_file.write_vectored_sync(
|
self.vhdx_file.write_vectored_sync(
|
||||||
@@ -96,7 +97,7 @@ impl AsyncIo for VhdxSync {
|
|||||||
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
|
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||||
self.completion_list.drain(..).collect()
|
self.completion_list.pop_front()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "block_util"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
io-uring = "0.5.8"
|
|
||||||
libc = "0.2.137"
|
|
||||||
log = "0.4.17"
|
|
||||||
qcow = { path = "../qcow" }
|
|
||||||
thiserror = "1.0.37"
|
|
||||||
versionize = "0.1.6"
|
|
||||||
versionize_derive = "0.1.4"
|
|
||||||
vhdx = { path = "../vhdx" }
|
|
||||||
virtio-bindings = { version = "0.1.0", features = ["virtio-v5_0_0"] }
|
|
||||||
virtio-queue = "0.6.1"
|
|
||||||
vm-memory = { version = "0.9.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
|
|
||||||
vm-virtio = { path = "../vm-virtio" }
|
|
||||||
vmm-sys-util = "0.10.0"
|
|
||||||
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use libc::{ioctl, S_IFBLK, S_IFMT};
|
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
use thiserror::Error;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
use vmm_sys_util::{ioctl_io_nr, ioctl_ioc_nr};
|
|
||||||
|
|
||||||
#[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),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DiskTopology {
|
|
||||||
pub logical_block_size: u64,
|
|
||||||
pub physical_block_size: u64,
|
|
||||||
pub minimum_io_size: u64,
|
|
||||||
pub optimal_io_size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DiskTopology {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
logical_block_size: 512,
|
|
||||||
physical_block_size: 512,
|
|
||||||
minimum_io_size: 512,
|
|
||||||
optimal_io_size: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ioctl_io_nr!(BLKSSZGET, 0x12, 104);
|
|
||||||
ioctl_io_nr!(BLKPBSZGET, 0x12, 123);
|
|
||||||
ioctl_io_nr!(BLKIOMIN, 0x12, 120);
|
|
||||||
ioctl_io_nr!(BLKIOOPT, 0x12, 121);
|
|
||||||
|
|
||||||
enum BlockSize {
|
|
||||||
LogicalBlock,
|
|
||||||
PhysicalBlock,
|
|
||||||
MinimumIo,
|
|
||||||
OptimalIo,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiskTopology {
|
|
||||||
fn is_block_device(f: &mut File) -> std::io::Result<bool> {
|
|
||||||
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
|
|
||||||
let ret = unsafe { libc::fstat(f.as_raw_fd(), stat.as_mut_ptr()) };
|
|
||||||
if ret != 0 {
|
|
||||||
return Err(std::io::Error::last_os_error());
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_block = unsafe { (*stat.as_ptr()).st_mode & S_IFMT == S_IFBLK };
|
|
||||||
Ok(is_block)
|
|
||||||
}
|
|
||||||
|
|
||||||
// libc::ioctl() takes different types on different architectures
|
|
||||||
#[allow(clippy::useless_conversion)]
|
|
||||||
fn query_block_size(f: &mut File, block_size_type: BlockSize) -> std::io::Result<u64> {
|
|
||||||
let mut block_size = 0;
|
|
||||||
let ret = unsafe {
|
|
||||||
ioctl(
|
|
||||||
f.as_raw_fd(),
|
|
||||||
match block_size_type {
|
|
||||||
BlockSize::LogicalBlock => BLKSSZGET(),
|
|
||||||
BlockSize::PhysicalBlock => BLKPBSZGET(),
|
|
||||||
BlockSize::MinimumIo => BLKIOMIN(),
|
|
||||||
BlockSize::OptimalIo => BLKIOOPT(),
|
|
||||||
}
|
|
||||||
.try_into()
|
|
||||||
.unwrap(),
|
|
||||||
&mut block_size,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
if ret != 0 {
|
|
||||||
return Err(std::io::Error::last_os_error());
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(block_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn probe(f: &mut File) -> std::io::Result<Self> {
|
|
||||||
if !Self::is_block_device(f)? {
|
|
||||||
return Ok(DiskTopology::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(DiskTopology {
|
|
||||||
logical_block_size: Self::query_block_size(f, BlockSize::LogicalBlock)?,
|
|
||||||
physical_block_size: Self::query_block_size(f, BlockSize::PhysicalBlock)?,
|
|
||||||
minimum_io_size: Self::query_block_size(f, BlockSize::MinimumIo)?,
|
|
||||||
optimal_io_size: Self::query_block_size(f, BlockSize::OptimalIo)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
|
|
||||||
|
|
||||||
pub trait DiskFile: Send {
|
|
||||||
fn size(&mut self) -> DiskFileResult<u64>;
|
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
|
|
||||||
fn topology(&mut self) -> DiskTopology {
|
|
||||||
DiskTopology::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 {
|
|
||||||
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)>;
|
|
||||||
}
|
|
||||||
9
build.rs
9
build.rs
@@ -3,13 +3,10 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
#[macro_use(crate_version)]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut version = "v".to_owned() + crate_version!();
|
let mut version = "v".to_owned() + env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
if let Ok(git_out) = Command::new("git").args(["describe", "--dirty"]).output() {
|
if let Ok(git_out) = Command::new("git").args(["describe", "--dirty"]).output() {
|
||||||
if git_out.status.success() {
|
if git_out.status.success() {
|
||||||
@@ -20,8 +17,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This println!() has a special behavior, as it will set the environment
|
// This println!() has a special behavior, as it will set the environment
|
||||||
// variable BUILT_VERSION, so that it can be reused from the binary.
|
// variable BUILD_VERSION, so that it can be reused from the binary.
|
||||||
// Particularly, this is used from src/main.rs to display the exact
|
// Particularly, this is used from src/main.rs to display the exact
|
||||||
// version.
|
// version.
|
||||||
println!("cargo:rustc-env=BUILT_VERSION={}", version);
|
println!("cargo:rustc-env=BUILD_VERSION={version}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,23 +5,25 @@ authors = ["The Chromium OS Authors"]
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
acpi_tables = { path = "../acpi_tables" }
|
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||||
anyhow = "1.0.66"
|
anyhow = "1.0.75"
|
||||||
arch = { path = "../arch" }
|
arch = { path = "../arch" }
|
||||||
bitflags = "1.3.2"
|
bitflags = "2.3.3"
|
||||||
byteorder = "1.4.3"
|
byteorder = "1.4.3"
|
||||||
|
event_monitor = { path = "../event_monitor" }
|
||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
libc = "0.2.137"
|
libc = "0.2.147"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
phf = { version = "0.11", features = ["macros"] }
|
pci = { path = "../pci" }
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.40"
|
||||||
tpm = { path = "../tpm" }
|
tpm = { path = "../tpm" }
|
||||||
versionize = "0.1.6"
|
versionize = "0.1.10"
|
||||||
versionize_derive = "0.1.4"
|
versionize_derive = "0.1.4"
|
||||||
|
vm-allocator = { path = "../vm-allocator" }
|
||||||
vm-device = { path = "../vm-device" }
|
vm-device = { path = "../vm-device" }
|
||||||
vm-memory = "0.9.0"
|
vm-memory = "0.12.2"
|
||||||
vm-migration = { path = "../vm-migration" }
|
vm-migration = { path = "../vm-migration" }
|
||||||
vmm-sys-util = "0.10.0"
|
vmm-sys-util = "0.11.0"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||||
arch = { path = "../arch" }
|
arch = { path = "../arch" }
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
use super::AcpiNotificationFlags;
|
use super::AcpiNotificationFlags;
|
||||||
use acpi_tables::{aml, aml::Aml};
|
use acpi_tables::{aml, Aml, AmlSink};
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
|
use std::thread;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
@@ -18,14 +20,20 @@ pub const GED_DEVICE_ACPI_SIZE: usize = 0x1;
|
|||||||
pub struct AcpiShutdownDevice {
|
pub struct AcpiShutdownDevice {
|
||||||
exit_evt: EventFd,
|
exit_evt: EventFd,
|
||||||
reset_evt: EventFd,
|
reset_evt: EventFd,
|
||||||
|
vcpus_kill_signalled: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AcpiShutdownDevice {
|
impl AcpiShutdownDevice {
|
||||||
/// Constructs a device that will signal the given event when the guest requests it.
|
/// Constructs a device that will signal the given event when the guest requests it.
|
||||||
pub fn new(exit_evt: EventFd, reset_evt: EventFd) -> AcpiShutdownDevice {
|
pub fn new(
|
||||||
|
exit_evt: EventFd,
|
||||||
|
reset_evt: EventFd,
|
||||||
|
vcpus_kill_signalled: Arc<AtomicBool>,
|
||||||
|
) -> AcpiShutdownDevice {
|
||||||
AcpiShutdownDevice {
|
AcpiShutdownDevice {
|
||||||
exit_evt,
|
exit_evt,
|
||||||
reset_evt,
|
reset_evt,
|
||||||
|
vcpus_kill_signalled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,6 +51,13 @@ impl BusDevice for AcpiShutdownDevice {
|
|||||||
if let Err(e) = self.reset_evt.write(1) {
|
if let Err(e) = self.reset_evt.write(1) {
|
||||||
error!("Error triggering ACPI reset event: {}", e);
|
error!("Error triggering ACPI reset event: {}", e);
|
||||||
}
|
}
|
||||||
|
// Spin until we are sure the reset_evt has been handled and that when
|
||||||
|
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
||||||
|
while !self.vcpus_kill_signalled.load(Ordering::SeqCst) {
|
||||||
|
// This is more effective than thread::yield_now() at
|
||||||
|
// avoiding a priority inversion with the VMM thread
|
||||||
|
thread::sleep(std::time::Duration::from_millis(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// The ACPI DSDT table specifies the S5 sleep state (shutdown) as value 5
|
// The ACPI DSDT table specifies the S5 sleep state (shutdown) as value 5
|
||||||
const S5_SLEEP_VALUE: u8 = 5;
|
const S5_SLEEP_VALUE: u8 = 5;
|
||||||
@@ -53,6 +68,13 @@ impl BusDevice for AcpiShutdownDevice {
|
|||||||
if let Err(e) = self.exit_evt.write(1) {
|
if let Err(e) = self.exit_evt.write(1) {
|
||||||
error!("Error triggering ACPI shutdown event: {}", e);
|
error!("Error triggering ACPI shutdown event: {}", e);
|
||||||
}
|
}
|
||||||
|
// Spin until we are sure the reset_evt has been handled and that when
|
||||||
|
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
||||||
|
while !self.vcpus_kill_signalled.load(Ordering::SeqCst) {
|
||||||
|
// This is more effective than thread::yield_now() at
|
||||||
|
// avoiding a priority inversion with the VMM thread
|
||||||
|
thread::sleep(std::time::Duration::from_millis(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -103,30 +125,32 @@ impl BusDevice for AcpiGedDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Aml for AcpiGedDevice {
|
impl Aml for AcpiGedDevice {
|
||||||
fn append_aml_bytes(&self, bytes: &mut Vec<u8>) {
|
fn to_aml_bytes(&self, sink: &mut dyn AmlSink) {
|
||||||
aml::Device::new(
|
aml::Device::new(
|
||||||
"_SB_.GEC_".into(),
|
"_SB_.GEC_".into(),
|
||||||
vec![
|
vec![
|
||||||
&aml::Name::new("_HID".into(), &aml::EisaName::new("PNP0A06")),
|
&aml::Name::new("_HID".into(), &aml::EISAName::new("PNP0A06")),
|
||||||
&aml::Name::new("_UID".into(), &"Generic Event Controller"),
|
&aml::Name::new("_UID".into(), &"Generic Event Controller"),
|
||||||
&aml::Name::new(
|
&aml::Name::new(
|
||||||
"_CRS".into(),
|
"_CRS".into(),
|
||||||
&aml::ResourceTemplate::new(vec![&aml::AddressSpace::new_memory(
|
&aml::ResourceTemplate::new(vec![&aml::AddressSpace::new_memory(
|
||||||
aml::AddressSpaceCachable::NotCacheable,
|
aml::AddressSpaceCacheable::NotCacheable,
|
||||||
true,
|
true,
|
||||||
self.address.0,
|
self.address.0,
|
||||||
self.address.0 + GED_DEVICE_ACPI_SIZE as u64 - 1,
|
self.address.0 + GED_DEVICE_ACPI_SIZE as u64 - 1,
|
||||||
|
None,
|
||||||
)]),
|
)]),
|
||||||
),
|
),
|
||||||
&aml::OpRegion::new(
|
&aml::OpRegion::new(
|
||||||
"GDST".into(),
|
"GDST".into(),
|
||||||
aml::OpRegionSpace::SystemMemory,
|
aml::OpRegionSpace::SystemMemory,
|
||||||
self.address.0 as usize,
|
&(self.address.0 as usize),
|
||||||
GED_DEVICE_ACPI_SIZE,
|
&GED_DEVICE_ACPI_SIZE,
|
||||||
),
|
),
|
||||||
&aml::Field::new(
|
&aml::Field::new(
|
||||||
"GDST".into(),
|
"GDST".into(),
|
||||||
aml::FieldAccessType::Byte,
|
aml::FieldAccessType::Byte,
|
||||||
|
aml::FieldLockRule::NoLock,
|
||||||
aml::FieldUpdateRule::WriteAsZeroes,
|
aml::FieldUpdateRule::WriteAsZeroes,
|
||||||
vec![aml::FieldEntry::Named(*b"GDAT", 8)],
|
vec![aml::FieldEntry::Named(*b"GDAT", 8)],
|
||||||
),
|
),
|
||||||
@@ -163,7 +187,7 @@ impl Aml for AcpiGedDevice {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.append_aml_bytes(bytes);
|
.to_aml_bytes(sink);
|
||||||
aml::Device::new(
|
aml::Device::new(
|
||||||
"_SB_.GED_".into(),
|
"_SB_.GED_".into(),
|
||||||
vec![
|
vec![
|
||||||
@@ -187,7 +211,7 @@ impl Aml for AcpiGedDevice {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.append_aml_bytes(bytes)
|
.to_aml_bytes(sink)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use anyhow::anyhow;
|
|||||||
use arch::layout;
|
use arch::layout;
|
||||||
use hypervisor::{
|
use hypervisor::{
|
||||||
arch::aarch64::gic::{Vgic, VgicConfig},
|
arch::aarch64::gic::{Vgic, VgicConfig},
|
||||||
CpuState,
|
CpuState, GicState,
|
||||||
};
|
};
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@@ -25,8 +25,9 @@ type Result<T> = result::Result<T, Error>;
|
|||||||
// Reserve 32 IRQs for legacy devices.
|
// Reserve 32 IRQs for legacy devices.
|
||||||
pub const IRQ_LEGACY_BASE: usize = layout::IRQ_BASE as usize;
|
pub const IRQ_LEGACY_BASE: usize = layout::IRQ_BASE as usize;
|
||||||
pub const IRQ_LEGACY_COUNT: usize = 32;
|
pub const IRQ_LEGACY_COUNT: usize = 32;
|
||||||
|
pub const GIC_SNAPSHOT_ID: &str = "gic-v3-its";
|
||||||
|
|
||||||
// Gic (Generic Interupt Controller) struct provides all the functionality of a
|
// Gic (Generic Interrupt Controller) struct provides all the functionality of a
|
||||||
// GIC device. It wraps a hypervisor-emulated GIC device (Vgic) provided by the
|
// GIC device. It wraps a hypervisor-emulated GIC device (Vgic) provided by the
|
||||||
// `hypervisor` crate.
|
// `hypervisor` crate.
|
||||||
// Gic struct also implements InterruptController to provide interrupt delivery
|
// Gic struct also implements InterruptController to provide interrupt delivery
|
||||||
@@ -39,8 +40,9 @@ pub struct Gic {
|
|||||||
|
|
||||||
impl Gic {
|
impl Gic {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
_vcpu_count: u8,
|
vcpu_count: u8,
|
||||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||||
|
vm: Arc<dyn hypervisor::Vm>,
|
||||||
) -> Result<Gic> {
|
) -> Result<Gic> {
|
||||||
let interrupt_source_group = interrupt_manager
|
let interrupt_source_group = interrupt_manager
|
||||||
.create_group(MsiIrqGroupConfig {
|
.create_group(MsiIrqGroupConfig {
|
||||||
@@ -49,45 +51,34 @@ impl Gic {
|
|||||||
})
|
})
|
||||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||||
|
|
||||||
Ok(Gic {
|
let vgic = vm
|
||||||
|
.create_vgic(Gic::create_default_config(vcpu_count as u64))
|
||||||
|
.map_err(Error::CreateGic)?;
|
||||||
|
|
||||||
|
let gic = Gic {
|
||||||
interrupt_source_group,
|
interrupt_source_group,
|
||||||
vgic: None,
|
vgic: Some(vgic),
|
||||||
})
|
};
|
||||||
|
gic.enable()?;
|
||||||
|
|
||||||
|
Ok(gic)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default config implied by arch::layout
|
pub fn restore_vgic(
|
||||||
pub fn create_default_config(vcpu_count: u64) -> VgicConfig {
|
|
||||||
let redists_size = layout::GIC_V3_REDIST_SIZE * vcpu_count;
|
|
||||||
let redists_addr = layout::GIC_V3_DIST_START.raw_value() - redists_size;
|
|
||||||
VgicConfig {
|
|
||||||
vcpu_count,
|
|
||||||
dist_addr: layout::GIC_V3_DIST_START.raw_value(),
|
|
||||||
dist_size: layout::GIC_V3_DIST_SIZE,
|
|
||||||
redists_addr,
|
|
||||||
redists_size,
|
|
||||||
msi_addr: redists_addr - layout::GIC_V3_ITS_SIZE,
|
|
||||||
msi_size: layout::GIC_V3_ITS_SIZE,
|
|
||||||
nr_irqs: layout::IRQ_NUM,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_vgic(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
vm: &Arc<dyn hypervisor::Vm>,
|
state: Option<GicState>,
|
||||||
config: VgicConfig,
|
saved_vcpu_states: &[CpuState],
|
||||||
) -> Result<Arc<Mutex<dyn Vgic>>> {
|
) -> Result<()> {
|
||||||
let vgic = vm.create_vgic(config).map_err(Error::CreateGic)?;
|
self.set_gicr_typers(saved_vcpu_states);
|
||||||
self.vgic = Some(vgic.clone());
|
self.vgic
|
||||||
Ok(vgic.clone())
|
.clone()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.set_state(&state.unwrap())
|
||||||
|
.map_err(Error::RestoreGic)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_gicr_typers(&mut self, vcpu_states: &[CpuState]) {
|
|
||||||
let vgic = self.vgic.as_ref().unwrap().clone();
|
|
||||||
vgic.lock().unwrap().set_gicr_typers(vcpu_states);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InterruptController for Gic {
|
|
||||||
fn enable(&self) -> Result<()> {
|
fn enable(&self) -> Result<()> {
|
||||||
// Set irqfd for legacy interrupts
|
// Set irqfd for legacy interrupts
|
||||||
self.interrupt_source_group
|
self.interrupt_source_group
|
||||||
@@ -107,12 +98,44 @@ impl InterruptController for Gic {
|
|||||||
i as InterruptIndex,
|
i as InterruptIndex,
|
||||||
InterruptSourceConfig::LegacyIrq(config),
|
InterruptSourceConfig::LegacyIrq(config),
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.map_err(Error::EnableInterrupt)?;
|
.map_err(Error::EnableInterrupt)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.interrupt_source_group
|
||||||
|
.set_gsi()
|
||||||
|
.map_err(Error::EnableInterrupt)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Default config implied by arch::layout
|
||||||
|
pub fn create_default_config(vcpu_count: u64) -> VgicConfig {
|
||||||
|
let redists_size = layout::GIC_V3_REDIST_SIZE * vcpu_count;
|
||||||
|
let redists_addr = layout::GIC_V3_DIST_START.raw_value() - redists_size;
|
||||||
|
VgicConfig {
|
||||||
|
vcpu_count,
|
||||||
|
dist_addr: layout::GIC_V3_DIST_START.raw_value(),
|
||||||
|
dist_size: layout::GIC_V3_DIST_SIZE,
|
||||||
|
redists_addr,
|
||||||
|
redists_size,
|
||||||
|
msi_addr: redists_addr - layout::GIC_V3_ITS_SIZE,
|
||||||
|
msi_size: layout::GIC_V3_ITS_SIZE,
|
||||||
|
nr_irqs: layout::IRQ_NUM,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_vgic(&mut self) -> Result<Arc<Mutex<dyn Vgic>>> {
|
||||||
|
Ok(self.vgic.clone().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_gicr_typers(&mut self, vcpu_states: &[CpuState]) {
|
||||||
|
let vgic = self.vgic.as_ref().unwrap().clone();
|
||||||
|
vgic.lock().unwrap().set_gicr_typers(vcpu_states);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterruptController for Gic {
|
||||||
// This should be called anytime an interrupt needs to be injected into the
|
// This should be called anytime an interrupt needs to be injected into the
|
||||||
// running guest.
|
// running guest.
|
||||||
fn service_irq(&mut self, irq: usize) -> Result<()> {
|
fn service_irq(&mut self, irq: usize) -> Result<()> {
|
||||||
@@ -128,27 +151,15 @@ impl InterruptController for Gic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const GIC_V3_ITS_SNAPSHOT_ID: &str = "gic-v3-its";
|
|
||||||
impl Snapshottable for Gic {
|
impl Snapshottable for Gic {
|
||||||
fn id(&self) -> String {
|
fn id(&self) -> String {
|
||||||
GIC_V3_ITS_SNAPSHOT_ID.to_string()
|
GIC_SNAPSHOT_ID.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
let vgic = self.vgic.as_ref().unwrap().clone();
|
let vgic = self.vgic.as_ref().unwrap().clone();
|
||||||
let state = vgic.lock().unwrap().state().unwrap();
|
let state = vgic.lock().unwrap().state().unwrap();
|
||||||
Snapshot::new_from_state(&self.id(), &state)
|
Snapshot::new_from_state(&state)
|
||||||
}
|
|
||||||
|
|
||||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
|
||||||
let vgic = self.vgic.as_ref().unwrap().clone();
|
|
||||||
vgic.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_state(&snapshot.to_state(&self.id())?)
|
|
||||||
.map_err(|e| {
|
|
||||||
MigratableError::Restore(anyhow!("Could not restore GICv3ITS state {:?}", e))
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,12 @@ pub enum Error {
|
|||||||
UpdateInterrupt(io::Error),
|
UpdateInterrupt(io::Error),
|
||||||
/// Failed enabling the interrupt.
|
/// Failed enabling the interrupt.
|
||||||
EnableInterrupt(io::Error),
|
EnableInterrupt(io::Error),
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
/// Failed creating GIC device.
|
/// Failed creating GIC device.
|
||||||
CreateGic(hypervisor::HypervisorVmError),
|
CreateGic(hypervisor::HypervisorVmError),
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
/// Failed restoring GIC device.
|
||||||
|
RestoreGic(hypervisor::arch::aarch64::gic::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
@@ -55,8 +59,6 @@ pub struct MsiMessage {
|
|||||||
// IOAPIC (X86) or GIC (Arm).
|
// IOAPIC (X86) or GIC (Arm).
|
||||||
pub trait InterruptController: Send {
|
pub trait InterruptController: Send {
|
||||||
fn service_irq(&mut self, irq: usize) -> Result<()>;
|
fn service_irq(&mut self, irq: usize) -> Result<()>;
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
fn enable(&self) -> Result<()>;
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
fn end_of_interrupt(&mut self, vec: u8);
|
fn end_of_interrupt(&mut self, vec: u8);
|
||||||
fn notifier(&self, irq: usize) -> Option<EventFd>;
|
fn notifier(&self, irq: usize) -> Option<EventFd>;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
|
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
|
||||||
|
|
||||||
use super::interrupt_controller::{Error, InterruptController};
|
use super::interrupt_controller::{Error, InterruptController};
|
||||||
use anyhow::anyhow;
|
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
@@ -194,6 +193,7 @@ impl Ioapic {
|
|||||||
id: String,
|
id: String,
|
||||||
apic_address: GuestAddress,
|
apic_address: GuestAddress,
|
||||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||||
|
state: Option<IoapicState>,
|
||||||
) -> Result<Ioapic> {
|
) -> Result<Ioapic> {
|
||||||
let interrupt_source_group = interrupt_manager
|
let interrupt_source_group = interrupt_manager
|
||||||
.create_group(MsiIrqGroupConfig {
|
.create_group(MsiIrqGroupConfig {
|
||||||
@@ -202,17 +202,52 @@ impl Ioapic {
|
|||||||
})
|
})
|
||||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||||
|
|
||||||
|
let (id_reg, reg_sel, reg_entries, used_entries, apic_address) = if let Some(state) = &state
|
||||||
|
{
|
||||||
|
(
|
||||||
|
state.id_reg,
|
||||||
|
state.reg_sel,
|
||||||
|
state.reg_entries,
|
||||||
|
state.used_entries,
|
||||||
|
GuestAddress(state.apic_address),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
[0x10000; NUM_IOAPIC_PINS],
|
||||||
|
[false; NUM_IOAPIC_PINS],
|
||||||
|
apic_address,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
// The IOAPIC is created with entries already masked. The guest will be
|
// The IOAPIC is created with entries already masked. The guest will be
|
||||||
// in charge of unmasking them if/when necessary.
|
// in charge of unmasking them if/when necessary.
|
||||||
Ok(Ioapic {
|
let ioapic = Ioapic {
|
||||||
id,
|
id,
|
||||||
id_reg: 0,
|
id_reg,
|
||||||
reg_sel: 0,
|
reg_sel,
|
||||||
reg_entries: [0x10000; NUM_IOAPIC_PINS],
|
reg_entries,
|
||||||
used_entries: [false; NUM_IOAPIC_PINS],
|
used_entries,
|
||||||
apic_address,
|
apic_address,
|
||||||
interrupt_source_group,
|
interrupt_source_group,
|
||||||
})
|
};
|
||||||
|
|
||||||
|
// When restoring the Ioapic, we must enable used entries.
|
||||||
|
if state.is_some() {
|
||||||
|
for (irq, entry) in ioapic.used_entries.iter().enumerate() {
|
||||||
|
if *entry {
|
||||||
|
ioapic.update_entry(irq, false)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ioapic
|
||||||
|
.interrupt_source_group
|
||||||
|
.set_gsi()
|
||||||
|
.map_err(Error::UpdateInterrupt)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ioapic)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ioapic_write(&mut self, val: u32) {
|
fn ioapic_write(&mut self, val: u32) {
|
||||||
@@ -248,7 +283,7 @@ impl Ioapic {
|
|||||||
}
|
}
|
||||||
// The entry must be updated through the interrupt source
|
// The entry must be updated through the interrupt source
|
||||||
// group.
|
// group.
|
||||||
if let Err(e) = self.update_entry(index) {
|
if let Err(e) = self.update_entry(index, true) {
|
||||||
error!("Failed updating IOAPIC entry: {:?}", e);
|
error!("Failed updating IOAPIC entry: {:?}", e);
|
||||||
}
|
}
|
||||||
// Store the information this IRQ is now being used.
|
// Store the information this IRQ is now being used.
|
||||||
@@ -299,22 +334,7 @@ impl Ioapic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_state(&mut self, state: &IoapicState) -> Result<()> {
|
fn update_entry(&self, irq: usize, set_gsi: bool) -> Result<()> {
|
||||||
self.id_reg = state.id_reg;
|
|
||||||
self.reg_sel = state.reg_sel;
|
|
||||||
self.reg_entries = state.reg_entries;
|
|
||||||
self.used_entries = state.used_entries;
|
|
||||||
self.apic_address = GuestAddress(state.apic_address);
|
|
||||||
for (irq, entry) in self.used_entries.iter().enumerate() {
|
|
||||||
if *entry {
|
|
||||||
self.update_entry(irq)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_entry(&self, irq: usize) -> Result<()> {
|
|
||||||
let entry = self.reg_entries[irq];
|
let entry = self.reg_entries[irq];
|
||||||
|
|
||||||
// Validate Destination Mode value, and retrieve Destination ID
|
// Validate Destination Mode value, and retrieve Destination ID
|
||||||
@@ -371,6 +391,7 @@ impl Ioapic {
|
|||||||
irq as InterruptIndex,
|
irq as InterruptIndex,
|
||||||
InterruptSourceConfig::MsiIrq(config),
|
InterruptSourceConfig::MsiIrq(config),
|
||||||
interrupt_mask(entry) == 1,
|
interrupt_mask(entry) == 1,
|
||||||
|
set_gsi,
|
||||||
)
|
)
|
||||||
.map_err(Error::UpdateInterrupt)?;
|
.map_err(Error::UpdateInterrupt)?;
|
||||||
|
|
||||||
@@ -423,18 +444,7 @@ impl Snapshottable for Ioapic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_versioned_state(&self.id, &self.state())
|
Snapshot::new_from_versioned_state(&self.state())
|
||||||
}
|
|
||||||
|
|
||||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
|
||||||
self.set_state(&snapshot.to_versioned_state(&self.id)?)
|
|
||||||
.map_err(|e| {
|
|
||||||
MigratableError::Restore(anyhow!(
|
|
||||||
"Could not restore state for {}: {:?}",
|
|
||||||
self.id,
|
|
||||||
e
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
|
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
|
use std::thread;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
@@ -23,13 +25,19 @@ pub struct Cmos {
|
|||||||
index: u8,
|
index: u8,
|
||||||
data: [u8; DATA_LEN],
|
data: [u8; DATA_LEN],
|
||||||
reset_evt: EventFd,
|
reset_evt: EventFd,
|
||||||
|
vcpus_kill_signalled: Option<Arc<AtomicBool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cmos {
|
impl Cmos {
|
||||||
/// Constructs a CMOS/RTC device with initial data.
|
/// Constructs a CMOS/RTC device with initial data.
|
||||||
/// `mem_below_4g` is the size of memory in bytes below the 32-bit gap.
|
/// `mem_below_4g` is the size of memory in bytes below the 32-bit gap.
|
||||||
/// `mem_above_4g` is the size of memory in bytes above the 32-bit gap.
|
/// `mem_above_4g` is the size of memory in bytes above the 32-bit gap.
|
||||||
pub fn new(mem_below_4g: u64, mem_above_4g: u64, reset_evt: EventFd) -> Cmos {
|
pub fn new(
|
||||||
|
mem_below_4g: u64,
|
||||||
|
mem_above_4g: u64,
|
||||||
|
reset_evt: EventFd,
|
||||||
|
vcpus_kill_signalled: Option<Arc<AtomicBool>>,
|
||||||
|
) -> Cmos {
|
||||||
let mut data = [0u8; DATA_LEN];
|
let mut data = [0u8; DATA_LEN];
|
||||||
|
|
||||||
// Extended memory from 16 MB to 4 GB in units of 64 KB
|
// Extended memory from 16 MB to 4 GB in units of 64 KB
|
||||||
@@ -50,6 +58,7 @@ impl Cmos {
|
|||||||
index: 0,
|
index: 0,
|
||||||
data,
|
data,
|
||||||
reset_evt,
|
reset_evt,
|
||||||
|
vcpus_kill_signalled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,6 +76,15 @@ impl BusDevice for Cmos {
|
|||||||
if self.index == 0x8f && data[0] == 0 {
|
if self.index == 0x8f && data[0] == 0 {
|
||||||
info!("CMOS reset");
|
info!("CMOS reset");
|
||||||
self.reset_evt.write(1).unwrap();
|
self.reset_evt.write(1).unwrap();
|
||||||
|
if let Some(vcpus_kill_signalled) = self.vcpus_kill_signalled.take() {
|
||||||
|
// Spin until we are sure the reset_evt has been handled and that when
|
||||||
|
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
||||||
|
while !vcpus_kill_signalled.load(Ordering::SeqCst) {
|
||||||
|
// This is more effective than thread::yield_now() at
|
||||||
|
// avoiding a priority inversion with the VMM thread
|
||||||
|
thread::sleep(std::time::Duration::from_millis(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.data[(self.index & INDEX_MASK) as usize] = data[0]
|
self.data[(self.index & INDEX_MASK) as usize] = data[0]
|
||||||
}
|
}
|
||||||
@@ -97,7 +115,7 @@ impl BusDevice for Cmos {
|
|||||||
let day;
|
let day;
|
||||||
let month;
|
let month;
|
||||||
let year;
|
let year;
|
||||||
// The clock_gettime and gmtime_r calls are safe as long as the structs they are
|
// SAFETY: The clock_gettime and gmtime_r calls are safe as long as the structs they are
|
||||||
// given are large enough, and neither of them fail. It is safe to zero initialize
|
// given are large enough, and neither of them fail. It is safe to zero initialize
|
||||||
// the tm and timespec struct because it contains only plain data.
|
// the tm and timespec struct because it contains only plain data.
|
||||||
let update_in_progress = unsafe {
|
let update_in_progress = unsafe {
|
||||||
|
|||||||
@@ -40,11 +40,11 @@ impl DebugIoPortRange {
|
|||||||
impl fmt::Display for DebugIoPortRange {
|
impl fmt::Display for DebugIoPortRange {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
DebugIoPortRange::Firmware => write!(f, "{}: Firmware", DEBUG_IOPORT_PREFIX),
|
DebugIoPortRange::Firmware => write!(f, "{DEBUG_IOPORT_PREFIX}: Firmware"),
|
||||||
DebugIoPortRange::Bootloader => write!(f, "{}: Bootloader", DEBUG_IOPORT_PREFIX),
|
DebugIoPortRange::Bootloader => write!(f, "{DEBUG_IOPORT_PREFIX}: Bootloader"),
|
||||||
DebugIoPortRange::Kernel => write!(f, "{}: Kernel", DEBUG_IOPORT_PREFIX),
|
DebugIoPortRange::Kernel => write!(f, "{DEBUG_IOPORT_PREFIX}: Kernel"),
|
||||||
DebugIoPortRange::Userspace => write!(f, "{}: Userspace", DEBUG_IOPORT_PREFIX),
|
DebugIoPortRange::Userspace => write!(f, "{DEBUG_IOPORT_PREFIX}: Userspace"),
|
||||||
DebugIoPortRange::Custom => write!(f, "{}: Custom", DEBUG_IOPORT_PREFIX),
|
DebugIoPortRange::Custom => write!(f, "{DEBUG_IOPORT_PREFIX}: Custom"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const GPIOIC: u64 = 0x41c; // Interrupt Clear Register
|
|||||||
const GPIOAFSEL: u64 = 0x420; // Mode Control Select Register
|
const GPIOAFSEL: u64 = 0x420; // Mode Control Select Register
|
||||||
// From 0x424 to 0xFDC => reserved space.
|
// From 0x424 to 0xFDC => reserved space.
|
||||||
// From 0xFE0 to 0xFFC => Peripheral and PrimeCell Identification Registers which are Read Only registers.
|
// 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.
|
// These 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.
|
// 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];
|
const GPIO_ID: [u8; 8] = [0x61, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
||||||
// ID Margins
|
// ID Margins
|
||||||
@@ -51,13 +51,13 @@ pub enum Error {
|
|||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {}", offset),
|
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {offset}"),
|
||||||
Error::GpioInterruptDisabled => write!(f, "GPIO interrupt disabled by guest driver.",),
|
Error::GpioInterruptDisabled => write!(f, "GPIO interrupt disabled by guest driver.",),
|
||||||
Error::GpioInterruptFailure(ref e) => {
|
Error::GpioInterruptFailure(ref e) => {
|
||||||
write!(f, "Could not trigger GPIO interrupt: {}.", e)
|
write!(f, "Could not trigger GPIO interrupt: {e}.")
|
||||||
}
|
}
|
||||||
Error::GpioTriggerKeyFailure(key) => {
|
Error::GpioTriggerKeyFailure(key) => {
|
||||||
write!(f, "Invalid GPIO Input key triggerd: {}.", key)
|
write!(f, "Invalid GPIO Input key triggered: {key}.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,18 +106,39 @@ impl VersionMapped for GpioState {}
|
|||||||
|
|
||||||
impl Gpio {
|
impl Gpio {
|
||||||
/// Constructs an PL061 GPIO device.
|
/// Constructs an PL061 GPIO device.
|
||||||
pub fn new(id: String, interrupt: Arc<dyn InterruptSourceGroup>) -> Self {
|
pub fn new(
|
||||||
|
id: String,
|
||||||
|
interrupt: Arc<dyn InterruptSourceGroup>,
|
||||||
|
state: Option<GpioState>,
|
||||||
|
) -> Self {
|
||||||
|
let (data, old_in_data, dir, isense, ibe, iev, im, istate, afsel) =
|
||||||
|
if let Some(state) = state {
|
||||||
|
(
|
||||||
|
state.data,
|
||||||
|
state.old_in_data,
|
||||||
|
state.dir,
|
||||||
|
state.isense,
|
||||||
|
state.ibe,
|
||||||
|
state.iev,
|
||||||
|
state.im,
|
||||||
|
state.istate,
|
||||||
|
state.afsel,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
data: 0,
|
data,
|
||||||
old_in_data: 0,
|
old_in_data,
|
||||||
dir: 0,
|
dir,
|
||||||
isense: 0,
|
isense,
|
||||||
ibe: 0,
|
ibe,
|
||||||
iev: 0,
|
iev,
|
||||||
im: 0,
|
im,
|
||||||
istate: 0,
|
istate,
|
||||||
afsel: 0,
|
afsel,
|
||||||
interrupt,
|
interrupt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,24 +157,12 @@ impl Gpio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
fn pl061_internal_update(&mut self) {
|
||||||
// FIXME:
|
// FIXME:
|
||||||
// Missing Output Interrupt Emulation.
|
// Missing Output Interrupt Emulation.
|
||||||
|
|
||||||
// Input Edging Interrupt Emulation.
|
// Input Edging Interrupt Emulation.
|
||||||
let changed = ((self.old_in_data ^ self.data) & !self.dir) as u32;
|
let changed = (self.old_in_data ^ self.data) & !self.dir;
|
||||||
if changed > 0 {
|
if changed > 0 {
|
||||||
self.old_in_data = self.data;
|
self.old_in_data = self.data;
|
||||||
for i in 0..N_GPIOS {
|
for i in 0..N_GPIOS {
|
||||||
@@ -319,12 +328,7 @@ impl Snapshottable for Gpio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_versioned_state(&self.id, &self.state())
|
Snapshot::new_from_versioned_state(&self.state())
|
||||||
}
|
|
||||||
|
|
||||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
|
||||||
self.set_state(&snapshot.to_versioned_state(&self.id)?);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,10 +361,15 @@ mod tests {
|
|||||||
_index: InterruptIndex,
|
_index: InterruptIndex,
|
||||||
_config: InterruptSourceConfig,
|
_config: InterruptSourceConfig,
|
||||||
_masked: bool,
|
_masked: bool,
|
||||||
|
_set_gsi: bool,
|
||||||
) -> result::Result<(), std::io::Error> {
|
) -> result::Result<(), std::io::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||||
Some(self.event_fd.try_clone().unwrap())
|
Some(self.event_fd.try_clone().unwrap())
|
||||||
}
|
}
|
||||||
@@ -378,6 +387,7 @@ mod tests {
|
|||||||
let mut gpio = Gpio::new(
|
let mut gpio = Gpio::new(
|
||||||
String::from(GPIO_NAME),
|
String::from(GPIO_NAME),
|
||||||
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
let mut data = [0; 4];
|
let mut data = [0; 4];
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,27 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc, Barrier,
|
||||||
|
};
|
||||||
|
use std::thread;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
/// A i8042 PS/2 controller that emulates just enough to shutdown the machine.
|
/// A i8042 PS/2 controller that emulates just enough to shutdown the machine.
|
||||||
pub struct I8042Device {
|
pub struct I8042Device {
|
||||||
reset_evt: EventFd,
|
reset_evt: EventFd,
|
||||||
|
vcpus_kill_signalled: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl I8042Device {
|
impl I8042Device {
|
||||||
/// Constructs a i8042 device that will signal the given event when the guest requests it.
|
/// Constructs a i8042 device that will signal the given event when the guest requests it.
|
||||||
pub fn new(reset_evt: EventFd) -> I8042Device {
|
pub fn new(reset_evt: EventFd, vcpus_kill_signalled: Arc<AtomicBool>) -> I8042Device {
|
||||||
I8042Device { reset_evt }
|
I8042Device {
|
||||||
|
reset_evt,
|
||||||
|
vcpus_kill_signalled,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +46,13 @@ impl BusDevice for I8042Device {
|
|||||||
if let Err(e) = self.reset_evt.write(1) {
|
if let Err(e) = self.reset_evt.write(1) {
|
||||||
error!("Error triggering i8042 reset event: {}", e);
|
error!("Error triggering i8042 reset event: {}", e);
|
||||||
}
|
}
|
||||||
|
// Spin until we are sure the reset_evt has been handled and that when
|
||||||
|
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
||||||
|
while !self.vcpus_kill_signalled.load(Ordering::SeqCst) {
|
||||||
|
// This is more effective than thread::yield_now() at
|
||||||
|
// avoiding a priority inversion with the VMM thread
|
||||||
|
thread::sleep(std::time::Duration::from_millis(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ pub enum Error {
|
|||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {}", offset),
|
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {offset}"),
|
||||||
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {}", e),
|
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,12 +61,10 @@ pub enum ClockType {
|
|||||||
/// Equivalent to `libc::CLOCK_MONOTONIC`.
|
/// Equivalent to `libc::CLOCK_MONOTONIC`.
|
||||||
Monotonic,
|
Monotonic,
|
||||||
/// Equivalent to `libc::CLOCK_REALTIME`.
|
/// Equivalent to `libc::CLOCK_REALTIME`.
|
||||||
#[allow(dead_code)]
|
|
||||||
Real,
|
Real,
|
||||||
/// Equivalent to `libc::CLOCK_PROCESS_CPUTIME_ID`.
|
/// Equivalent to `libc::CLOCK_PROCESS_CPUTIME_ID`.
|
||||||
ProcessCpu,
|
ProcessCpu,
|
||||||
/// Equivalent to `libc::CLOCK_THREAD_CPUTIME_ID`.
|
/// Equivalent to `libc::CLOCK_THREAD_CPUTIME_ID`.
|
||||||
#[allow(dead_code)]
|
|
||||||
ThreadCpu,
|
ThreadCpu,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +99,7 @@ pub struct LocalTime {
|
|||||||
|
|
||||||
impl LocalTime {
|
impl LocalTime {
|
||||||
/// Returns the [LocalTime](struct.LocalTime.html) structure for the calling moment.
|
/// Returns the [LocalTime](struct.LocalTime.html) structure for the calling moment.
|
||||||
#[allow(dead_code)]
|
#[cfg(test)]
|
||||||
pub fn now() -> LocalTime {
|
pub fn now() -> LocalTime {
|
||||||
let mut timespec = libc::timespec {
|
let mut timespec = libc::timespec {
|
||||||
tv_sec: 0,
|
tv_sec: 0,
|
||||||
@@ -121,7 +119,7 @@ impl LocalTime {
|
|||||||
tm_zone: std::ptr::null(),
|
tm_zone: std::ptr::null(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Safe because the parameters are valid.
|
// SAFETY: the parameters are valid.
|
||||||
unsafe {
|
unsafe {
|
||||||
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
|
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
|
||||||
libc::localtime_r(×pec.tv_sec, &mut tm);
|
libc::localtime_r(×pec.tv_sec, &mut tm);
|
||||||
@@ -173,22 +171,6 @@ impl Default for TimestampUs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a timestamp in nanoseconds from a monotonic clock.
|
|
||||||
///
|
|
||||||
/// Uses `_rdstc` on `x86_64` and [`get_time`](fn.get_time.html) on other architectures.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn timestamp_cycles() -> u64 {
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
// Safe because there's nothing that can go wrong with this call.
|
|
||||||
unsafe {
|
|
||||||
std::arch::x86_64::_rdtsc() as u64
|
|
||||||
}
|
|
||||||
#[cfg(not(target_arch = "x86_64"))]
|
|
||||||
{
|
|
||||||
get_time(ClockType::Monotonic)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a timestamp in nanoseconds based on the provided clock type.
|
/// Returns a timestamp in nanoseconds based on the provided clock type.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -199,7 +181,7 @@ pub fn get_time(clock_type: ClockType) -> u64 {
|
|||||||
tv_sec: 0,
|
tv_sec: 0,
|
||||||
tv_nsec: 0,
|
tv_nsec: 0,
|
||||||
};
|
};
|
||||||
// Safe because the parameters are valid.
|
// SAFETY: the parameters are valid.
|
||||||
unsafe { libc::clock_gettime(clock_type.into(), &mut time_struct) };
|
unsafe { libc::clock_gettime(clock_type.into(), &mut time_struct) };
|
||||||
seconds_to_nanoseconds(time_struct.tv_sec).unwrap() as u64 + (time_struct.tv_nsec as u64)
|
seconds_to_nanoseconds(time_struct.tv_sec).unwrap() as u64 + (time_struct.tv_nsec as u64)
|
||||||
}
|
}
|
||||||
@@ -431,10 +413,15 @@ mod tests {
|
|||||||
_index: InterruptIndex,
|
_index: InterruptIndex,
|
||||||
_config: InterruptSourceConfig,
|
_config: InterruptSourceConfig,
|
||||||
_masked: bool,
|
_masked: bool,
|
||||||
|
_set_gsi: bool,
|
||||||
) -> result::Result<(), std::io::Error> {
|
) -> result::Result<(), std::io::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||||
Some(self.event_fd.try_clone().unwrap())
|
Some(self.event_fd.try_clone().unwrap())
|
||||||
}
|
}
|
||||||
@@ -525,7 +512,6 @@ mod tests {
|
|||||||
($test_name: ident, $write_fn_name: ident, $read_fn_name: ident, $is_be: expr, $data_type: ty) => {
|
($test_name: ident, $write_fn_name: ident, $read_fn_name: ident, $is_be: expr, $data_type: ty) => {
|
||||||
#[test]
|
#[test]
|
||||||
fn $test_name() {
|
fn $test_name() {
|
||||||
#[allow(overflowing_literals)]
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
(
|
(
|
||||||
0x0123_4567_89AB_CDEF as u64,
|
0x0123_4567_89AB_CDEF as u64,
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ pub struct Serial {
|
|||||||
id: String,
|
id: String,
|
||||||
interrupt_enable: u8,
|
interrupt_enable: u8,
|
||||||
interrupt_identification: u8,
|
interrupt_identification: u8,
|
||||||
interrupt: Arc<dyn InterruptSourceGroup>,
|
|
||||||
line_control: u8,
|
line_control: u8,
|
||||||
line_status: u8,
|
line_status: u8,
|
||||||
modem_control: u8,
|
modem_control: u8,
|
||||||
@@ -71,6 +70,7 @@ pub struct Serial {
|
|||||||
scratch: u8,
|
scratch: u8,
|
||||||
baud_divisor: u16,
|
baud_divisor: u16,
|
||||||
in_buffer: VecDeque<u8>,
|
in_buffer: VecDeque<u8>,
|
||||||
|
interrupt: Arc<dyn InterruptSourceGroup>,
|
||||||
out: Option<Box<dyn io::Write + Send>>,
|
out: Option<Box<dyn io::Write + Send>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,19 +93,56 @@ impl Serial {
|
|||||||
id: String,
|
id: String,
|
||||||
interrupt: Arc<dyn InterruptSourceGroup>,
|
interrupt: Arc<dyn InterruptSourceGroup>,
|
||||||
out: Option<Box<dyn io::Write + Send>>,
|
out: Option<Box<dyn io::Write + Send>>,
|
||||||
|
state: Option<SerialState>,
|
||||||
) -> Serial {
|
) -> Serial {
|
||||||
|
let (
|
||||||
|
interrupt_enable,
|
||||||
|
interrupt_identification,
|
||||||
|
line_control,
|
||||||
|
line_status,
|
||||||
|
modem_control,
|
||||||
|
modem_status,
|
||||||
|
scratch,
|
||||||
|
baud_divisor,
|
||||||
|
in_buffer,
|
||||||
|
) = if let Some(state) = state {
|
||||||
|
(
|
||||||
|
state.interrupt_enable,
|
||||||
|
state.interrupt_identification,
|
||||||
|
state.line_control,
|
||||||
|
state.line_status,
|
||||||
|
state.modem_control,
|
||||||
|
state.modem_status,
|
||||||
|
state.scratch,
|
||||||
|
state.baud_divisor,
|
||||||
|
state.in_buffer.into(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
DEFAULT_INTERRUPT_IDENTIFICATION,
|
||||||
|
DEFAULT_LINE_CONTROL,
|
||||||
|
DEFAULT_LINE_STATUS,
|
||||||
|
DEFAULT_MODEM_CONTROL,
|
||||||
|
DEFAULT_MODEM_STATUS,
|
||||||
|
0,
|
||||||
|
DEFAULT_BAUD_DIVISOR,
|
||||||
|
VecDeque::new(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
Serial {
|
Serial {
|
||||||
id,
|
id,
|
||||||
interrupt_enable: 0,
|
interrupt_enable,
|
||||||
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
|
interrupt_identification,
|
||||||
|
line_control,
|
||||||
|
line_status,
|
||||||
|
modem_control,
|
||||||
|
modem_status,
|
||||||
|
scratch,
|
||||||
|
baud_divisor,
|
||||||
|
in_buffer,
|
||||||
interrupt,
|
interrupt,
|
||||||
line_control: DEFAULT_LINE_CONTROL,
|
|
||||||
line_status: DEFAULT_LINE_STATUS,
|
|
||||||
modem_control: DEFAULT_MODEM_CONTROL,
|
|
||||||
modem_status: DEFAULT_MODEM_STATUS,
|
|
||||||
scratch: 0,
|
|
||||||
baud_divisor: DEFAULT_BAUD_DIVISOR,
|
|
||||||
in_buffer: VecDeque::new(),
|
|
||||||
out,
|
out,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,13 +152,18 @@ impl Serial {
|
|||||||
id: String,
|
id: String,
|
||||||
interrupt: Arc<dyn InterruptSourceGroup>,
|
interrupt: Arc<dyn InterruptSourceGroup>,
|
||||||
out: Box<dyn io::Write + Send>,
|
out: Box<dyn io::Write + Send>,
|
||||||
|
state: Option<SerialState>,
|
||||||
) -> Serial {
|
) -> Serial {
|
||||||
Self::new(id, interrupt, Some(out))
|
Self::new(id, interrupt, Some(out), state)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a Serial port with no connected output.
|
/// Constructs a Serial port with no connected output.
|
||||||
pub fn new_sink(id: String, interrupt: Arc<dyn InterruptSourceGroup>) -> Serial {
|
pub fn new_sink(
|
||||||
Self::new(id, interrupt, None)
|
id: String,
|
||||||
|
interrupt: Arc<dyn InterruptSourceGroup>,
|
||||||
|
state: Option<SerialState>,
|
||||||
|
) -> Serial {
|
||||||
|
Self::new(id, interrupt, None, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_out(&mut self, out: Box<dyn io::Write + Send>) {
|
pub fn set_out(&mut self, out: Box<dyn io::Write + Send>) {
|
||||||
@@ -242,18 +284,6 @@ impl Serial {
|
|||||||
in_buffer: self.in_buffer.clone().into(),
|
in_buffer: self.in_buffer.clone().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_state(&mut self, state: &SerialState) {
|
|
||||||
self.interrupt_enable = state.interrupt_enable;
|
|
||||||
self.interrupt_identification = state.interrupt_identification;
|
|
||||||
self.line_control = state.line_control;
|
|
||||||
self.line_status = state.line_status;
|
|
||||||
self.modem_control = state.modem_control;
|
|
||||||
self.modem_status = state.modem_status;
|
|
||||||
self.scratch = state.scratch;
|
|
||||||
self.baud_divisor = state.baud_divisor;
|
|
||||||
self.in_buffer = state.in_buffer.clone().into();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BusDevice for Serial {
|
impl BusDevice for Serial {
|
||||||
@@ -304,12 +334,7 @@ impl Snapshottable for Serial {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_versioned_state(&self.id, &self.state())
|
Snapshot::new_from_versioned_state(&self.state())
|
||||||
}
|
|
||||||
|
|
||||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
|
||||||
self.set_state(&snapshot.to_versioned_state(&self.id)?);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,9 +365,13 @@ mod tests {
|
|||||||
_index: InterruptIndex,
|
_index: InterruptIndex,
|
||||||
_config: InterruptSourceConfig,
|
_config: InterruptSourceConfig,
|
||||||
_masked: bool,
|
_masked: bool,
|
||||||
|
_set_gsi: bool,
|
||||||
) -> result::Result<(), std::io::Error> {
|
) -> result::Result<(), std::io::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||||
Some(self.event_fd.try_clone().unwrap())
|
Some(self.event_fd.try_clone().unwrap())
|
||||||
}
|
}
|
||||||
@@ -384,6 +413,7 @@ mod tests {
|
|||||||
String::from(SERIAL_NAME),
|
String::from(SERIAL_NAME),
|
||||||
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
||||||
Box::new(serial_out.clone()),
|
Box::new(serial_out.clone()),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
serial.write(0, DATA as u64, &[b'x', b'y']);
|
serial.write(0, DATA as u64, &[b'x', b'y']);
|
||||||
@@ -404,6 +434,7 @@ mod tests {
|
|||||||
String::from(SERIAL_NAME),
|
String::from(SERIAL_NAME),
|
||||||
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
||||||
Box::new(serial_out),
|
Box::new(serial_out),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||||
@@ -440,6 +471,7 @@ mod tests {
|
|||||||
let mut serial = Serial::new_sink(
|
let mut serial = Serial::new_sink(
|
||||||
String::from(SERIAL_NAME),
|
String::from(SERIAL_NAME),
|
||||||
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||||
@@ -462,6 +494,7 @@ mod tests {
|
|||||||
let mut serial = Serial::new_sink(
|
let mut serial = Serial::new_sink(
|
||||||
String::from(SERIAL_NAME),
|
String::from(SERIAL_NAME),
|
||||||
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
serial.write(0, LCR as u64, &[LCR_DLAB_BIT]);
|
serial.write(0, LCR as u64, &[LCR_DLAB_BIT]);
|
||||||
@@ -483,6 +516,7 @@ mod tests {
|
|||||||
let mut serial = Serial::new_sink(
|
let mut serial = Serial::new_sink(
|
||||||
String::from(SERIAL_NAME),
|
String::from(SERIAL_NAME),
|
||||||
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT]);
|
serial.write(0, MCR as u64, &[MCR_LOOP_BIT]);
|
||||||
@@ -509,6 +543,7 @@ mod tests {
|
|||||||
let mut serial = Serial::new_sink(
|
let mut serial = Serial::new_sink(
|
||||||
String::from(SERIAL_NAME),
|
String::from(SERIAL_NAME),
|
||||||
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
serial.write(0, SCR as u64, &[0x12]);
|
serial.write(0, SCR as u64, &[0x12]);
|
||||||
|
|||||||
@@ -60,11 +60,11 @@ pub enum Error {
|
|||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Error::BadWriteOffset(offset) => write!(f, "pl011_write: Bad Write Offset: {}", offset),
|
Error::BadWriteOffset(offset) => write!(f, "pl011_write: Bad Write Offset: {offset}"),
|
||||||
Error::DmaNotImplemented => write!(f, "pl011: DMA not implemented."),
|
Error::DmaNotImplemented => write!(f, "pl011: DMA not implemented."),
|
||||||
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {}", e),
|
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {e}"),
|
||||||
Error::WriteAllFailure(e) => write!(f, "Failed to write: {}", e),
|
Error::WriteAllFailure(e) => write!(f, "Failed to write: {e}"),
|
||||||
Error::FlushFailure(e) => write!(f, "Failed to flush: {}", e),
|
Error::FlushFailure(e) => write!(f, "Failed to flush: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,24 +122,79 @@ impl Pl011 {
|
|||||||
irq: Arc<dyn InterruptSourceGroup>,
|
irq: Arc<dyn InterruptSourceGroup>,
|
||||||
out: Option<Box<dyn io::Write + Send>>,
|
out: Option<Box<dyn io::Write + Send>>,
|
||||||
timestamp: Instant,
|
timestamp: Instant,
|
||||||
|
state: Option<Pl011State>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let (
|
||||||
|
flags,
|
||||||
|
lcr,
|
||||||
|
rsr,
|
||||||
|
cr,
|
||||||
|
dmacr,
|
||||||
|
debug,
|
||||||
|
int_enabled,
|
||||||
|
int_level,
|
||||||
|
read_fifo,
|
||||||
|
ilpr,
|
||||||
|
ibrd,
|
||||||
|
fbrd,
|
||||||
|
ifl,
|
||||||
|
read_count,
|
||||||
|
read_trigger,
|
||||||
|
) = if let Some(state) = state {
|
||||||
|
(
|
||||||
|
state.flags,
|
||||||
|
state.lcr,
|
||||||
|
state.rsr,
|
||||||
|
state.cr,
|
||||||
|
state.dmacr,
|
||||||
|
state.debug,
|
||||||
|
state.int_enabled,
|
||||||
|
state.int_level,
|
||||||
|
state.read_fifo.into(),
|
||||||
|
state.ilpr,
|
||||||
|
state.ibrd,
|
||||||
|
state.fbrd,
|
||||||
|
state.ifl,
|
||||||
|
state.read_count,
|
||||||
|
state.read_trigger,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
0x90,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0x300,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
VecDeque::new(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0x12,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
flags: 0x90u32,
|
flags,
|
||||||
lcr: 0u32,
|
lcr,
|
||||||
rsr: 0u32,
|
rsr,
|
||||||
cr: 0x300u32,
|
cr,
|
||||||
dmacr: 0u32,
|
dmacr,
|
||||||
debug: 0u32,
|
debug,
|
||||||
int_enabled: 0u32,
|
int_enabled,
|
||||||
int_level: 0u32,
|
int_level,
|
||||||
read_fifo: VecDeque::new(),
|
read_fifo,
|
||||||
ilpr: 0u32,
|
ilpr,
|
||||||
ibrd: 0u32,
|
ibrd,
|
||||||
fbrd: 0u32,
|
fbrd,
|
||||||
ifl: 0x12u32,
|
ifl,
|
||||||
read_count: 0u32,
|
read_count,
|
||||||
read_trigger: 1u32,
|
read_trigger,
|
||||||
irq,
|
irq,
|
||||||
out,
|
out,
|
||||||
timestamp,
|
timestamp,
|
||||||
@@ -170,24 +225,6 @@ impl Pl011 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.debug = state.debug;
|
|
||||||
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
|
/// 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<()> {
|
pub fn queue_input_bytes(&mut self, c: &[u8]) -> vmm_sys_util::errno::Result<()> {
|
||||||
self.read_fifo.extend(c);
|
self.read_fifo.extend(c);
|
||||||
@@ -417,12 +454,7 @@ impl Snapshottable for Pl011 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_versioned_state(&self.id, &self.state())
|
Snapshot::new_from_versioned_state(&self.state())
|
||||||
}
|
|
||||||
|
|
||||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
|
||||||
self.set_state(&snapshot.to_versioned_state(&self.id)?);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,9 +485,13 @@ mod tests {
|
|||||||
_index: InterruptIndex,
|
_index: InterruptIndex,
|
||||||
_config: InterruptSourceConfig,
|
_config: InterruptSourceConfig,
|
||||||
_masked: bool,
|
_masked: bool,
|
||||||
|
_set_gsi: bool,
|
||||||
) -> result::Result<(), std::io::Error> {
|
) -> result::Result<(), std::io::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||||
Some(self.event_fd.try_clone().unwrap())
|
Some(self.event_fd.try_clone().unwrap())
|
||||||
}
|
}
|
||||||
@@ -498,12 +534,13 @@ mod tests {
|
|||||||
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
||||||
Some(Box::new(pl011_out.clone())),
|
Some(Box::new(pl011_out.clone())),
|
||||||
Instant::now(),
|
Instant::now(),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
pl011.write(0, UARTDR as u64, &[b'x', b'y']);
|
pl011.write(0, UARTDR, &[b'x', b'y']);
|
||||||
pl011.write(0, UARTDR as u64, &[b'a']);
|
pl011.write(0, UARTDR, &[b'a']);
|
||||||
pl011.write(0, UARTDR as u64, &[b'b']);
|
pl011.write(0, UARTDR, &[b'b']);
|
||||||
pl011.write(0, UARTDR as u64, &[b'c']);
|
pl011.write(0, UARTDR, &[b'c']);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pl011_out.buf.lock().unwrap().as_slice(),
|
pl011_out.buf.lock().unwrap().as_slice(),
|
||||||
&[b'x', b'a', b'b', b'c']
|
&[b'x', b'a', b'b', b'c']
|
||||||
@@ -519,6 +556,7 @@ mod tests {
|
|||||||
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
||||||
Some(Box::new(pl011_out)),
|
Some(Box::new(pl011_out)),
|
||||||
Instant::now(),
|
Instant::now(),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||||
@@ -529,11 +567,11 @@ mod tests {
|
|||||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||||
|
|
||||||
let mut data = [0u8];
|
let mut data = [0u8];
|
||||||
pl011.read(0, UARTDR as u64, &mut data);
|
pl011.read(0, UARTDR, &mut data);
|
||||||
assert_eq!(data[0], b'a');
|
assert_eq!(data[0], b'a');
|
||||||
pl011.read(0, UARTDR as u64, &mut data);
|
pl011.read(0, UARTDR, &mut data);
|
||||||
assert_eq!(data[0], b'b');
|
assert_eq!(data[0], b'b');
|
||||||
pl011.read(0, UARTDR as u64, &mut data);
|
pl011.read(0, UARTDR, &mut data);
|
||||||
assert_eq!(data[0], b'c');
|
assert_eq!(data[0], b'c');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
extern crate event_monitor;
|
||||||
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
pub mod acpi;
|
pub mod acpi;
|
||||||
@@ -19,9 +21,11 @@ pub mod interrupt_controller;
|
|||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub mod ioapic;
|
pub mod ioapic;
|
||||||
pub mod legacy;
|
pub mod legacy;
|
||||||
|
pub mod pvpanic;
|
||||||
pub mod tpm;
|
pub mod tpm;
|
||||||
|
|
||||||
pub use self::acpi::{AcpiGedDevice, AcpiPmTimerDevice, AcpiShutdownDevice};
|
pub use self::acpi::{AcpiGedDevice, AcpiPmTimerDevice, AcpiShutdownDevice};
|
||||||
|
pub use self::pvpanic::{PvPanicDevice, PVPANIC_DEVICE_MMIO_SIZE};
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub struct AcpiNotificationFlags: u8 {
|
pub struct AcpiNotificationFlags: u8 {
|
||||||
@@ -33,11 +37,9 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_macros)]
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
macro_rules! generate_read_fn {
|
macro_rules! generate_read_fn {
|
||||||
($fn_name: ident, $data_type: ty, $byte_type: ty, $type_size: expr, $endian_type: ident) => {
|
($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 {
|
pub fn $fn_name(input: &[$byte_type]) -> $data_type {
|
||||||
assert!($type_size == std::mem::size_of::<$data_type>());
|
assert!($type_size == std::mem::size_of::<$data_type>());
|
||||||
let mut array = [0u8; $type_size];
|
let mut array = [0u8; $type_size];
|
||||||
@@ -49,11 +51,9 @@ macro_rules! generate_read_fn {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_macros)]
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
macro_rules! generate_write_fn {
|
macro_rules! generate_write_fn {
|
||||||
($fn_name: ident, $data_type: ty, $byte_type: ty, $endian_type: ident) => {
|
($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) {
|
pub fn $fn_name(buf: &mut [$byte_type], n: $data_type) {
|
||||||
for (byte, read) in buf
|
for (byte, read) in buf
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
|||||||
271
devices/src/pvpanic.rs
Normal file
271
devices/src/pvpanic.rs
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
// Copyright © 2023 Tencent Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use pci::{
|
||||||
|
BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType,
|
||||||
|
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
|
||||||
|
PCI_CONFIGURATION_ID,
|
||||||
|
};
|
||||||
|
use std::any::Any;
|
||||||
|
use std::result;
|
||||||
|
use std::sync::{Arc, Barrier, Mutex};
|
||||||
|
use thiserror::Error;
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
|
use versionize_derive::Versionize;
|
||||||
|
use vm_allocator::{AddressAllocator, SystemAllocator};
|
||||||
|
use vm_device::{BusDevice, Resource};
|
||||||
|
use vm_memory::{Address, GuestAddress};
|
||||||
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PVPANIC_VENDOR_ID: u16 = 0x1b36;
|
||||||
|
const PVPANIC_DEVICE_ID: u16 = 0x0011;
|
||||||
|
|
||||||
|
pub const PVPANIC_DEVICE_MMIO_SIZE: u64 = 0x2;
|
||||||
|
|
||||||
|
const PVPANIC_PANICKED: u8 = 1 << 0;
|
||||||
|
const PVPANIC_CRASH_LOADED: u8 = 1 << 1;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum PvPanicError {
|
||||||
|
#[error("Failed creating PvPanicDevice: {0}")]
|
||||||
|
CreatePvPanicDevice(#[source] anyhow::Error),
|
||||||
|
#[error("Failed to retrieve PciConfigurationState: {0}")]
|
||||||
|
RetrievePciConfigurationState(#[source] anyhow::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
enum PvPanicSubclass {
|
||||||
|
Other = 0x80,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PciSubclass for PvPanicSubclass {
|
||||||
|
fn get_register_value(&self) -> u8 {
|
||||||
|
*self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A device for handling guest panic event
|
||||||
|
pub struct PvPanicDevice {
|
||||||
|
id: String,
|
||||||
|
events: u8,
|
||||||
|
|
||||||
|
// PCI configuration registers.
|
||||||
|
configuration: PciConfiguration,
|
||||||
|
bar_regions: Vec<PciBarConfiguration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Versionize)]
|
||||||
|
pub struct PvPanicDeviceState {
|
||||||
|
events: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VersionMapped for PvPanicDeviceState {}
|
||||||
|
|
||||||
|
impl PvPanicDevice {
|
||||||
|
pub fn new(id: String, snapshot: Option<Snapshot>) -> Result<Self, PvPanicError> {
|
||||||
|
let pci_configuration_state =
|
||||||
|
vm_migration::versioned_state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID)
|
||||||
|
.map_err(|e| {
|
||||||
|
PvPanicError::RetrievePciConfigurationState(anyhow!(
|
||||||
|
"Failed to get PciConfigurationState from Snapshot: {}",
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut configuration = PciConfiguration::new(
|
||||||
|
PVPANIC_VENDOR_ID,
|
||||||
|
PVPANIC_DEVICE_ID,
|
||||||
|
0x1, // modern pci devices
|
||||||
|
PciClassCode::BaseSystemPeripheral,
|
||||||
|
&PvPanicSubclass::Other,
|
||||||
|
None,
|
||||||
|
PciHeaderType::Device,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
pci_configuration_state,
|
||||||
|
);
|
||||||
|
|
||||||
|
let command: [u8; 2] = [0x03, 0x01];
|
||||||
|
configuration.write_config_register(1, 0, &command);
|
||||||
|
|
||||||
|
let state: Option<PvPanicDeviceState> = snapshot
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.to_versioned_state())
|
||||||
|
.transpose()
|
||||||
|
.map_err(|e| {
|
||||||
|
PvPanicError::CreatePvPanicDevice(anyhow!(
|
||||||
|
"Failed to get PvPanicDeviceState from Snapshot: {}",
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
let events = if let Some(state) = state {
|
||||||
|
state.events
|
||||||
|
} else {
|
||||||
|
PVPANIC_PANICKED | PVPANIC_CRASH_LOADED
|
||||||
|
};
|
||||||
|
|
||||||
|
let pvpanic_device = PvPanicDevice {
|
||||||
|
id,
|
||||||
|
events,
|
||||||
|
configuration,
|
||||||
|
bar_regions: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(pvpanic_device)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event_to_string(&self, event: u8) -> String {
|
||||||
|
if event == PVPANIC_PANICKED {
|
||||||
|
"panic".to_string()
|
||||||
|
} else if event == PVPANIC_CRASH_LOADED {
|
||||||
|
"crash_loaded".to_string()
|
||||||
|
} else {
|
||||||
|
"unknown_event".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> PvPanicDeviceState {
|
||||||
|
PvPanicDeviceState {
|
||||||
|
events: self.events,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config_bar_addr(&self) -> u64 {
|
||||||
|
self.configuration.get_bar_addr(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BusDevice for PvPanicDevice {
|
||||||
|
fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) {
|
||||||
|
self.read_bar(base, offset, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||||
|
let event = self.event_to_string(data[0]);
|
||||||
|
info!("pvpanic got guest event {}", event);
|
||||||
|
event!("guest", "panic", "event", &event);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PciDevice for PvPanicDevice {
|
||||||
|
fn write_config_register(
|
||||||
|
&mut self,
|
||||||
|
reg_idx: usize,
|
||||||
|
offset: u64,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Option<Arc<Barrier>> {
|
||||||
|
self.configuration
|
||||||
|
.write_config_register(reg_idx, offset, data);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
||||||
|
self.configuration.read_reg(reg_idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_bar_reprogramming(
|
||||||
|
&mut self,
|
||||||
|
reg_idx: usize,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Option<BarReprogrammingParams> {
|
||||||
|
self.configuration.detect_bar_reprogramming(reg_idx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocate_bars(
|
||||||
|
&mut self,
|
||||||
|
allocator: &Arc<Mutex<SystemAllocator>>,
|
||||||
|
_mmio_allocator: &mut AddressAllocator,
|
||||||
|
resources: Option<Vec<Resource>>,
|
||||||
|
) -> std::result::Result<Vec<PciBarConfiguration>, PciDeviceError> {
|
||||||
|
let mut bars = Vec::new();
|
||||||
|
let region_type = PciBarRegionType::Memory32BitRegion;
|
||||||
|
let bar_id = 0;
|
||||||
|
let region_size = PVPANIC_DEVICE_MMIO_SIZE;
|
||||||
|
let restoring = resources.is_some();
|
||||||
|
let bar_addr = allocator
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.allocate_mmio_hole_addresses(None, region_size, None)
|
||||||
|
.ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
|
||||||
|
|
||||||
|
let bar = PciBarConfiguration::default()
|
||||||
|
.set_index(bar_id as usize)
|
||||||
|
.set_address(bar_addr.raw_value())
|
||||||
|
.set_size(region_size)
|
||||||
|
.set_region_type(region_type)
|
||||||
|
.set_prefetchable(PciBarPrefetchable::NotPrefetchable);
|
||||||
|
|
||||||
|
debug!("pvpanic bar address 0x{:x}", bar_addr.0);
|
||||||
|
if !restoring {
|
||||||
|
self.configuration
|
||||||
|
.add_pci_bar(&bar)
|
||||||
|
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
bars.push(bar);
|
||||||
|
self.bar_regions = bars.clone();
|
||||||
|
|
||||||
|
Ok(bars)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free_bars(
|
||||||
|
&mut self,
|
||||||
|
allocator: &mut SystemAllocator,
|
||||||
|
_mmio_allocator: &mut AddressAllocator,
|
||||||
|
) -> std::result::Result<(), PciDeviceError> {
|
||||||
|
for bar in self.bar_regions.drain(..) {
|
||||||
|
allocator.free_mmio_hole_addresses(GuestAddress(bar.addr()), bar.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), std::io::Error> {
|
||||||
|
for bar in self.bar_regions.iter_mut() {
|
||||||
|
if bar.addr() == old_base {
|
||||||
|
*bar = bar.set_address(new_base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_bar(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||||
|
data[0] = self.events;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> Option<String> {
|
||||||
|
Some(self.id.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pausable for PvPanicDevice {}
|
||||||
|
|
||||||
|
impl Snapshottable for PvPanicDevice {
|
||||||
|
fn id(&self) -> String {
|
||||||
|
self.id.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
|
let mut snapshot = Snapshot::new_from_versioned_state(&self.state())?;
|
||||||
|
|
||||||
|
// Snapshot PciConfiguration
|
||||||
|
snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
|
||||||
|
|
||||||
|
Ok(snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transportable for PvPanicDevice {}
|
||||||
|
impl Migratable for PvPanicDevice {}
|
||||||
@@ -8,13 +8,11 @@ use anyhow::anyhow;
|
|||||||
use arch::aarch64::layout::{TPM_SIZE, TPM_START};
|
use arch::aarch64::layout::{TPM_SIZE, TPM_START};
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
use arch::x86_64::layout::{TPM_SIZE, TPM_START};
|
use arch::x86_64::layout::{TPM_SIZE, TPM_START};
|
||||||
use phf::phf_map;
|
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tpm::emulator::{BackendCmd, Emulator};
|
use tpm::emulator::{BackendCmd, Emulator};
|
||||||
use tpm::TPM_CRB_BUFFER_MAX;
|
use tpm::TPM_CRB_BUFFER_MAX;
|
||||||
use tpm::TPM_SUCCESS;
|
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
@@ -23,62 +21,135 @@ pub enum Error {
|
|||||||
CheckCaps(#[source] anyhow::Error),
|
CheckCaps(#[source] anyhow::Error),
|
||||||
#[error("Failed to initialize tpm: {0}")]
|
#[error("Failed to initialize tpm: {0}")]
|
||||||
Init(#[source] anyhow::Error),
|
Init(#[source] anyhow::Error),
|
||||||
#[error("Failed to deliver tpm Command: {0}")]
|
|
||||||
DeliverRequest(#[source] anyhow::Error),
|
|
||||||
}
|
}
|
||||||
type Result<T> = anyhow::Result<T, Error>;
|
type Result<T> = anyhow::Result<T, Error>;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
enum LocStateFields {
|
||||||
|
TpmEstablished,
|
||||||
|
LocAssigned,
|
||||||
|
ActiveLocality,
|
||||||
|
Reserved,
|
||||||
|
TpmRegValidSts,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LocStsFields {
|
||||||
|
Granted,
|
||||||
|
BeenSeized,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
enum IntfIdFields {
|
||||||
|
InterfaceType,
|
||||||
|
InterfaceVersion,
|
||||||
|
CapLocality,
|
||||||
|
CapCRBIdleBypass,
|
||||||
|
Reserved1,
|
||||||
|
CapDataXferSizeSupport,
|
||||||
|
CapFIFO,
|
||||||
|
CapCRB,
|
||||||
|
CapIFRes,
|
||||||
|
InterfaceSelector,
|
||||||
|
IntfSelLock,
|
||||||
|
Reserved2,
|
||||||
|
Rid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
enum IntfId2Fields {
|
||||||
|
Vid,
|
||||||
|
Did,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CtrlStsFields {
|
||||||
|
TpmSts,
|
||||||
|
TpmIdle,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CrbRegister {
|
||||||
|
LocState(LocStateFields),
|
||||||
|
LocSts(LocStsFields),
|
||||||
|
IntfId(IntfIdFields),
|
||||||
|
IntfId2(IntfId2Fields),
|
||||||
|
CtrlSts(CtrlStsFields),
|
||||||
|
}
|
||||||
|
|
||||||
/* crb 32-bit registers */
|
/* crb 32-bit registers */
|
||||||
const CRB_LOC_STATE: u32 = 0x0;
|
const CRB_LOC_STATE: u32 = 0x0;
|
||||||
//Register Fields
|
//Register Fields
|
||||||
// Field => (start, length)
|
// Field => (base, offset, length)
|
||||||
// start: lowest bit in the bit field numbered from 0
|
// base: starting position of the register
|
||||||
|
// offset: lowest bit in the bit field numbered from 0
|
||||||
// length: length of the bit field
|
// length: length of the bit field
|
||||||
const CRB_LOC_STATE_FIELDS: phf::Map<&str, [u32; 2]> = phf_map! {
|
const fn get_crb_loc_state_field(f: LocStateFields) -> (u32, u32, u32) {
|
||||||
"tpmEstablished" => [0, 1],
|
let (offset, len) = match f {
|
||||||
"locAssigned" => [1,1],
|
LocStateFields::TpmEstablished => (0, 1),
|
||||||
"activeLocality"=> [2, 3],
|
LocStateFields::LocAssigned => (1, 1),
|
||||||
"reserved" => [5, 2],
|
LocStateFields::ActiveLocality => (2, 3),
|
||||||
"tpmRegValidSts" => [7, 1]
|
LocStateFields::Reserved => (5, 2),
|
||||||
};
|
LocStateFields::TpmRegValidSts => (7, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
(CRB_LOC_STATE, offset, len)
|
||||||
|
}
|
||||||
|
|
||||||
const CRB_LOC_CTRL: u32 = 0x08;
|
const CRB_LOC_CTRL: u32 = 0x08;
|
||||||
const CRB_LOC_CTRL_REQUEST_ACCESS: u32 = 1 << 0;
|
const CRB_LOC_CTRL_REQUEST_ACCESS: u32 = 1 << 0;
|
||||||
const CRB_LOC_CTRL_RELINQUISH: u32 = 1 << 1;
|
const CRB_LOC_CTRL_RELINQUISH: u32 = 1 << 1;
|
||||||
const CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT: u32 = 1 << 3;
|
const CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT: u32 = 1 << 3;
|
||||||
const CRB_LOC_STS: u32 = 0x0C;
|
const CRB_LOC_STS: u32 = 0x0C;
|
||||||
const CRB_LOC_STS_FIELDS: phf::Map<&str, [u32; 2]> = phf_map! {
|
const fn get_crb_loc_sts_field(f: LocStsFields) -> (u32, u32, u32) {
|
||||||
"Granted" => [0, 1],
|
let (offset, len) = match f {
|
||||||
"beenSeized" => [1,1]
|
LocStsFields::Granted => (0, 1),
|
||||||
};
|
LocStsFields::BeenSeized => (1, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
(CRB_LOC_STS, offset, len)
|
||||||
|
}
|
||||||
|
|
||||||
const CRB_INTF_ID: u32 = 0x30;
|
const CRB_INTF_ID: u32 = 0x30;
|
||||||
const CRB_INTF_ID_FIELDS: phf::Map<&str, [u32; 2]> = phf_map! {
|
const fn get_crb_intf_id_field(f: IntfIdFields) -> (u32, u32, u32) {
|
||||||
"InterfaceType" => [0, 4],
|
let (offset, len) = match f {
|
||||||
"InterfaceVersion" => [4, 4],
|
IntfIdFields::InterfaceType => (0, 4),
|
||||||
"CapLocality" => [8, 1],
|
IntfIdFields::InterfaceVersion => (4, 4),
|
||||||
"CapCRBIdleBypass" => [9, 1],
|
IntfIdFields::CapLocality => (8, 1),
|
||||||
"Reserved1" => [10, 1],
|
IntfIdFields::CapCRBIdleBypass => (9, 1),
|
||||||
"CapDataXferSizeSupport" => [11, 2],
|
IntfIdFields::Reserved1 => (10, 1),
|
||||||
"CapFIFO" => [13, 1],
|
IntfIdFields::CapDataXferSizeSupport => (11, 2),
|
||||||
"CapCRB" => [14, 1],
|
IntfIdFields::CapFIFO => (13, 1),
|
||||||
"CapIFRes" => [15, 2],
|
IntfIdFields::CapCRB => (14, 1),
|
||||||
"InterfaceSelector" => [17, 2],
|
IntfIdFields::CapIFRes => (15, 2),
|
||||||
"IntfSelLock" => [19, 1],
|
IntfIdFields::InterfaceSelector => (17, 2),
|
||||||
"Reserved2" => [20, 4],
|
IntfIdFields::IntfSelLock => (19, 1),
|
||||||
"RID" => [24, 8]
|
IntfIdFields::Reserved2 => (20, 4),
|
||||||
};
|
IntfIdFields::Rid => (24, 8),
|
||||||
|
};
|
||||||
|
|
||||||
|
(CRB_INTF_ID, offset, len)
|
||||||
|
}
|
||||||
|
|
||||||
const CRB_INTF_ID2: u32 = 0x34;
|
const CRB_INTF_ID2: u32 = 0x34;
|
||||||
const CRB_INTF_ID2_FIELDS: phf::Map<&str, [u32; 2]> = phf_map! {
|
const fn get_crb_intf_id2_field(f: IntfId2Fields) -> (u32, u32, u32) {
|
||||||
"VID" => [0, 16],
|
let (offset, len) = match f {
|
||||||
"DID" => [16, 16]
|
IntfId2Fields::Vid => (0, 16),
|
||||||
};
|
IntfId2Fields::Did => (16, 16),
|
||||||
|
};
|
||||||
|
|
||||||
|
(CRB_INTF_ID2, offset, len)
|
||||||
|
}
|
||||||
|
|
||||||
const CRB_CTRL_REQ: u32 = 0x40;
|
const CRB_CTRL_REQ: u32 = 0x40;
|
||||||
const CRB_CTRL_REQ_CMD_READY: u32 = 1 << 0;
|
const CRB_CTRL_REQ_CMD_READY: u32 = 1 << 0;
|
||||||
const CRB_CTRL_REQ_GO_IDLE: u32 = 1 << 1;
|
const CRB_CTRL_REQ_GO_IDLE: u32 = 1 << 1;
|
||||||
const CRB_CTRL_STS: u32 = 0x44;
|
const CRB_CTRL_STS: u32 = 0x44;
|
||||||
const CRB_CTRL_STS_FIELDS: phf::Map<&str, [u32; 2]> = phf_map! {
|
const fn get_crb_ctrl_sts_field(f: CtrlStsFields) -> (u32, u32, u32) {
|
||||||
"tpmSts" => [0, 1],
|
let (offset, len) = match f {
|
||||||
"tpmIdle" => [1, 1]
|
CtrlStsFields::TpmSts => (0, 1),
|
||||||
};
|
CtrlStsFields::TpmIdle => (1, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
(CRB_CTRL_STS, offset, len)
|
||||||
|
}
|
||||||
const CRB_CTRL_CANCEL: u32 = 0x48;
|
const CRB_CTRL_CANCEL: u32 = 0x48;
|
||||||
const CRB_CANCEL_INVOKE: u32 = 1 << 0;
|
const CRB_CANCEL_INVOKE: u32 = 1 << 0;
|
||||||
const CRB_CTRL_START: u32 = 0x4C;
|
const CRB_CTRL_START: u32 = 0x4C;
|
||||||
@@ -94,7 +165,7 @@ const TPM_CRB_NO_LOCALITY: u32 = 0xff;
|
|||||||
const TPM_CRB_ADDR_BASE: u32 = TPM_START.0 as u32;
|
const TPM_CRB_ADDR_BASE: u32 = TPM_START.0 as u32;
|
||||||
const TPM_CRB_ADDR_SIZE: usize = TPM_SIZE as usize;
|
const TPM_CRB_ADDR_SIZE: usize = TPM_SIZE as usize;
|
||||||
|
|
||||||
const TPM_CRB_R_MAX: u32 = CRB_DATA_BUFFER;
|
const TPM_CRB_R_MAX: usize = CRB_DATA_BUFFER as usize;
|
||||||
|
|
||||||
// CRB Protocol details
|
// CRB Protocol details
|
||||||
const CRB_INTF_TYPE_CRB_ACTIVE: u32 = 0b1;
|
const CRB_INTF_TYPE_CRB_ACTIVE: u32 = 0b1;
|
||||||
@@ -109,50 +180,29 @@ const PCI_VENDOR_ID_IBM: u32 = 0x1014;
|
|||||||
const CRB_CTRL_CMD_SIZE_REG: u32 = 0x58;
|
const CRB_CTRL_CMD_SIZE_REG: u32 = 0x58;
|
||||||
const CRB_CTRL_CMD_SIZE: usize = TPM_CRB_ADDR_SIZE - CRB_DATA_BUFFER as usize;
|
const CRB_CTRL_CMD_SIZE: usize = TPM_CRB_ADDR_SIZE - CRB_DATA_BUFFER as usize;
|
||||||
|
|
||||||
fn get_fields_map(reg: u32) -> phf::Map<&'static str, [u32; 2]> {
|
// Returns (register base, offset, len)
|
||||||
|
const fn get_field(reg: CrbRegister) -> (u32, u32, u32) {
|
||||||
match reg {
|
match reg {
|
||||||
CRB_LOC_STATE => CRB_LOC_STATE_FIELDS,
|
CrbRegister::LocState(f) => get_crb_loc_state_field(f),
|
||||||
CRB_LOC_STS => CRB_LOC_STS_FIELDS,
|
CrbRegister::LocSts(f) => get_crb_loc_sts_field(f),
|
||||||
CRB_INTF_ID => CRB_INTF_ID_FIELDS,
|
CrbRegister::IntfId(f) => get_crb_intf_id_field(f),
|
||||||
CRB_INTF_ID2 => CRB_INTF_ID2_FIELDS,
|
CrbRegister::IntfId2(f) => get_crb_intf_id2_field(f),
|
||||||
CRB_CTRL_STS => CRB_CTRL_STS_FIELDS,
|
CrbRegister::CtrlSts(f) => get_crb_ctrl_sts_field(f),
|
||||||
_ => {
|
|
||||||
panic!(
|
|
||||||
"Fields in '{:?}' register were accessed which are Invalid",
|
|
||||||
reg
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a particular field in a Register
|
// Set a particular field in a Register
|
||||||
fn set_reg_field(regs: &mut [u32; TPM_CRB_R_MAX as usize], reg: u32, field: &str, value: u32) {
|
fn set_reg_field(regs: &mut [u32; TPM_CRB_R_MAX], reg: CrbRegister, value: u32) {
|
||||||
let reg_fields = get_fields_map(reg);
|
let (base, offset, len) = get_field(reg);
|
||||||
if reg_fields.contains_key(field) {
|
let mask = (!(0_u32) >> (32 - len)) << offset;
|
||||||
let start = reg_fields.get(field).unwrap()[0];
|
regs[base as usize] = (regs[base as usize] & !mask) | ((value << offset) & mask);
|
||||||
let len = reg_fields.get(field).unwrap()[1];
|
|
||||||
let mask = (!(0_u32) >> (32 - len)) << start;
|
|
||||||
regs[reg as usize] = (regs[reg as usize] & !mask) | ((value << start) & mask);
|
|
||||||
} else {
|
|
||||||
error!(
|
|
||||||
"Failed to tpm Register. {:?} is not a valid field in Reg {:#X}",
|
|
||||||
field, reg
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value of a particular field in a Register
|
// Get the value of a particular field in a Register
|
||||||
fn get_reg_field(regs: &[u32; TPM_CRB_R_MAX as usize], reg: u32, field: &str) -> u32 {
|
const fn get_reg_field(regs: &[u32; TPM_CRB_R_MAX], reg: CrbRegister) -> u32 {
|
||||||
let reg_fields = get_fields_map(reg);
|
let (base, offset, len) = get_field(reg);
|
||||||
if reg_fields.contains_key(field) {
|
let mask = (!(0_u32) >> (32 - len)) << offset;
|
||||||
let start = reg_fields.get(field).unwrap()[0];
|
(regs[base as usize] & mask) >> offset
|
||||||
let len = reg_fields.get(field).unwrap()[1];
|
|
||||||
let mask = (!(0_u32) >> (32 - len)) << start;
|
|
||||||
(regs[reg as usize] & mask) >> start
|
|
||||||
} else {
|
|
||||||
// TODO: Sensible return value if fields do not exist
|
|
||||||
0x0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn locality_from_addr(addr: u32) -> u8 {
|
fn locality_from_addr(addr: u32) -> u8 {
|
||||||
@@ -161,8 +211,7 @@ fn locality_from_addr(addr: u32) -> u8 {
|
|||||||
|
|
||||||
pub struct Tpm {
|
pub struct Tpm {
|
||||||
emulator: Emulator,
|
emulator: Emulator,
|
||||||
cmd: Option<BackendCmd>,
|
regs: [u32; TPM_CRB_R_MAX],
|
||||||
regs: [u32; TPM_CRB_R_MAX as usize],
|
|
||||||
backend_buff_size: usize,
|
backend_buff_size: usize,
|
||||||
data_buff: [u8; TPM_CRB_BUFFER_MAX],
|
data_buff: [u8; TPM_CRB_BUFFER_MAX],
|
||||||
data_buff_len: usize,
|
data_buff_len: usize,
|
||||||
@@ -174,8 +223,7 @@ impl Tpm {
|
|||||||
.map_err(|e| Error::Init(anyhow!("Failed while initializing tpm Emulator: {:?}", e)))?;
|
.map_err(|e| Error::Init(anyhow!("Failed while initializing tpm Emulator: {:?}", e)))?;
|
||||||
let mut tpm = Tpm {
|
let mut tpm = Tpm {
|
||||||
emulator,
|
emulator,
|
||||||
cmd: None,
|
regs: [0; TPM_CRB_R_MAX],
|
||||||
regs: [0; TPM_CRB_R_MAX as usize],
|
|
||||||
backend_buff_size: TPM_CRB_BUFFER_MAX,
|
backend_buff_size: TPM_CRB_BUFFER_MAX,
|
||||||
data_buff: [0; TPM_CRB_BUFFER_MAX],
|
data_buff: [0; TPM_CRB_BUFFER_MAX],
|
||||||
data_buff_len: 0,
|
data_buff_len: 0,
|
||||||
@@ -185,74 +233,93 @@ impl Tpm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_active_locality(&mut self) -> u32 {
|
fn get_active_locality(&mut self) -> u32 {
|
||||||
if get_reg_field(&self.regs, CRB_LOC_STATE, "locAssigned") == 0 {
|
if get_reg_field(
|
||||||
|
&self.regs,
|
||||||
|
CrbRegister::LocState(LocStateFields::LocAssigned),
|
||||||
|
) == 0
|
||||||
|
{
|
||||||
return TPM_CRB_NO_LOCALITY;
|
return TPM_CRB_NO_LOCALITY;
|
||||||
}
|
}
|
||||||
get_reg_field(&self.regs, CRB_LOC_STATE, "activeLocality")
|
get_reg_field(
|
||||||
|
&self.regs,
|
||||||
|
CrbRegister::LocState(LocStateFields::ActiveLocality),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_completed(&mut self, result: isize) {
|
fn request_completed(&mut self, success: bool) {
|
||||||
self.regs[CRB_CTRL_START as usize] = !CRB_START_INVOKE;
|
self.regs[CRB_CTRL_START as usize] = !CRB_START_INVOKE;
|
||||||
if result != 0 {
|
if !success {
|
||||||
set_reg_field(&mut self.regs, CRB_CTRL_STS, "tpmSts", 1);
|
set_reg_field(
|
||||||
|
&mut self.regs,
|
||||||
|
CrbRegister::CtrlSts(CtrlStsFields::TpmSts),
|
||||||
|
1,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self) -> Result<()> {
|
fn reset(&mut self) -> Result<()> {
|
||||||
let cur_buff_size = self.emulator.get_buffer_size().unwrap();
|
let cur_buff_size = self.emulator.get_buffer_size();
|
||||||
self.regs = [0; TPM_CRB_R_MAX as usize];
|
self.regs = [0; TPM_CRB_R_MAX];
|
||||||
set_reg_field(&mut self.regs, CRB_LOC_STATE, "tpmRegValidSts", 1);
|
|
||||||
set_reg_field(&mut self.regs, CRB_CTRL_STS, "tpmIdle", 1);
|
|
||||||
set_reg_field(
|
set_reg_field(
|
||||||
&mut self.regs,
|
&mut self.regs,
|
||||||
CRB_INTF_ID,
|
CrbRegister::LocState(LocStateFields::TpmRegValidSts),
|
||||||
"InterfaceType",
|
1,
|
||||||
|
);
|
||||||
|
set_reg_field(
|
||||||
|
&mut self.regs,
|
||||||
|
CrbRegister::CtrlSts(CtrlStsFields::TpmIdle),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
set_reg_field(
|
||||||
|
&mut self.regs,
|
||||||
|
CrbRegister::IntfId(IntfIdFields::InterfaceType),
|
||||||
CRB_INTF_TYPE_CRB_ACTIVE,
|
CRB_INTF_TYPE_CRB_ACTIVE,
|
||||||
);
|
);
|
||||||
set_reg_field(
|
set_reg_field(
|
||||||
&mut self.regs,
|
&mut self.regs,
|
||||||
CRB_INTF_ID,
|
CrbRegister::IntfId(IntfIdFields::InterfaceVersion),
|
||||||
"InterfaceVersion",
|
|
||||||
CRB_INTF_VERSION_CRB,
|
CRB_INTF_VERSION_CRB,
|
||||||
);
|
);
|
||||||
set_reg_field(
|
set_reg_field(
|
||||||
&mut self.regs,
|
&mut self.regs,
|
||||||
CRB_INTF_ID,
|
CrbRegister::IntfId(IntfIdFields::CapLocality),
|
||||||
"CapLocality",
|
|
||||||
CRB_INTF_CAP_LOCALITY_0_ONLY,
|
CRB_INTF_CAP_LOCALITY_0_ONLY,
|
||||||
);
|
);
|
||||||
set_reg_field(
|
set_reg_field(
|
||||||
&mut self.regs,
|
&mut self.regs,
|
||||||
CRB_INTF_ID,
|
CrbRegister::IntfId(IntfIdFields::CapCRBIdleBypass),
|
||||||
"CapCRBIdleBypass",
|
|
||||||
CRB_INTF_CAP_IDLE_FAST,
|
CRB_INTF_CAP_IDLE_FAST,
|
||||||
);
|
);
|
||||||
set_reg_field(
|
set_reg_field(
|
||||||
&mut self.regs,
|
&mut self.regs,
|
||||||
CRB_INTF_ID,
|
CrbRegister::IntfId(IntfIdFields::CapDataXferSizeSupport),
|
||||||
"CapDataXferSizeSupport",
|
|
||||||
CRB_INTF_CAP_XFER_SIZE_64,
|
CRB_INTF_CAP_XFER_SIZE_64,
|
||||||
);
|
);
|
||||||
set_reg_field(
|
set_reg_field(
|
||||||
&mut self.regs,
|
&mut self.regs,
|
||||||
CRB_INTF_ID,
|
CrbRegister::IntfId(IntfIdFields::CapFIFO),
|
||||||
"CapFIFO",
|
|
||||||
CRB_INTF_CAP_FIFO_NOT_SUPPORTED,
|
CRB_INTF_CAP_FIFO_NOT_SUPPORTED,
|
||||||
);
|
);
|
||||||
set_reg_field(
|
set_reg_field(
|
||||||
&mut self.regs,
|
&mut self.regs,
|
||||||
CRB_INTF_ID,
|
CrbRegister::IntfId(IntfIdFields::CapCRB),
|
||||||
"CapCRB",
|
|
||||||
CRB_INTF_CAP_CRB_SUPPORTED,
|
CRB_INTF_CAP_CRB_SUPPORTED,
|
||||||
);
|
);
|
||||||
set_reg_field(
|
set_reg_field(
|
||||||
&mut self.regs,
|
&mut self.regs,
|
||||||
CRB_INTF_ID,
|
CrbRegister::IntfId(IntfIdFields::InterfaceSelector),
|
||||||
"InterfaceSelector",
|
|
||||||
CRB_INTF_IF_SELECTOR_CRB,
|
CRB_INTF_IF_SELECTOR_CRB,
|
||||||
);
|
);
|
||||||
set_reg_field(&mut self.regs, CRB_INTF_ID, "RID", 0b0000);
|
set_reg_field(
|
||||||
set_reg_field(&mut self.regs, CRB_INTF_ID2, "VID", PCI_VENDOR_ID_IBM);
|
&mut self.regs,
|
||||||
|
CrbRegister::IntfId(IntfIdFields::Rid),
|
||||||
|
0b0000,
|
||||||
|
);
|
||||||
|
set_reg_field(
|
||||||
|
&mut self.regs,
|
||||||
|
CrbRegister::IntfId2(IntfId2Fields::Vid),
|
||||||
|
PCI_VENDOR_ID_IBM,
|
||||||
|
);
|
||||||
|
|
||||||
self.regs[CRB_CTRL_CMD_SIZE_REG as usize] = CRB_CTRL_CMD_SIZE as u32;
|
self.regs[CRB_CTRL_CMD_SIZE_REG as usize] = CRB_CTRL_CMD_SIZE as u32;
|
||||||
self.regs[CRB_CTRL_CMD_LADDR as usize] = TPM_CRB_ADDR_BASE + CRB_DATA_BUFFER;
|
self.regs[CRB_CTRL_CMD_LADDR as usize] = TPM_CRB_ADDR_BASE + CRB_DATA_BUFFER;
|
||||||
@@ -271,7 +338,6 @@ impl Tpm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//impl BusDevice for TPM
|
|
||||||
impl BusDevice for Tpm {
|
impl BusDevice for Tpm {
|
||||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||||
let mut offset: u32 = offset as u32;
|
let mut offset: u32 = offset as u32;
|
||||||
@@ -370,10 +436,18 @@ impl BusDevice for Tpm {
|
|||||||
}
|
}
|
||||||
CRB_CTRL_REQ => match v {
|
CRB_CTRL_REQ => match v {
|
||||||
CRB_CTRL_REQ_CMD_READY => {
|
CRB_CTRL_REQ_CMD_READY => {
|
||||||
set_reg_field(&mut self.regs, CRB_CTRL_STS, "tpmIdle", 0);
|
set_reg_field(
|
||||||
|
&mut self.regs,
|
||||||
|
CrbRegister::CtrlSts(CtrlStsFields::TpmIdle),
|
||||||
|
0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
CRB_CTRL_REQ_GO_IDLE => {
|
CRB_CTRL_REQ_GO_IDLE => {
|
||||||
set_reg_field(&mut self.regs, CRB_CTRL_STS, "tpmIdle", 1);
|
set_reg_field(
|
||||||
|
&mut self.regs,
|
||||||
|
CrbRegister::CtrlSts(CtrlStsFields::TpmIdle),
|
||||||
|
1,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!("Invalid value passed to CRTL_REQ register");
|
error!("Invalid value passed to CRTL_REQ register");
|
||||||
@@ -396,27 +470,14 @@ impl BusDevice for Tpm {
|
|||||||
{
|
{
|
||||||
self.regs[CRB_CTRL_START as usize] |= CRB_START_INVOKE;
|
self.regs[CRB_CTRL_START as usize] |= CRB_START_INVOKE;
|
||||||
|
|
||||||
self.cmd = Some(BackendCmd {
|
let mut cmd = BackendCmd {
|
||||||
locality: locality as u8,
|
buffer: &mut self.data_buff,
|
||||||
input: self.data_buff[0..self.data_buff_len].to_vec(),
|
|
||||||
input_len: cmp::min(self.data_buff_len, TPM_CRB_BUFFER_MAX),
|
input_len: cmp::min(self.data_buff_len, TPM_CRB_BUFFER_MAX),
|
||||||
output: self.data_buff.to_vec(),
|
};
|
||||||
output_len: TPM_CRB_BUFFER_MAX,
|
|
||||||
selftest_done: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut cmd = self.cmd.as_ref().unwrap().clone();
|
let status = self.emulator.deliver_request(&mut cmd).is_ok();
|
||||||
let output = self.emulator.deliver_request(&mut cmd).map_err(|e| {
|
|
||||||
Error::DeliverRequest(anyhow!(
|
|
||||||
"Failed to deliver tpm request. Error :{:?}",
|
|
||||||
e
|
|
||||||
))
|
|
||||||
});
|
|
||||||
//TODO: drop the copy here
|
|
||||||
self.data_buff.fill(0);
|
|
||||||
self.data_buff.clone_from_slice(output.unwrap().as_slice());
|
|
||||||
|
|
||||||
self.request_completed(TPM_SUCCESS as isize);
|
self.request_completed(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CRB_LOC_CTRL => {
|
CRB_LOC_CTRL => {
|
||||||
@@ -427,13 +488,33 @@ impl BusDevice for Tpm {
|
|||||||
match v {
|
match v {
|
||||||
CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT => {}
|
CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT => {}
|
||||||
CRB_LOC_CTRL_RELINQUISH => {
|
CRB_LOC_CTRL_RELINQUISH => {
|
||||||
set_reg_field(&mut self.regs, CRB_LOC_STATE, "locAssigned", 0);
|
set_reg_field(
|
||||||
set_reg_field(&mut self.regs, CRB_LOC_STS, "Granted", 0);
|
&mut self.regs,
|
||||||
|
CrbRegister::LocState(LocStateFields::LocAssigned),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
set_reg_field(
|
||||||
|
&mut self.regs,
|
||||||
|
CrbRegister::LocSts(LocStsFields::Granted),
|
||||||
|
0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
CRB_LOC_CTRL_REQUEST_ACCESS => {
|
CRB_LOC_CTRL_REQUEST_ACCESS => {
|
||||||
set_reg_field(&mut self.regs, CRB_LOC_STS, "Granted", 1);
|
set_reg_field(
|
||||||
set_reg_field(&mut self.regs, CRB_LOC_STS, "beenSeized", 0);
|
&mut self.regs,
|
||||||
set_reg_field(&mut self.regs, CRB_LOC_STATE, "locAssigned", 1);
|
CrbRegister::LocSts(LocStsFields::Granted),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
set_reg_field(
|
||||||
|
&mut self.regs,
|
||||||
|
CrbRegister::LocSts(LocStsFields::BeenSeized),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
set_reg_field(
|
||||||
|
&mut self.regs,
|
||||||
|
CrbRegister::LocState(LocStateFields::LocAssigned),
|
||||||
|
1,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!("Invalid value to write in CRB_LOC_CTRL {:#X} ", v);
|
error!("Invalid value to write in CRB_LOC_CTRL {:#X} ", v);
|
||||||
@@ -459,10 +540,10 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_set_get_reg_field() {
|
fn test_set_get_reg_field() {
|
||||||
let mut regs: [u32; TPM_CRB_R_MAX as usize] = [0; TPM_CRB_R_MAX as usize];
|
let mut regs: [u32; TPM_CRB_R_MAX] = [0; TPM_CRB_R_MAX];
|
||||||
set_reg_field(&mut regs, CRB_INTF_ID, "RID", 0xAC);
|
set_reg_field(&mut regs, CrbRegister::IntfId(IntfIdFields::Rid), 0xAC);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_reg_field(®s, CRB_INTF_ID, "RID"),
|
get_reg_field(®s, CrbRegister::IntfId(IntfIdFields::Rid)),
|
||||||
0xAC,
|
0xAC,
|
||||||
concat!("Test: ", stringify!(set_get_reg_field))
|
concat!("Test: ", stringify!(set_get_reg_field))
|
||||||
);
|
);
|
||||||
|
|||||||
38
docs/amd_sev_snp.md
Normal file
38
docs/amd_sev_snp.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# AMD SEV-SNP
|
||||||
|
|
||||||
|
### WARNING
|
||||||
|
This feature is only currently supported on MSHV.
|
||||||
|
|
||||||
|
AMD Secure Encrypted Virtualization & Secure Nested Paging (SEV-SNP) is an AMD
|
||||||
|
technology designed to add strong memory integrity protection to help prevent
|
||||||
|
malicious hypervisor-based attacks like data replay, memory-remapping and more
|
||||||
|
in order to create an isolated execution environment. Here are some useful
|
||||||
|
links:
|
||||||
|
|
||||||
|
* [SNP Homepage] (https://www.amd.com/en/processors/amd-secure-encrypted-virtualization)
|
||||||
|
more information about SEV-SNP technical aspects, design and specification.
|
||||||
|
|
||||||
|
## Cloud Hypervisor support
|
||||||
|
|
||||||
|
It is required to use a machine which has enabled support for AMD SEV-SNP in
|
||||||
|
the BIOS.
|
||||||
|
|
||||||
|
On the Cloud Hypervisor side, all you need is to build the project with the
|
||||||
|
`sev_snp` feature enabled:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build --no-default-features --features "sev_snp"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**
|
||||||
|
Please note that `sev_snp` cannot be enabled in conjunction with `tdx` feature flag.
|
||||||
|
|
||||||
|
You can run a SEV-SNP VM using the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cloud-hypervisor \
|
||||||
|
--platform sev_snp=on \
|
||||||
|
--cpus boot=1 \
|
||||||
|
--memory size=1G \
|
||||||
|
--disk path=ubuntu.img
|
||||||
|
```
|
||||||
241
docs/api.md
241
docs/api.md
@@ -1,18 +1,21 @@
|
|||||||
- [Cloud Hypervisor API](#cloud-hypervisor-api)
|
- [Cloud Hypervisor API](#cloud-hypervisor-api)
|
||||||
- [External API](#external-api)
|
- [External API](#external-api)
|
||||||
- [REST API](#rest-api)
|
- [REST API](#rest-api)
|
||||||
- [Location and availability](#location-and-availability)
|
- [REST API Location and availability](#rest-api-location-and-availability)
|
||||||
- [Endpoints](#endpoints)
|
- [REST API Endpoints](#rest-api-endpoints)
|
||||||
- [Virtual Machine Manager (VMM) Actions](#virtual-machine-manager-vmm-actions)
|
- [Virtual Machine Manager (VMM) Actions](#virtual-machine-manager-vmm-actions)
|
||||||
- [Virtual Machine (VM) Actions](#virtual-machine-vm-actions)
|
- [Virtual Machine (VM) Actions](#virtual-machine-vm-actions)
|
||||||
- [REST API Examples](#rest-api-examples)
|
- [REST API Examples](#rest-api-examples)
|
||||||
- [Create a Virtual Machine](#create-a-virtual-machine)
|
- [Create a Virtual Machine](#create-a-virtual-machine)
|
||||||
- [Boot a Virtual Machine](#boot-a-virtual-machine)
|
- [Boot a Virtual Machine](#boot-a-virtual-machine)
|
||||||
- [Dump a Virtual Machine Information](#dump-a-virtual-machine-information)
|
- [Dump a Virtual Machine Information](#dump-a-virtual-machine-information)
|
||||||
- [Reboot a Virtual Machine](#reboot-a-virtual-machine)
|
- [Reboot a Virtual Machine](#reboot-a-virtual-machine)
|
||||||
- [Shut a Virtual Machine Down](#shut-a-virtual-machine-down)
|
- [Shut a Virtual Machine Down](#shut-a-virtual-machine-down)
|
||||||
|
- [D-Bus API](#d-bus-api)
|
||||||
|
- [D-Bus API Location and availability](#d-bus-api-location-and-availability)
|
||||||
|
- [D-Bus API Interface](#d-bus-api-interface)
|
||||||
- [Command Line Interface](#command-line-interface)
|
- [Command Line Interface](#command-line-interface)
|
||||||
- [REST API and CLI Architectural Relationship](#rest-api-and-cli-architectural-relationship)
|
- [REST API, D-Bus API and CLI Architectural Relationship](#rest-api-and-cli-architectural-relationship)
|
||||||
- [Internal API](#internal-api)
|
- [Internal API](#internal-api)
|
||||||
- [Goals and Design](#goals-and-design)
|
- [Goals and Design](#goals-and-design)
|
||||||
- [End to End Example](#end-to-end-example)
|
- [End to End Example](#end-to-end-example)
|
||||||
@@ -21,9 +24,11 @@
|
|||||||
|
|
||||||
The Cloud Hypervisor API is made of 2 distinct interfaces:
|
The Cloud Hypervisor API is made of 2 distinct interfaces:
|
||||||
|
|
||||||
1. **The external API**. This is the user facing API. Users and operators can
|
1. **The External API** This is the user facing API. Users and operators
|
||||||
control and manage Cloud Hypervisor through either a REST API or a Command
|
can control and manage the Cloud Hypervisor through various options
|
||||||
Line Interface (CLI).
|
including a REST API, a Command Line Interface (CLI) or a D-Bus based API,
|
||||||
|
which is not compiled into Cloud Hypervisor by default.
|
||||||
|
|
||||||
1. **The internal API**, based on [rust's Multi-Producer, Single-Consumer (MPSC)](https://doc.rust-lang.org/std/sync/mpsc/)
|
1. **The internal API**, based on [rust's Multi-Producer, Single-Consumer (MPSC)](https://doc.rust-lang.org/std/sync/mpsc/)
|
||||||
module. This API is used internally by the Cloud Hypervisor threads to
|
module. This API is used internally by the Cloud Hypervisor threads to
|
||||||
communicate between each others.
|
communicate between each others.
|
||||||
@@ -40,10 +45,10 @@ API triggers VM and VMM specific actions, and as such it is designed as a
|
|||||||
collection of RPC-style, static methods.
|
collection of RPC-style, static methods.
|
||||||
|
|
||||||
The API is [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md)
|
The API is [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md)
|
||||||
compliant. Please consult the [Cloud Hypervisor API](https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/master/vmm/src/api/openapi/cloud-hypervisor.yaml)
|
compliant. Please consult the [Cloud Hypervisor OpenAPI Document](https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/master/vmm/src/api/openapi/cloud-hypervisor.yaml)
|
||||||
document for more details about the API payloads and responses.
|
for more details about the API payloads and responses.
|
||||||
|
|
||||||
### Location and availability
|
#### REST API Location and availability
|
||||||
|
|
||||||
The REST API is available as soon as the Cloud Hypervisor binary is started,
|
The REST API is available as soon as the Cloud Hypervisor binary is started,
|
||||||
through a local UNIX socket.
|
through a local UNIX socket.
|
||||||
@@ -65,48 +70,55 @@ Cloud Hypervisor Guest
|
|||||||
Disk(s): None
|
Disk(s): None
|
||||||
```
|
```
|
||||||
|
|
||||||
### Endpoints
|
#### REST API Endpoints
|
||||||
|
|
||||||
The Cloud Hypervisor API exposes the following actions through its endpoints:
|
The Cloud Hypervisor API exposes the following actions through its endpoints:
|
||||||
|
|
||||||
#### Virtual Machine Manager (VMM) Actions
|
##### Virtual Machine Manager (VMM) Actions
|
||||||
|
|
||||||
| Action | Endpoint | Request Body | Response Body | Prerequisites |
|
| Action | Endpoint | Request Body | Response Body | Prerequisites |
|
||||||
| ----------------------------------- | --------------- | ------------ | -------------------------- | ------------------ |
|
| ----------------------------------- | --------------- | ------------ | -------------------------- | ------------------ |
|
||||||
| Check for the REST API availability | `/vmm.ping` | N/A | `/schemas/VmmPingResponse` | N/A |
|
| Check for the REST API availability | `/vmm.ping` | N/A | `/schemas/VmmPingResponse` | N/A |
|
||||||
| Shut the VMM down | `/vmm.shutdown` | N/A | N/A | The VMM is running |
|
| Shut the VMM down | `/vmm.shutdown` | N/A | N/A | The VMM is running |
|
||||||
|
|
||||||
#### Virtual Machine (VM) Actions
|
##### Virtual Machine (VM) Actions
|
||||||
|
|
||||||
| Action | Endpoint | Request Body | Response Body | Prerequisites |
|
| Action | Endpoint | Request Body | Response Body | Prerequisites |
|
||||||
| ---------------------------------- | --------------------- | --------------------------- | ------------------------ | -------------------------------- |
|
| ---------------------------------- | ----------------------- | ------------------------------- | ------------------------ | ------------------------------------------------------ |
|
||||||
| Create the VM | `/vm.create` | `/schemas/VmConfig` | N/A | The VM is not created yet |
|
| Create the VM | `/vm.create` | `/schemas/VmConfig` | N/A | The VM is not created yet |
|
||||||
| Delete the VM | `/vm.delete` | N/A | N/A | N/A |
|
| Delete the VM | `/vm.delete` | N/A | N/A | N/A |
|
||||||
| Boot the VM | `/vm.boot` | N/A | N/A | The VM is created but not booted |
|
| Boot the VM | `/vm.boot` | N/A | N/A | The VM is created but not booted |
|
||||||
| Shut the VM down | `/vm.shutdown` | N/A | N/A | The VM is booted |
|
| Shut the VM down | `/vm.shutdown` | N/A | N/A | The VM is booted |
|
||||||
| Reboot the VM | `/vm.reboot` | N/A | N/A | The VM is booted |
|
| Reboot the VM | `/vm.reboot` | N/A | N/A | The VM is booted |
|
||||||
| Trigger power button of the VM | `/vm.power-button` | N/A | N/A | The VM is booted |
|
| Trigger power button of the VM | `/vm.power-button` | N/A | N/A | The VM is booted |
|
||||||
| Pause the VM | `/vm.pause` | N/A | N/A | The VM is booted |
|
| Pause the VM | `/vm.pause` | N/A | N/A | The VM is booted |
|
||||||
| Resume the VM | `/vm.resume` | N/A | N/A | The VM is paused |
|
| Resume the VM | `/vm.resume` | N/A | N/A | The VM is paused |
|
||||||
| Task a snapshot of the VM | `/vm.snapshot` | `/schemas/VmSnapshotConfig` | N/A | The VM is paused |
|
| Task a snapshot of the VM | `/vm.snapshot` | `/schemas/VmSnapshotConfig` | N/A | The VM is paused |
|
||||||
| Perform a coredump of the VM | `/vm.coredump` | `/schemas/VmCoredumpData` | N/A | The VM is paused |
|
| Perform a coredump of the VM* | `/vm.coredump` | `/schemas/VmCoredumpData` | N/A | The VM is paused |
|
||||||
| Restore the VM from a snapshot | `/vm.restore` | `/schemas/RestoreConfig` | N/A | The VM is created but not booted |
|
| Restore the VM from a snapshot | `/vm.restore` | `/schemas/RestoreConfig` | N/A | The VM is created but not booted |
|
||||||
| Add/remove CPUs to/from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted |
|
| Add/remove CPUs to/from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted |
|
||||||
| Add/remove memory from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted |
|
| Add/remove memory from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted |
|
||||||
| Add/remove memory from a zone | `/vm.resize-zone` | `/schemas/VmResizeZone` | N/A | The VM is booted |
|
| Add/remove memory from a zone | `/vm.resize-zone` | `/schemas/VmResizeZone` | N/A | The VM is booted |
|
||||||
| Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created |
|
| Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created |
|
||||||
| Add VFIO PCI device to the VM | `/vm.add-device` | `/schemas/VmAddDevice` | `/schemas/PciDeviceInfo` | The VM is booted |
|
| Add VFIO PCI device to the VM | `/vm.add-device` | `/schemas/VmAddDevice` | `/schemas/PciDeviceInfo` | The VM is booted |
|
||||||
| Add disk device to the VM | `/vm.add-disk` | `/schemas/DiskConfig` | `/schemas/PciDeviceInfo` | The VM is booted |
|
| Add disk device to the VM | `/vm.add-disk` | `/schemas/DiskConfig` | `/schemas/PciDeviceInfo` | The VM is booted |
|
||||||
| Add fs device to the VM | `/vm.add-fs` | `/schemas/FsConfig` | `/schemas/PciDeviceInfo` | The VM is booted |
|
| Add fs device to the VM | `/vm.add-fs` | `/schemas/FsConfig` | `/schemas/PciDeviceInfo` | The VM is booted |
|
||||||
| Add pmem device to the VM | `/vm.add-pmem` | `/schemas/PmemConfig` | `/schemas/PciDeviceInfo` | The VM is booted |
|
| Add pmem device to the VM | `/vm.add-pmem` | `/schemas/PmemConfig` | `/schemas/PciDeviceInfo` | The VM is booted |
|
||||||
| Add network device to the VM | `/vm.add-net` | `/schemas/NetConfig` | `/schemas/PciDeviceInfo` | The VM is booted |
|
| Add network device to the VM | `/vm.add-net` | `/schemas/NetConfig` | `/schemas/PciDeviceInfo` | The VM is booted |
|
||||||
| Add userspace PCI device to the VM | `/vm.add-user-device` | `/schemas/VmAddUserDevice` | `/schemas/PciDeviceInfo` | The VM is booted |
|
| Add userspace PCI device to the VM | `/vm.add-user-device` | `/schemas/VmAddUserDevice` | `/schemas/PciDeviceInfo` | The VM is booted |
|
||||||
| Add vdpa device to the VM | `/vm.add-vdpa` | `/schemas/VdpaConfig` | `/schemas/PciDeviceInfo` | The VM is booted |
|
| Add vdpa device to the VM | `/vm.add-vdpa` | `/schemas/VdpaConfig` | `/schemas/PciDeviceInfo` | The VM is booted |
|
||||||
| Add vsock device to the VM | `/vm.add-vsock` | `/schemas/VsockConfig` | `/schemas/PciDeviceInfo` | The VM is booted |
|
| Add vsock device to the VM | `/vm.add-vsock` | `/schemas/VsockConfig` | `/schemas/PciDeviceInfo` | The VM is booted |
|
||||||
| Remove device from the VM | `/vm.remove-device` | `/schemas/VmRemoveDevice` | N/A | The VM is booted |
|
| Remove device from the VM | `/vm.remove-device` | `/schemas/VmRemoveDevice` | N/A | The VM is booted |
|
||||||
| Dump the VM counters | `/vm.counters` | N/A | `/schemas/VmCounters` | The VM is booted |
|
| Dump the VM counters | `/vm.counters` | N/A | `/schemas/VmCounters` | The VM is booted |
|
||||||
|
| Prepare to receive a migration | `/vm.receive-migration` | `/schemas/ReceiveMigrationData` | N/A | N/A |
|
||||||
|
| Start to send migration to target | `/vm.send-migration` | `/schemas/SendMigrationData` | N/A | The VM is booted and (shared mem or hugepages enabled) |
|
||||||
|
|
||||||
### REST API Examples
|
* The `vmcoredump` action is available exclusively for the `x86_64`
|
||||||
|
architecture and can be executed only when the `guest_debug` feature is
|
||||||
|
enabled. Without this feature, the corresponding [REST API](#rest-api) or
|
||||||
|
[D-Bus API](#d-bus-api) endpoints are not available.
|
||||||
|
|
||||||
|
#### REST API Examples
|
||||||
|
|
||||||
For the following set of examples, we assume Cloud Hypervisor is started with
|
For the following set of examples, we assume Cloud Hypervisor is started with
|
||||||
the REST API available at `/tmp/cloud-hypervisor.sock`:
|
the REST API available at `/tmp/cloud-hypervisor.sock`:
|
||||||
@@ -122,7 +134,7 @@ Cloud Hypervisor Guest
|
|||||||
Disk(s): None
|
Disk(s): None
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Create a Virtual Machine
|
##### Create a Virtual Machine
|
||||||
|
|
||||||
We want to create a virtual machine with the following characteristics:
|
We want to create a virtual machine with the following characteristics:
|
||||||
|
|
||||||
@@ -150,7 +162,7 @@ curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
|||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Boot a Virtual Machine
|
##### Boot a Virtual Machine
|
||||||
|
|
||||||
Once the VM is created, we can boot it:
|
Once the VM is created, we can boot it:
|
||||||
|
|
||||||
@@ -160,7 +172,7 @@ Once the VM is created, we can boot it:
|
|||||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.boot'
|
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.boot'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Dump a Virtual Machine Information
|
##### Dump a Virtual Machine Information
|
||||||
|
|
||||||
We can fetch information about any VM, as soon as it's created:
|
We can fetch information about any VM, as soon as it's created:
|
||||||
|
|
||||||
@@ -172,7 +184,7 @@ curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
|||||||
-H 'Accept: application/json'
|
-H 'Accept: application/json'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Reboot a Virtual Machine
|
##### Reboot a Virtual Machine
|
||||||
|
|
||||||
We can reboot a VM that's already booted:
|
We can reboot a VM that's already booted:
|
||||||
|
|
||||||
@@ -182,7 +194,7 @@ We can reboot a VM that's already booted:
|
|||||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.reboot'
|
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.reboot'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Shut a Virtual Machine Down
|
##### Shut a Virtual Machine Down
|
||||||
|
|
||||||
Once booted, we can shut a VM down from the REST API:
|
Once booted, we can shut a VM down from the REST API:
|
||||||
|
|
||||||
@@ -192,6 +204,68 @@ Once booted, we can shut a VM down from the REST API:
|
|||||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.shutdown'
|
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.shutdown'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### D-Bus API
|
||||||
|
|
||||||
|
Cloud Hypervisor offers a D-Bus API as an alternative to its REST API. This
|
||||||
|
D-Bus API fully reflects the functionality of the REST API, exposing the
|
||||||
|
same group of endpoints. It can be a drop-in replacement since it also
|
||||||
|
consumes/produces JSON.
|
||||||
|
|
||||||
|
In addition, the D-Bus API also exposes events from `event-monitor` in the
|
||||||
|
form of a D-Bus signal to which users can subscribe. For more information,
|
||||||
|
see [D-Bus API Interface](#d-bus-api-interface).
|
||||||
|
|
||||||
|
#### D-Bus API Location and availability
|
||||||
|
|
||||||
|
This feature is not compiled into Cloud Hypervisor by default. Users who
|
||||||
|
wish to use the D-Bus API, must explicitly enable it with the `dbus_api`
|
||||||
|
feature flag when compiling Cloud Hypervisor.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ ./scripts/dev_cli.sh build --release --libc musl -- --features dbus_api
|
||||||
|
```
|
||||||
|
|
||||||
|
Once this feature is enabled, it can be configured with the following
|
||||||
|
CLI options:
|
||||||
|
|
||||||
|
```
|
||||||
|
--dbus-service-name
|
||||||
|
well known name of the service
|
||||||
|
--dbus-object-path
|
||||||
|
object path to serve the dbus interface
|
||||||
|
--dbus-system-bus use the system bus instead of a session bus
|
||||||
|
```
|
||||||
|
|
||||||
|
Example invocation:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ ./cloud-hypervisor --dbus-service-name "org.cloudhypervisor.DBusApi" \
|
||||||
|
--dbus-object-path "/org/cloudhypervisor/DBusApi"
|
||||||
|
```
|
||||||
|
|
||||||
|
This will start serving a service with the name `org.cloudhypervisor.DBusApi1`
|
||||||
|
which in turn can be used to control and manage Cloud Hypervisor.
|
||||||
|
|
||||||
|
#### D-Bus API Interface
|
||||||
|
|
||||||
|
Please refer to the [REST API](#rest-api) documentation for everything that
|
||||||
|
is in common with the REST API. As previously mentioned, the D-Bus API can
|
||||||
|
be used as a drop-in replacement for the [REST API](#rest-api).
|
||||||
|
|
||||||
|
The D-Bus interface also exposes a signal, named `Event`, which is emitted
|
||||||
|
whenever a new event is published from the `event-monitor` crate. Here is its
|
||||||
|
definition in XML format:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<node>
|
||||||
|
<interface name="org.cloudhypervisor.DBusApi1">
|
||||||
|
<signal name="Event">
|
||||||
|
<arg name="event" type="s"/>
|
||||||
|
</signal>
|
||||||
|
</interface>
|
||||||
|
</node>
|
||||||
|
```
|
||||||
|
|
||||||
### Command Line Interface
|
### Command Line Interface
|
||||||
|
|
||||||
The Cloud Hypervisor Command Line Interface (CLI) can only be used for launching
|
The Cloud Hypervisor Command Line Interface (CLI) can only be used for launching
|
||||||
@@ -199,33 +273,41 @@ the Cloud Hypervisor binary, i.e. it can not be used for controlling the VMM or
|
|||||||
the launched VM once they're up and running.
|
the launched VM once they're up and running.
|
||||||
|
|
||||||
If you want to inspect the VMM, or control the VM after launching Cloud
|
If you want to inspect the VMM, or control the VM after launching Cloud
|
||||||
Hypervisor from the CLI, you must use the [REST API](#rest-api).
|
Hypervisor from the CLI, you must use either the [REST API](#rest-api)
|
||||||
|
or the [D-Bus API](#d-bus-api).
|
||||||
|
|
||||||
From the CLI, one can either:
|
From the CLI, one can:
|
||||||
|
|
||||||
1. Create and boot a complete virtual machine by using the CLI options to build
|
1. Create and boot a complete virtual machine by using the CLI options to build
|
||||||
the VM config. Run `cloud-hypervisor --help` for a complete list of CLI
|
the VM config. Run `cloud-hypervisor --help` for a complete list of CLI
|
||||||
options. As soon as the `cloud-hypervisor` binary is launched, the
|
options. As soon as the `cloud-hypervisor` binary is launched, contrary
|
||||||
[REST API](#rest-api) is available for controlling and managing the VM.
|
to the [D-Bus API](#d-bus-api), the [REST API](#rest-api) is available
|
||||||
1. Start the [REST API](#rest-api) server only, by not passing any VM
|
for controlling and managing the VM. The [D-Bus API](#d-bus-api) doesn't start
|
||||||
configuration options. The VM can then be asynchronously created and booted
|
automatically and needs to be explicitly configured in order to be run.
|
||||||
by sending HTTP commands to the [REST API](#rest-api). Check the
|
1. Start either the REST API, D-Bus API or both simultaneously without passing
|
||||||
[REST API examples](#rest-api-examples) section for more details.
|
any VM configuration options. The VM can then be asynchronously created and
|
||||||
|
booted by calling API methods of choice. It should be noted that one external
|
||||||
|
API does not exclude another; it is possible to have both the REST and D-Bus
|
||||||
|
APIs running simultaneously.
|
||||||
|
|
||||||
### REST API and CLI Architectural Relationship
|
### REST API, D-Bus API and CLI Architectural Relationship
|
||||||
|
|
||||||
The REST API and the CLI both rely on a common, [internal API](#internal-api).
|
The REST API, D-Bus API and the CLI all rely on a common, [internal API](#internal-api).
|
||||||
|
|
||||||
The CLI options are parsed by the
|
The CLI options are parsed by the
|
||||||
[clap crate](https://docs.rs/clap/2.33.0/clap/) and then translated into
|
[argh crate](https://docs.rs/argh/latest/argh/) and then translated into
|
||||||
[internal API](#internal-api) commands.
|
[internal API](#internal-api) commands.
|
||||||
|
|
||||||
The REST API is processed by an HTTP thread using the
|
The REST API is processed by an HTTP thread using the
|
||||||
[Firecracker's `micro_http`](https://github.com/firecracker-microvm/firecracker/tree/master/src/micro_http)
|
[Firecracker's `micro_http`](https://github.com/firecracker-microvm/micro-http)
|
||||||
crate. As with the CLI, the HTTP requests eventually get translated into
|
crate. As with the CLI, the HTTP requests eventually get translated into
|
||||||
[internal API](#internal-api) commands.
|
[internal API](#internal-api) commands.
|
||||||
|
|
||||||
As a summary, the REST API and the CLI are essentially frontends for the
|
The D-Bus API is implemented using the [zbus](https://github.com/dbus2/zbus)
|
||||||
|
crate and runs in its own thread. Whenever it needs to call the [internal API](#internal-api),
|
||||||
|
the [blocking](https://github.com/smol-rs/blocking) crate is used perform the call in zbus' async context.
|
||||||
|
|
||||||
|
As a summary, the REST API, the D-Bus API and the CLI are essentially frontends for the
|
||||||
[internal API](#internal-api):
|
[internal API](#internal-api):
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -236,16 +318,16 @@ As a summary, the REST API and the CLI are essentially frontends for the
|
|||||||
| +------------------+ |
|
| +------------------+ |
|
||||||
| | +------------------------+
|
| | +------------------------+
|
||||||
| | | |
|
| | | |
|
||||||
+------------+ | | | |
|
+------------+ | +----------+ | | |
|
||||||
| | | | | +--------------+ |
|
| | | D-Bus API | | | | +--------------+ |
|
||||||
| User +---------+ +------> | Internal API | |
|
| User +---------+----------->+ zbus +--------------+------> | Internal API | |
|
||||||
| | | | | +--------------+ |
|
| | | | | | | +--------------+ |
|
||||||
+------------+ | | | |
|
+------------+ | +----------+ | | |
|
||||||
| | | |
|
| | | |
|
||||||
| | +------------------------+
|
| | +------------------------+
|
||||||
| +----------+ | VMM
|
| +----------+ | VMM
|
||||||
| CLI | | |
|
| CLI | | |
|
||||||
+----------->+ clap +--------------+
|
+----------->+ argh +--------------+
|
||||||
| |
|
| |
|
||||||
+----------+
|
+----------+
|
||||||
|
|
||||||
@@ -255,22 +337,23 @@ As a summary, the REST API and the CLI are essentially frontends for the
|
|||||||
## Internal API
|
## Internal API
|
||||||
|
|
||||||
The Cloud Hypervisor internal API, as its name suggests, is used internally
|
The Cloud Hypervisor internal API, as its name suggests, is used internally
|
||||||
by the different Cloud Hypervisor threads (VMM, HTTP, control loop, etc) to
|
by the different Cloud Hypervisor threads (VMM, HTTP, D-Bus, control loop,
|
||||||
send commands and responses to each others.
|
etc) to send commands and responses to each others.
|
||||||
|
|
||||||
It is based on [rust's Multi-Producer, Single-Consumer (MPSC)](https://doc.rust-lang.org/std/sync/mpsc/),
|
It is based on [rust's Multi-Producer, Single-Consumer (MPSC)](https://doc.rust-lang.org/std/sync/mpsc/),
|
||||||
and the single consumer (a.k.a. the API receiver) is the Cloud Hypervisor
|
and the single consumer (a.k.a. the API receiver) is the Cloud Hypervisor
|
||||||
control loop.
|
control loop.
|
||||||
|
|
||||||
API producers are the HTTP thread handling the [REST API](#rest-api) and the
|
API producers are the HTTP thread handling the [REST API](#rest-api), the
|
||||||
main thread that initially parses the [CLI](#command-line-interface).
|
D-Bus thread handling the [D-Bus API](#d-bus-api) and the main thread that
|
||||||
|
initially parses the [CLI](#command-line-interface).
|
||||||
|
|
||||||
### Goals and Design
|
### Goals and Design
|
||||||
|
|
||||||
The internal API is designed for controlling, managing and inspecting a Cloud
|
The internal API is designed for controlling, managing and inspecting a Cloud
|
||||||
Hypervisor VMM and its guest. It is a backend for handling external, user
|
Hypervisor VMM and its guest. It is a backend for handling external, user
|
||||||
visible requests through either the [REST API](#rest-api) or the
|
visible requests through the [REST API](#rest-api), the [D-Bus API](#d-bus-api)
|
||||||
[CLI](#command-line-interface) interfaces.
|
or the [CLI](#command-line-interface) interfaces.
|
||||||
|
|
||||||
The API follows a command-response scheme that closely maps the [REST API](#rest-api).
|
The API follows a command-response scheme that closely maps the [REST API](#rest-api).
|
||||||
Any command must be replied to with a response.
|
Any command must be replied to with a response.
|
||||||
@@ -379,5 +462,5 @@ APIs work together, let's look at a complete VM creation flow, from the
|
|||||||
```
|
```
|
||||||
1. The Cloud Hypervisor HTTP thread sends the formed HTTP response back to the
|
1. The Cloud Hypervisor HTTP thread sends the formed HTTP response back to the
|
||||||
user. This is abstracted by the
|
user. This is abstracted by the
|
||||||
[micro_http](https://github.com/firecracker-microvm/firecracker/tree/master/src/micro_http)
|
[micro_http](https://github.com/firecracker-microvm/micro-http)
|
||||||
crate.
|
crate.
|
||||||
|
|||||||
152
docs/arm64.md
152
docs/arm64.md
@@ -1,152 +0,0 @@
|
|||||||
# How to build and test Cloud Hypervisor on AArch64
|
|
||||||
|
|
||||||
This document introduces how to build and test Cloud Hypervisor on AArch64.
|
|
||||||
Currently, Cloud Hypervisor supports 2 methods of booting on AArch64: UEFI
|
|
||||||
booting and direct-kernel booting. The document covers both methods.
|
|
||||||
|
|
||||||
All the steps are based on Ubuntu. We use the Ubuntu cloud image for guest VM
|
|
||||||
disk.
|
|
||||||
|
|
||||||
## Hardware requirements
|
|
||||||
|
|
||||||
- AArch64 servers (recommended) or development boards equipped with the GICv3
|
|
||||||
interrupt controller.
|
|
||||||
|
|
||||||
- On development boards that have constrained RAM resources, if the creation of
|
|
||||||
a VM consumes a large portion of the free memory on the host, it may be required
|
|
||||||
to enable swap. For example, this was required on a board with 3 GB of RAM
|
|
||||||
booting a 2 GB VM at a point in time when 2.8 GB were free. Without enabling
|
|
||||||
swap the `cloud-hypervisor` process was terminated by the OOM killer. In this
|
|
||||||
situation memory was allocated for the virtual machine using memfd while the
|
|
||||||
page cache was filled, leading to a situation where the kernel could not even
|
|
||||||
drop caches. Making a small section of swap available (observably, 1 to 15 MB),
|
|
||||||
this situation can be resolved and the resulting memory footprint of
|
|
||||||
`cloud-hypervisor` is as expected.
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
We create a folder to build and run Cloud Hypervisor at `$HOME/cloud-hypervisor`
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ export CLOUDH=$HOME/cloud-hypervisor
|
|
||||||
$ mkdir $CLOUDH
|
|
||||||
```
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
You need to install some prerequisite packages to build and test Cloud Hypervisor.
|
|
||||||
|
|
||||||
### Tools
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install rust tool chain
|
|
||||||
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
|
||||||
# Install the tools used for building guest kernel, EDK2 and converting guest disk
|
|
||||||
$ sudo apt-get update
|
|
||||||
$ sudo apt-get install git build-essential m4 bison flex uuid-dev qemu-utils
|
|
||||||
```
|
|
||||||
|
|
||||||
### Building Cloud Hypervisor
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ pushd $CLOUDH
|
|
||||||
$ git clone https://github.com/cloud-hypervisor/cloud-hypervisor.git
|
|
||||||
$ cd cloud-hypervisor
|
|
||||||
$ cargo build
|
|
||||||
$ popd
|
|
||||||
```
|
|
||||||
|
|
||||||
### Disk image
|
|
||||||
|
|
||||||
Download the Ubuntu cloud image and convert the image type.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ pushd $CLOUDH
|
|
||||||
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-arm64.img
|
|
||||||
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-arm64.img focal-server-cloudimg-arm64.raw
|
|
||||||
$ popd
|
|
||||||
```
|
|
||||||
|
|
||||||
## UEFI booting
|
|
||||||
|
|
||||||
This part introduces how to build EDK2 firmware and boot Cloud Hypervisor with it.
|
|
||||||
|
|
||||||
### Building EDK2
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ pushd $CLOUDH
|
|
||||||
|
|
||||||
# Clone source code repos
|
|
||||||
$ git clone --depth 1 https://github.com/tianocore/edk2.git -b master
|
|
||||||
$ cd edk2
|
|
||||||
$ git submodule update --init
|
|
||||||
$ cd ..
|
|
||||||
$ git clone --depth 1 https://github.com/tianocore/edk2-platforms.git -b master
|
|
||||||
$ git clone --depth 1 https://github.com/acpica/acpica.git -b master
|
|
||||||
|
|
||||||
# Build tools
|
|
||||||
$ export PACKAGES_PATH="$PWD/edk2:$PWD/edk2-platforms"
|
|
||||||
$ export IASL_PREFIX="$PWD/acpica/generate/unix/bin/"
|
|
||||||
$ make -C acpica
|
|
||||||
$ cd edk2/
|
|
||||||
$ . edksetup.sh
|
|
||||||
$ cd ..
|
|
||||||
$ make -C edk2/BaseTools
|
|
||||||
|
|
||||||
# Build EDK2
|
|
||||||
$ build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtCloudHv.dsc -b RELEASE
|
|
||||||
|
|
||||||
$ popd
|
|
||||||
```
|
|
||||||
|
|
||||||
If the build goes well, the EDK2 binary is available at
|
|
||||||
`edk2/Build/ArmVirtCloudHv-AARCH64/RELEASE_GCC5/FV/CLOUDHV_EFI.fd`.
|
|
||||||
|
|
||||||
### Booting the guest VM
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ pushd $CLOUDH
|
|
||||||
$ sudo RUST_BACKTRACE=1 $CLOUDH/cloud-hypervisor/target/debug/cloud-hypervisor \
|
|
||||||
--api-socket /tmp/cloud-hypervisor.sock \
|
|
||||||
--kernel $CLOUDH/edk2/Build/ArmVirtCloudHv-AARCH64/RELEASE_GCC5/FV/CLOUDHV_EFI.fd \
|
|
||||||
--disk path=$CLOUDH/focal-server-cloudimg-arm64.raw \
|
|
||||||
--cpus boot=4 \
|
|
||||||
--memory size=4096M \
|
|
||||||
--net tap=,mac=12:34:56:78:90:01,ip=192.168.1.1,mask=255.255.255.0 \
|
|
||||||
--serial tty \
|
|
||||||
--console off
|
|
||||||
$ popd
|
|
||||||
```
|
|
||||||
|
|
||||||
## Direct-kernel booting
|
|
||||||
|
|
||||||
Alternativelly, you can build your own kernel for guest VM. This way, UEFI is
|
|
||||||
not involved and ACPI cannot be enabled.
|
|
||||||
|
|
||||||
### Building kernel
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ pushd $CLOUDH
|
|
||||||
$ git clone --depth 1 "https://github.com/cloud-hypervisor/linux.git" -b ch-5.12
|
|
||||||
$ cd linux
|
|
||||||
$ cp $CLOUDH/cloud-hypervisor/resources/linux-config-aarch64 .config
|
|
||||||
$ make -j `nproc`
|
|
||||||
$ popd
|
|
||||||
```
|
|
||||||
|
|
||||||
### Booting the guest VM
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ pushd $CLOUDH
|
|
||||||
$ sudo $CLOUDH/cloud-hypervisor/target/debug/cloud-hypervisor \
|
|
||||||
--api-socket /tmp/cloud-hypervisor.sock \
|
|
||||||
--kernel $CLOUDH/linux/arch/arm64/boot/Image \
|
|
||||||
--disk path=focal-server-cloudimg-arm64.raw \
|
|
||||||
--cmdline "keep_bootcon console=ttyAMA0 reboot=k panic=1 root=/dev/vda1 rw" \
|
|
||||||
--cpus boot=4 \
|
|
||||||
--memory size=4096M \
|
|
||||||
--net tap=,mac=12:34:56:78:90:01,ip=192.168.1.1,mask=255.255.255.0 \
|
|
||||||
--serial tty \
|
|
||||||
--console off
|
|
||||||
$ popd
|
|
||||||
```
|
|
||||||
@@ -24,12 +24,15 @@ Hypervisor. Here, all the steps are based on Ubuntu, for other Linux
|
|||||||
distributions please replace the package manager and package name.
|
distributions please replace the package manager and package name.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# Install build-essential, git, and qemu-utils
|
# Install basic packages needed. For a package list targeting for more
|
||||||
$ sudo apt install git build-essential qemu-utils
|
# functionalities for example the test, please see resources/Dockerfile.
|
||||||
|
$ sudo apt-get update
|
||||||
|
$ sudo apt install git build-essential m4 bison flex uuid-dev qemu-utils musl-tools
|
||||||
# Install rust tool chain
|
# Install rust tool chain
|
||||||
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
# If you want to build statically linked binary please add musl target
|
# If you want to build statically linked binary please add musl target
|
||||||
$ rustup target add x86_64-unknown-linux-musl
|
$ rustup target add x86_64-unknown-linux-musl # x86-64
|
||||||
|
$ rustup target add aarch64-unknown-linux-musl # AArch64
|
||||||
```
|
```
|
||||||
|
|
||||||
## Clone and build
|
## Clone and build
|
||||||
@@ -46,7 +49,8 @@ $ cargo build --release
|
|||||||
$ sudo setcap cap_net_admin+ep ./target/release/cloud-hypervisor
|
$ sudo setcap cap_net_admin+ep ./target/release/cloud-hypervisor
|
||||||
|
|
||||||
# If you want to build statically linked binary
|
# If you want to build statically linked binary
|
||||||
$ cargo build --release --target=x86_64-unknown-linux-musl --all
|
$ cargo build --release --target=x86_64-unknown-linux-musl --all # x86-64
|
||||||
|
$ cargo build --release --target=aarch64-unknown-linux-musl --all # AArch64
|
||||||
$ popd
|
$ popd
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ Ubuntu distributions.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
apt update
|
apt update
|
||||||
apt install fio iperf iperf3 socat stress
|
apt install fio iperf iperf3 socat stress cpuid tpm2-tools
|
||||||
```
|
```
|
||||||
|
|
||||||
### Remove counterproductive packages
|
### Remove counterproductive packages
|
||||||
@@ -158,3 +158,172 @@ as we might need to update the direct kernel boot command line, replacing
|
|||||||
`/dev/vda1` with the appropriate partition number.
|
`/dev/vda1` with the appropriate partition number.
|
||||||
|
|
||||||
Update all references to the previous image name to the new one.
|
Update all references to the previous image name to the new one.
|
||||||
|
|
||||||
|
## NVIDIA image for VFIO baremetal CI
|
||||||
|
|
||||||
|
Here we are going to describe how to create a cloud image that contains the
|
||||||
|
necessary NVIDIA drivers for our VFIO baremetal CI.
|
||||||
|
|
||||||
|
### Download base image
|
||||||
|
|
||||||
|
We usually start from one of the custom cloud image we have previously created
|
||||||
|
but we can use a stock cloud image as well.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wget https://cloud-hypervisor.azureedge.net/jammy-server-cloudimg-amd64-custom-20230119-0.raw
|
||||||
|
mv jammy-server-cloudimg-amd64-custom-20230119-0.raw jammy-server-cloudimg-amd64-nvidia.raw
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extend the image size
|
||||||
|
|
||||||
|
The NVIDIA drivers consume lots of space, which is why we must resize the image
|
||||||
|
before we proceed any further.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
qemu-img resize jammy-server-cloudimg-amd64-nvidia.raw 5G
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resize the partition
|
||||||
|
|
||||||
|
We use `parted` for fixing the GPT after the image was resized, as well as for
|
||||||
|
resizing the `Linux` partition.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo parted jammy-server-cloudimg-amd64-nvidia.raw
|
||||||
|
|
||||||
|
(parted) print
|
||||||
|
Warning: Not all of the space available to jammy-server-cloudimg-amd64-nvidia.raw
|
||||||
|
appears to be used, you can fix the GPT to use all of the space (an extra 5873664
|
||||||
|
blocks) or continue with the current setting?
|
||||||
|
Fix/Ignore? Fix
|
||||||
|
Model: (file)
|
||||||
|
Disk jammy-server-cloudimg-amd64-nvidia.raw: 5369MB
|
||||||
|
Sector size (logical/physical): 512B/512B
|
||||||
|
Partition Table: gpt
|
||||||
|
Disk Flags:
|
||||||
|
|
||||||
|
Number Start End Size File system Name Flags
|
||||||
|
14 1049kB 5243kB 4194kB bios_grub
|
||||||
|
15 5243kB 116MB 111MB fat32 boot, esp
|
||||||
|
1 116MB 2361MB 2245MB ext4
|
||||||
|
|
||||||
|
(parted) resizepart 1 5369MB
|
||||||
|
(parted) print
|
||||||
|
Model: (file)
|
||||||
|
Disk jammy-server-cloudimg-amd64-nvidia.raw: 5369MB
|
||||||
|
Sector size (logical/physical): 512B/512B
|
||||||
|
Partition Table: gpt
|
||||||
|
Disk Flags:
|
||||||
|
|
||||||
|
Number Start End Size File system Name Flags
|
||||||
|
14 1049kB 5243kB 4194kB bios_grub
|
||||||
|
15 5243kB 116MB 111MB fat32 boot, esp
|
||||||
|
1 116MB 5369MB 5252MB ext4
|
||||||
|
|
||||||
|
(parted) quit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create a macvtap interface
|
||||||
|
|
||||||
|
Rely on the following [documentation](macvtap-bridge.md) to set up a
|
||||||
|
macvtap interface to provide your VM with proper connectivity.
|
||||||
|
|
||||||
|
### Boot the image
|
||||||
|
|
||||||
|
It is particularly important to boot with a `cloud-init` disk attached to the
|
||||||
|
VM as it will automatically resize the Linux `ext4` filesystem based on the
|
||||||
|
partition that we have previously resized.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cloud-hypervisor \
|
||||||
|
--kernel hypervisor-fw \
|
||||||
|
--disk path=focal-server-cloudimg-amd64-nvidia.raw path=/tmp/ubuntu-cloudinit.img \
|
||||||
|
--cpus boot=4 \
|
||||||
|
--memory size=4G \
|
||||||
|
--net fd=3,mac=$mac 3<>$"$tapdevice"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bring up connectivity
|
||||||
|
|
||||||
|
If your network has a DHCP server, run the following from your VM
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo dhclient
|
||||||
|
```
|
||||||
|
|
||||||
|
But if that's not the case, let's give it an IP manually (the IP addresses
|
||||||
|
depend on your actual network) and set the DNS server IP address as well.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ip addr add 192.168.2.10/24 dev ens4
|
||||||
|
sudo ip link set up dev ens4
|
||||||
|
sudo ip route add default via 192.168.2.1
|
||||||
|
sudo resolvectl dns ens4 8.8.8.8
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Check connectivity and update the image
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install NVIDIA drivers
|
||||||
|
|
||||||
|
The following steps and commands are referenced from the
|
||||||
|
[NVIDIA official documentation](https://docs.nvidia.com/datacenter/tesla/tesla-installation-notes/index.html#ubuntu-lts)
|
||||||
|
about Tesla compute cards.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
distribution=$(. /etc/os-release;echo $ID$VERSION_ID | sed -e 's/\.//g')
|
||||||
|
wget https://developer.download.nvidia.com/compute/cuda/repos/$distribution/x86_64/cuda-keyring_1.0-1_all.deb
|
||||||
|
sudo dpkg -i cuda-keyring_1.0-1_all.deb
|
||||||
|
sudo apt-key del 7fa2af80
|
||||||
|
sudo apt update
|
||||||
|
sudo apt -y install cuda-drivers
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check the `nvidia-smi` tool
|
||||||
|
|
||||||
|
Quickly validate that you can find and run the `nvidia-smi` command from your
|
||||||
|
VM. At this point it should fail given no NVIDIA card has been passed through
|
||||||
|
the VM, therefore no NVIDIA driver is loaded.
|
||||||
|
|
||||||
|
### Workaround LA57 reboot issue
|
||||||
|
|
||||||
|
Add `reboot=a` to `GRUB_CMDLINE_LINUX` in `etc/default/grub` so that the VM
|
||||||
|
will be booted with the ACPI reboot type. This resolves a reboot issue when
|
||||||
|
running on 5-level paging systems.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo vim /etc/default/grub
|
||||||
|
sudo update-grub
|
||||||
|
sudo reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remove previous logins
|
||||||
|
|
||||||
|
Since our integration tests rely on past logins to count the number of reboots,
|
||||||
|
we must ensure to clear the list.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
>/var/log/lastlog
|
||||||
|
>/var/log/wtmp
|
||||||
|
>/var/log/btmp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clear history
|
||||||
|
|
||||||
|
```
|
||||||
|
history -c
|
||||||
|
rm /home/cloud/.bash_history
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reset cloud-init
|
||||||
|
|
||||||
|
This is mandatory as we want `cloud-init` provisioning to work again when a new
|
||||||
|
VM will be booted with this image.
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo cloud-init clean
|
||||||
|
```
|
||||||
@@ -36,7 +36,7 @@ Assuming parts of the guest software stack have been instrumented to use the
|
|||||||
`cloud-hypervisor` debug I/O port, we may want to gather the related logs.
|
`cloud-hypervisor` debug I/O port, we may want to gather the related logs.
|
||||||
|
|
||||||
To do so we need to start `cloud-hypervisor` with the right debug level
|
To do so we need to start `cloud-hypervisor` with the right debug level
|
||||||
(`-vvv`). It is also recommended to have it log into a dedicated file in order
|
(`-v -v -v`). It is also recommended to have it log into a dedicated file in order
|
||||||
to easily grep for the tracing logs (e.g.
|
to easily grep for the tracing logs (e.g.
|
||||||
`--log-file /tmp/cloud-hypervisor.log`):
|
`--log-file /tmp/cloud-hypervisor.log`):
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ to easily grep for the tracing logs (e.g.
|
|||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--rng \
|
--rng \
|
||||||
--log-file /tmp/ch-fw.log \
|
--log-file /tmp/ch-fw.log \
|
||||||
-vvv
|
-v -v -v
|
||||||
```
|
```
|
||||||
|
|
||||||
After booting the guest, we then have to grep for the debug I/O port traces in
|
After booting the guest, we then have to grep for the debug I/O port traces in
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ To enable debugging with GDB, build with the `guest_debug` feature enabled:
|
|||||||
cargo build --features guest_debug
|
cargo build --features guest_debug
|
||||||
```
|
```
|
||||||
|
|
||||||
To use the `--gdb` option, specify the Unix Domain Socket with `--path` that Cloud Hypervisor will use to communicate with the host's GDB:
|
To use the `--gdb` option, specify the Unix Domain Socket with `path` that Cloud Hypervisor will use to communicate with the host's GDB:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./cloud-hypervisor \
|
./cloud-hypervisor \
|
||||||
|
|||||||
41
docs/heap-profiling.md
Normal file
41
docs/heap-profiling.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Heap profiling
|
||||||
|
|
||||||
|
Cloud Hypervisor supports generating a profile using
|
||||||
|
[dhat](https://docs.rs/dhat/latest/dhat/) of the heap allocations made during
|
||||||
|
the runtime of the process.
|
||||||
|
|
||||||
|
## Building a suitable binary
|
||||||
|
|
||||||
|
This adds the symbol information to the release binary but does not otherwise
|
||||||
|
affect the performance.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cargo build --profile profiling --features "dhat-heap"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generating output
|
||||||
|
|
||||||
|
Cloud Hypervisor can then be run as usual. However it is necessary to run with `--seccomp false` as the profiling requires extra syscalls.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ target/profiling/cloud-hypervisor \
|
||||||
|
--kernel ~/src/linux/vmlinux \
|
||||||
|
--pmem file=~/workloads/focal.raw \
|
||||||
|
--cpus boot=1 --memory size=1G \
|
||||||
|
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
|
||||||
|
--serial tty --console off \
|
||||||
|
--api-socket /tmp/api1 \
|
||||||
|
--seccomp false
|
||||||
|
```
|
||||||
|
|
||||||
|
When the VMM exits a message like the following will be shown:
|
||||||
|
|
||||||
|
```
|
||||||
|
dhat: Total: 384,582 bytes in 3,512 blocks
|
||||||
|
dhat: At t-gmax: 133,885 bytes in 379 blocks
|
||||||
|
dhat: At t-end: 12,160 bytes in 20 blocks
|
||||||
|
dhat: The data has been saved to dhat-heap.json, and is viewable with dhat/dh_view.html
|
||||||
|
```
|
||||||
|
|
||||||
|
The JSON output can then be uploaded to [the dh_view tool](https://nnethercote.github.io/dh_view/dh_view.html) for analysis.
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@ Currently Cloud Hypervisor supports hot plugging of CPUs devices (x86 only), PCI
|
|||||||
|
|
||||||
## Kernel support
|
## Kernel support
|
||||||
|
|
||||||
For hotplug on Cloud Hypervisor ACPI GED support is needed. This can either be achieved by turning on `CONFIG_ACPI_REDUCED_HARDWARE_ONLY`
|
For hotplug on Cloud Hypervisor ACPI GED support is needed. This can either be achieved by turning on `CONFIG_ACPI_REDUCED_HARDWARE_ONLY`
|
||||||
or by using this kernel patch (available in 5.5-rc1 and later): https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/patch/drivers/acpi/Makefile?id=ac36d37e943635fc072e9d4f47e40a48fbcdb3f0
|
or by using this kernel patch (available in 5.5-rc1 and later): https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/patch/drivers/acpi/Makefile?id=ac36d37e943635fc072e9d4f47e40a48fbcdb3f0
|
||||||
|
|
||||||
## CPU Hot Plug
|
## CPU Hot Plug
|
||||||
@@ -27,16 +27,16 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
|||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask=" \
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
--rng \
|
--rng \
|
||||||
--api-socket=/tmp/ch-socket
|
--api-socket /tmp/ch-socket
|
||||||
$ popd
|
$ 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:
|
To ask the VMM to add additional vCPUs then use the resize API:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./ch-remote --api-socket=/tmp/ch-socket resize --cpus 8
|
./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:
|
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,7 +56,7 @@ After a reboot the added CPUs will remain.
|
|||||||
Removing CPUs works similarly by reducing the number in the "desired_vcpus" field of the reisze API. The CPUs will be automatically offlined inside the guest so there is no need to run any commands inside the guest:
|
Removing CPUs works similarly by reducing the number in the "desired_vcpus" field of the reisze API. The CPUs will be automatically offlined inside the guest so there is no need to run any commands inside the guest:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./ch-remote --api-socket=/tmp/ch-socket resize --cpus 2
|
./ch-remote --api-socket /tmp/ch-socket resize --cpus 2
|
||||||
```
|
```
|
||||||
|
|
||||||
As per adding CPUs to the guest, after a reboot the VM will be running with the reduced number of vCPUs.
|
As per adding CPUs to the guest, after a reboot the VM will be running with the reduced number of vCPUs.
|
||||||
@@ -85,7 +85,7 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
|||||||
--memory size=1024M,hotplug_size=8192M \
|
--memory size=1024M,hotplug_size=8192M \
|
||||||
--net "tap=,mac=,ip=,mask=" \
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
--rng \
|
--rng \
|
||||||
--api-socket=/tmp/ch-socket
|
--api-socket /tmp/ch-socket
|
||||||
$ popd
|
$ popd
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ root@ch-guest ~ # echo online | sudo tee /sys/devices/system/memory/auto_online_
|
|||||||
To ask the VMM to expand the RAM for the VM:
|
To ask the VMM to expand the RAM for the VM:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./ch-remote --api-socket=/tmp/ch-socket resize --memory 3G
|
./ch-remote --api-socket /tmp/ch-socket resize --memory 3G
|
||||||
```
|
```
|
||||||
|
|
||||||
The new memory is now available to use inside the VM:
|
The new memory is now available to use inside the VM:
|
||||||
@@ -134,14 +134,14 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
|||||||
--disk path=focal-server-cloudimg-amd64.raw \
|
--disk path=focal-server-cloudimg-amd64.raw \
|
||||||
--memory size=1024M,hotplug_size=8192M,hotplug_method=virtio-mem \
|
--memory size=1024M,hotplug_size=8192M,hotplug_method=virtio-mem \
|
||||||
--net "tap=,mac=,ip=,mask=" \
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
--api-socket=/tmp/ch-socket
|
--api-socket /tmp/ch-socket
|
||||||
$ popd
|
$ popd
|
||||||
```
|
```
|
||||||
|
|
||||||
To ask the VMM to expand the RAM for the VM (request is in bytes):
|
To ask the VMM to expand the RAM for the VM (request is in bytes):
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./ch-remote --api-socket=/tmp/ch-socket resize --memory 3G
|
./ch-remote --api-socket /tmp/ch-socket resize --memory 3G
|
||||||
```
|
```
|
||||||
|
|
||||||
The new memory is now available to use inside the VM:
|
The new memory is now available to use inside the VM:
|
||||||
@@ -159,7 +159,7 @@ The same API can also be used to reduce the desired RAM for a VM. It is importan
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
Note: On AArch64 platform, PCI device hotplug can only be achieved using ACPI. Please refer to the [documentation](arm64.md#uefi-booting) for more information.
|
Note: On AArch64 platform, PCI device hotplug can only be achieved using ACPI. Please refer to the [documentation](uefi.md#building-uefi-firmware-for-aarch64) for more information.
|
||||||
|
|
||||||
To use PCI device hotplug start the VM with the HTTP server.
|
To use PCI device hotplug start the VM with the HTTP server.
|
||||||
|
|
||||||
@@ -172,17 +172,17 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
|||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask=" \
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
--api-socket=/tmp/ch-socket
|
--api-socket /tmp/ch-socket
|
||||||
```
|
```
|
||||||
|
|
||||||
Notice the addition of `--api-socket=/tmp/ch-socket`.
|
Notice the addition of `--api-socket /tmp/ch-socket`.
|
||||||
|
|
||||||
### Add VFIO Device
|
### Add VFIO Device
|
||||||
|
|
||||||
To ask the VMM to add additional VFIO device then use the `add-device` API.
|
To ask the VMM to add additional VFIO device then use the `add-device` API.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./ch-remote --api-socket=/tmp/ch-socket add-device path=/sys/bus/pci/devices/0000:01:00.0/
|
./ch-remote --api-socket /tmp/ch-socket add-device path=/sys/bus/pci/devices/0000:01:00.0/
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add Disk Device
|
### Add Disk Device
|
||||||
@@ -190,7 +190,7 @@ To ask the VMM to add additional VFIO device then use the `add-device` API.
|
|||||||
To ask the VMM to add additional disk device then use the `add-disk` API.
|
To ask the VMM to add additional disk device then use the `add-disk` API.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./ch-remote --api-socket=/tmp/ch-socket add-disk path=/foo/bar/cloud.img
|
./ch-remote --api-socket /tmp/ch-socket add-disk path=/foo/bar/cloud.img
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add Fs Device
|
### Add Fs Device
|
||||||
@@ -198,7 +198,7 @@ To ask the VMM to add additional disk device then use the `add-disk` API.
|
|||||||
To ask the VMM to add additional fs device then use the `add-fs` API.
|
To ask the VMM to add additional fs device then use the `add-fs` API.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./ch-remote --api-socket=/tmp/ch-socket add-fs tag=myfs,socket=/foo/bar/virtiofs.sock
|
./ch-remote --api-socket /tmp/ch-socket add-fs tag=myfs,socket=/foo/bar/virtiofs.sock
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add Net Device
|
### Add Net Device
|
||||||
@@ -206,7 +206,7 @@ To ask the VMM to add additional fs device then use the `add-fs` API.
|
|||||||
To ask the VMM to add additional network device then use the `add-net` API.
|
To ask the VMM to add additional network device then use the `add-net` API.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./ch-remote --api-socket=/tmp/ch-socket add-net tap=chtap0
|
./ch-remote --api-socket /tmp/ch-socket add-net tap=chtap0
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add Pmem Device
|
### Add Pmem Device
|
||||||
@@ -214,7 +214,7 @@ To ask the VMM to add additional network device then use the `add-net` API.
|
|||||||
To ask the VMM to add additional PMEM device then use the `add-pmem` API.
|
To ask the VMM to add additional PMEM device then use the `add-pmem` API.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./ch-remote --api-socket=/tmp/ch-socket add-pmem file=/foo/bar.cloud.img
|
./ch-remote --api-socket /tmp/ch-socket add-pmem file=/foo/bar.cloud.img
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add Vsock Device
|
### Add Vsock Device
|
||||||
@@ -222,7 +222,7 @@ To ask the VMM to add additional PMEM device then use the `add-pmem` API.
|
|||||||
To ask the VMM to add additional vsock device then use the `add-vsock` API.
|
To ask the VMM to add additional vsock device then use the `add-vsock` API.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./ch-remote --api-socket=/tmp/ch-socket add-vsock cid=3,socket=/foo/bar/vsock.sock
|
./ch-remote --api-socket /tmp/ch-socket add-vsock cid=3,socket=/foo/bar/vsock.sock
|
||||||
```
|
```
|
||||||
|
|
||||||
### Common Across All PCI Devices
|
### Common Across All PCI Devices
|
||||||
@@ -244,7 +244,7 @@ After a reboot the added PCI device will remain.
|
|||||||
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.
|
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
|
```shell
|
||||||
./ch-remote --api-socket=/tmp/ch-socket remove-device _disk0
|
./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.
|
As per adding a PCI device to the guest, after a reboot the VM will be running without the removed PCI device.
|
||||||
|
|||||||
@@ -2,49 +2,67 @@
|
|||||||
|
|
||||||
Intel® Trust Domain Extensions (Intel® TDX) is an Intel technology designed to
|
Intel® Trust Domain Extensions (Intel® TDX) is an Intel technology designed to
|
||||||
isolate virtual machines from the VMM, hypervisor and any other software on the
|
isolate virtual machines from the VMM, hypervisor and any other software on the
|
||||||
host platform.
|
host platform. Here are some useful links:
|
||||||
|
|
||||||
For more information about TDX technical aspects, design and specification
|
* [TDX Homepage](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-trust-domain-extensions.html):
|
||||||
please refer to the
|
more information about TDX technical aspects, design and specification
|
||||||
[TDX Homepage](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-trust-domain-extensions.html).
|
|
||||||
|
|
||||||
The required Linux changes for the host side can be found in the
|
* [KVM TDX tree](https://github.com/intel/tdx/tree/kvm): the required
|
||||||
[KVM TDX tree](https://github.com/intel/tdx/tree/kvm) while the changes for
|
Linux kernel changes for the host side
|
||||||
the guest side can be found in the [Guest TDX tree](https://github.com/intel/tdx/tree/guest).
|
|
||||||
|
|
||||||
The TDVF firmware can be found in the
|
* [Guest TDX tree](https://github.com/intel/tdx/tree/guest): the Linux
|
||||||
[EDK2 staging project](https://github.com/tianocore/edk2-staging/tree/TDVF).
|
kernel changes for the guest side
|
||||||
|
|
||||||
The TDShim firmware can be found in the
|
* [EDK2 project](https://github.com/tianocore/edk2): the TDVF firmware
|
||||||
[Confidential Containers project](https://github.com/confidential-containers/td-shim).
|
|
||||||
|
* [Confidential Containers project](https://github.com/confidential-containers/td-shim):
|
||||||
|
the TDShim firmware
|
||||||
|
|
||||||
|
* [TDX Tools](https://github.com/intel/tdx-tools): a collection of tools
|
||||||
|
and scripts to setup TDX environment for testing purpose (such as
|
||||||
|
installing required packages on the host, creating guest images, and
|
||||||
|
building the custom Linux kernel for TDX host and guest)
|
||||||
|
|
||||||
## Cloud Hypervisor support
|
## Cloud Hypervisor support
|
||||||
|
|
||||||
First, you must be running on a machine with TDX enabled in hardware, and
|
It is required to use a machine with TDX enabled in hardware and
|
||||||
with the host OS compiled from the [KVM TDX tree](https://github.com/intel/tdx/tree/kvm).
|
with the host OS compiled from the [KVM TDX tree](https://github.com/intel/tdx/tree/kvm).
|
||||||
|
The host environment can also be setup with the [TDX Tools](https://github.com/intel/tdx-tools).
|
||||||
|
|
||||||
Cloud Hypervisor can run TDX VM (Trust Domain) by loading a TD firmware,
|
Cloud Hypervisor can run TDX VM (Trust Domain) by loading a TD firmware ([TDVF](https://github.com/tianocore/edk2)),
|
||||||
which will then load the guest kernel from the image. The image must be custom
|
which will then load the guest kernel from the image. The image must be custom
|
||||||
as it must include a kernel built from the [Guest TDX tree](https://github.com/intel/tdx/tree/guest).
|
as it must include a kernel built from the [Guest TDX tree](https://github.com/intel/tdx/tree/guest).
|
||||||
|
Cloud Hypervisor can also boot a TDX VM with direct kernel boot using [TDshim](https://github.com/confidential-containers/td-shim).
|
||||||
|
The custom Linux kernel for the guest can be built with the [TDX Tools](https://github.com/intel/tdx-tools).
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> The latest version of custom host and guest kernel being tested is
|
||||||
|
> from [TDX Tools - 2023ww01](https://github.com/intel/tdx-tools/commits/2023ww01).
|
||||||
|
|
||||||
### TDVF
|
### TDVF
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> The latest version of TDVF being tested is [_13b9773_](https://github.com/tianocore/edk2/commit/13b97736c876919b9786055829caaa4fa46984b7).
|
||||||
|
|
||||||
The firmware can be built as follows:
|
The firmware can be built as follows:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/tianocore/edk2-staging.git
|
sudo apt-get update
|
||||||
cd edk2-staging
|
sudo apt-get install uuid-dev nasm iasl build-essential python3-distutils git
|
||||||
git checkout origin/TDVF
|
|
||||||
git submodule update --init --recursive
|
git clone https://github.com/tianocore/edk2.git
|
||||||
make -C BaseTools
|
cd edk2
|
||||||
|
git checkout 13b97736c876919b9786055829caaa4fa46984b7
|
||||||
source ./edksetup.sh
|
source ./edksetup.sh
|
||||||
build -p OvmfPkg/OvmfCh.dsc -a X64 -t GCC5 -b RELEASE
|
git submodule update --init --recursive
|
||||||
|
make -C BaseTools -j `nproc`
|
||||||
|
build -p OvmfPkg/IntelTdx/IntelTdxX64.dsc -a X64 -t GCC5 -b RELEASE
|
||||||
```
|
```
|
||||||
|
|
||||||
If debug logs are needed, here is the alternative command:
|
If debug logs are needed, here is the alternative command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
build -p OvmfPkg/OvmfCh.dsc -a X64 -t GCC5 -D DEBUG_ON_SERIAL_PORT=TRUE
|
build -p OvmfPkg/IntelTdx/IntelTdxX64.dsc -a X64 -t GCC5 -D DEBUG_ON_SERIAL_PORT=TRUE
|
||||||
```
|
```
|
||||||
|
|
||||||
On the Cloud Hypervisor side, all you need is to build the project with the
|
On the Cloud Hypervisor side, all you need is to build the project with the
|
||||||
@@ -62,7 +80,7 @@ meaning it will be printing guest kernel logs to the `virtio-console` device.
|
|||||||
```bash
|
```bash
|
||||||
./cloud-hypervisor \
|
./cloud-hypervisor \
|
||||||
--platform tdx=on
|
--platform tdx=on
|
||||||
--firmware edk2-staging/Build/OvmfCh/RELEASE_GCC5/FV/OVMF.fd \
|
--firmware edk2/Build/IntelTdx/RELEASE_GCC5/FV/OVMF.fd \
|
||||||
--cpus boot=1 \
|
--cpus boot=1 \
|
||||||
--memory size=1G \
|
--memory size=1G \
|
||||||
--disk path=tdx_guest_img
|
--disk path=tdx_guest_img
|
||||||
@@ -74,7 +92,7 @@ firmware:
|
|||||||
```bash
|
```bash
|
||||||
./cloud-hypervisor \
|
./cloud-hypervisor \
|
||||||
--platform tdx=on
|
--platform tdx=on
|
||||||
--firmware edk2-staging/Build/OvmfCh/DEBUG_GCC5/FV/OVMF.fd \
|
--firmware edk2/Build/IntelTdx/DEBUG_GCC5/FV/OVMF.fd \
|
||||||
--cpus boot=1 \
|
--cpus boot=1 \
|
||||||
--memory size=1G \
|
--memory size=1G \
|
||||||
--disk path=tdx_guest_img \
|
--disk path=tdx_guest_img \
|
||||||
@@ -84,21 +102,60 @@ firmware:
|
|||||||
|
|
||||||
### TDShim
|
### TDShim
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> The latest version of TDShim being tested is [_66bb334_](https://github.com/confidential-containers/td-shim/tree/66bb33451befbf1291abe3cfea7ee9e99d922b0d).
|
||||||
|
|
||||||
This is a lightweight version of the TDVF, written in Rust and designed for
|
This is a lightweight version of the TDVF, written in Rust and designed for
|
||||||
direct kernel boot, which is useful for containers use cases.
|
direct kernel boot, which is useful for containers use cases.
|
||||||
|
|
||||||
You can find the instructions for building the firmware directly from the
|
To build TDShim from source, it is required to install `Rust`, `NASM`,
|
||||||
project [documentation](https://github.com/confidential-containers/td-shim/tree/staging#how-to-build).
|
and `LLVM` first. The TDshim can be build as follows:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/confidential-containers/td-shim
|
||||||
|
cd td-shim
|
||||||
|
git checkout 66bb33451befbf1291abe3cfea7ee9e99d922b0d
|
||||||
|
cargo install cargo-xbuild
|
||||||
|
export CC=clang
|
||||||
|
export AR=llvm-ar
|
||||||
|
export CC_x86_64_unknown_none=clang
|
||||||
|
export AR_x86_64_unknown_none=llvm-ar
|
||||||
|
git submodule update --init --recursive
|
||||||
|
./sh_script/preparation.sh
|
||||||
|
cargo xbuild -p td-shim --target x86_64-unknown-none --release --features=main,tdx
|
||||||
|
cargo run -p td-shim-tools --bin td-shim-ld --features=linker -- target/x86_64-unknown-none/release/ResetVector.bin target/x86_64-unknown-none/release/td-shim -o target/release/final.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
If debug logs from the TDShim is needed, here are the alternative
|
||||||
|
commands:
|
||||||
|
```bash
|
||||||
|
cargo xbuild -p td-shim --target x86_64-unknown-none --features=main,tdx
|
||||||
|
cargo run -p td-shim-tools --bin td-shim-ld --features=linker -- target/x86_64-unknown-none/debug/ResetVector.bin target/x86_64-unknown-none/debug/td-shim -o target/debug/final.bin
|
||||||
|
```
|
||||||
|
|
||||||
And run a TDX VM by providing the firmware previously built, along with a guest
|
And run a TDX VM by providing the firmware previously built, along with a guest
|
||||||
kernel built from the [Guest TDX tree](https://github.com/intel/tdx/tree/guest).
|
kernel built from the [Guest TDX tree](https://github.com/intel/tdx/tree/guest)
|
||||||
|
or the [TDX Tools](https://github.com/intel/tdx-tools).
|
||||||
The appropriate kernel boot options must be provided through the `--cmdline`
|
The appropriate kernel boot options must be provided through the `--cmdline`
|
||||||
option as well.
|
option as well.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./cloud-hypervisor \
|
./cloud-hypervisor \
|
||||||
--platform tdx=on
|
--platform tdx=on
|
||||||
--firmware tdshim \
|
--firmware td-shim/target/release/final.bin \
|
||||||
|
--kernel bzImage \
|
||||||
|
--cmdline "root=/dev/vda3 console=hvc0 rw"
|
||||||
|
--cpus boot=1 \
|
||||||
|
--memory size=1G \
|
||||||
|
--disk path=tdx_guest_img
|
||||||
|
```
|
||||||
|
|
||||||
|
And here is the alternative command when looking for debug logs from the
|
||||||
|
TDShim:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cloud-hypervisor \
|
||||||
|
--platform tdx=on
|
||||||
|
--firmware td-shim/target/debug/final.bin \
|
||||||
--kernel bzImage \
|
--kernel bzImage \
|
||||||
--cmdline "root=/dev/vda3 console=hvc0 rw"
|
--cmdline "root=/dev/vda3 console=hvc0 rw"
|
||||||
--cpus boot=1 \
|
--cpus boot=1 \
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ e.g.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
./cloud-hypervisor \
|
./cloud-hypervisor \
|
||||||
--api-socket=/tmp/api \
|
--api-socket /tmp/api \
|
||||||
--cpus boot=1 \
|
--cpus boot=1 \
|
||||||
--memory size=4G,hugepages=on \
|
--memory size=4G,hugepages=on \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw \
|
--disk path=focal-server-cloudimg-amd64.raw \
|
||||||
@@ -260,7 +260,7 @@ requiring the IOMMU then may be hotplugged:
|
|||||||
e.g.
|
e.g.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./ch-remote --api-socket=/tmp/api add-device path=/sys/bus/pci/devices/0000:00:04.0,iommu=on,pci_segment=1
|
./ch-remote --api-socket /tmp/api add-device path=/sys/bus/pci/devices/0000:00:04.0,iommu=on,pci_segment=1
|
||||||
```
|
```
|
||||||
|
|
||||||
Devices that cannot be placed behind an IOMMU (e.g. lacking an `iommu=` option)
|
Devices that cannot be placed behind an IOMMU (e.g. lacking an `iommu=` option)
|
||||||
|
|||||||
@@ -16,22 +16,22 @@ $ target/release/cloud-hypervisor
|
|||||||
--disk path=~/workloads/focal.raw \
|
--disk path=~/workloads/focal.raw \
|
||||||
--cpus boot=1 --memory size=1G,shared=on \
|
--cpus boot=1 --memory size=1G,shared=on \
|
||||||
--cmdline "root=/dev/vda1 console=ttyS0" \
|
--cmdline "root=/dev/vda1 console=ttyS0" \
|
||||||
--serial tty --console off --api-socket=/tmp/api1
|
--serial tty --console off --api-socket /tmp/api1
|
||||||
```
|
```
|
||||||
|
|
||||||
Launch the destination VM from the same directory (on the host machine):
|
Launch the destination VM from the same directory (on the host machine):
|
||||||
```bash
|
```bash
|
||||||
$ target/release/cloud-hypervisor --api-socket=/tmp/api2
|
$ target/release/cloud-hypervisor --api-socket /tmp/api2
|
||||||
```
|
```
|
||||||
|
|
||||||
Get ready for receiving migration for the destination VM (on the host machine):
|
Get ready for receiving migration for the destination VM (on the host machine):
|
||||||
```bash
|
```bash
|
||||||
$ target/release/ch-remote --api-socket=/tmp/api2 receive-migration unix:/tmp/sock
|
$ 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):
|
Start to send migration for the source VM (on the host machine):
|
||||||
```bash
|
```bash
|
||||||
$ target/release/ch-remote --api-socket=/tmp/api1 send-migration --local unix:/tmp/sock
|
$ target/release/ch-remote --api-socket /tmp/api1 send-migration --local unix:/tmp/sock
|
||||||
```
|
```
|
||||||
|
|
||||||
When the above commands completed, the source VM should be successfully
|
When the above commands completed, the source VM should be successfully
|
||||||
@@ -51,7 +51,7 @@ $ sudo /target/release/cloud-hypervisor \
|
|||||||
--cpus boot=1 --memory size=512M \
|
--cpus boot=1 --memory size=512M \
|
||||||
--kernel vmlinux \
|
--kernel vmlinux \
|
||||||
--cmdline "root=/dev/vda1 console=ttyS0" \
|
--cmdline "root=/dev/vda1 console=ttyS0" \
|
||||||
--disk path=focal-1.raw path=focal-nested.raw path=tmp.img\
|
--disk path=focal-1.raw path=focal-nested.raw --disk path=tmp.img\
|
||||||
--net ip=192.168.101.1
|
--net ip=192.168.101.1
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ $ sudo /target/release/cloud-hypervisor \
|
|||||||
--cpus boot=1 --memory size=512M \
|
--cpus boot=1 --memory size=512M \
|
||||||
--kernel vmlinux \
|
--kernel vmlinux \
|
||||||
--cmdline "root=/dev/vda1 console=ttyS0" \
|
--cmdline "root=/dev/vda1 console=ttyS0" \
|
||||||
--disk path=focal-2.raw path=focal-nested.raw path=tmp.img\
|
--disk path=focal-2.raw path=focal-nested.raw --disk path=tmp.img\
|
||||||
--net ip=192.168.102.1
|
--net ip=192.168.102.1
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -74,8 +74,8 @@ vm-1:~$ sudo ./cloud-hypervisor \
|
|||||||
--memory size=128M \
|
--memory size=128M \
|
||||||
--kernel vmlinux \
|
--kernel vmlinux \
|
||||||
--cmdline "console=ttyS0 root=/dev/vda1" \
|
--cmdline "console=ttyS0 root=/dev/vda1" \
|
||||||
--disk path=/dev/vdb path=/dev/vdc \
|
--disk path=/dev/vdb --disk path=/dev/vdc \
|
||||||
--api-socket=/tmp/api1 \
|
--api-socket /tmp/api1 \
|
||||||
--net ip=192.168.100.1
|
--net ip=192.168.100.1
|
||||||
vm-1:~$ # setup the guest network if needed
|
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 addr add 192.168.101.2/24 dev ens4
|
||||||
@@ -108,7 +108,7 @@ echo "tmp = $tmp"
|
|||||||
|
|
||||||
Launch the nested destination VM (inside the guest OS of the VM 2):
|
Launch the nested destination VM (inside the guest OS of the VM 2):
|
||||||
```bash
|
```bash
|
||||||
vm-2:~$ sudo ./cloud-hypervisor --api-socket=/tmp/api2
|
vm-2:~$ sudo ./cloud-hypervisor --api-socket /tmp/api2
|
||||||
vm-2:~$ # setup the guest network with the following commands if needed
|
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 addr add 192.168.102.2/24 dev ens4
|
||||||
vm-2:~$ sudo ip link set up dev ens4
|
vm-2:~$ sudo ip link set up dev ens4
|
||||||
@@ -122,7 +122,7 @@ vm-2:~$ ping 192.168.101.2 # This should succeed
|
|||||||
Get ready for receiving migration for the nested destination VM (inside
|
Get ready for receiving migration for the nested destination VM (inside
|
||||||
the guest OS of the VM 2):
|
the guest OS of the VM 2):
|
||||||
```bash
|
```bash
|
||||||
vm-2:~$ sudo ./ch-remote --api-socket=/tmp/api2 receive-migration unix:/tmp/sock2
|
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
|
vm-2:~$ sudo socat TCP-LISTEN:6000,reuseaddr UNIX-CLIENT:/tmp/sock2
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ Start to send migration for the nested source VM (inside the guest OS of
|
|||||||
the VM 1):
|
the VM 1):
|
||||||
```bash
|
```bash
|
||||||
vm-1:~$ sudo socat UNIX-LISTEN:/tmp/sock1,reuseaddr TCP:192.168.102.2:6000
|
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
|
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
|
When the above commands completed, the source VM should be successfully
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ The target audience of this document is both:
|
|||||||
|
|
||||||
## Control
|
## Control
|
||||||
|
|
||||||
The number of `-v` parameters passed to the `cloud-hypervisor` binary will determine the log level. Currenly the default is log messages up to `WARN:` (`warn!`) are included by default. The `--log-file` allows the log to be sent to a location other than `stderr`.
|
The number of `-v` parameters passed to the `cloud-hypervisor` binary will determine the log level. Currently the default is log messages up to `WARN:` (`warn!`) are included by default. The `--log-file` allows the log to be sent to a location other than `stderr`.
|
||||||
|
|
||||||
## Levels
|
## Levels
|
||||||
|
|
||||||
### `error!()`
|
### `error!()`
|
||||||
|
|
||||||
For immediate, unrecoverable errors where it does not make sense for the execution to continue as the behaviour of the VM is considerablely impacted.
|
For immediate, unrecoverable errors where it does not make sense for the execution to continue as the behaviour of the VM is considerably impacted.
|
||||||
|
|
||||||
Cloud Hypervisor should exit shortly after reporting this error (with a non-zero exit code). Generally this should be used during initial construction of the VM state before the virtual CPUs have begun running code.
|
Cloud Hypervisor should exit shortly after reporting this error (with a non-zero exit code). Generally this should be used during initial construction of the VM state before the virtual CPUs have begun running code.
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ Users should react to this error by checking their initial VM configuration.
|
|||||||
|
|
||||||
### `warn!()`
|
### `warn!()`
|
||||||
|
|
||||||
A serious problem has occured but the execution of the VM can continue although some functionality might be impacted.
|
A serious problem has occurred but the execution of the VM can continue although some functionality might be impacted.
|
||||||
|
|
||||||
A typical example of where this level of message should be generated is during an API call request that cannot be fulfilled.
|
A typical example of where this level of message should be generated is during an API call request that cannot be fulfilled.
|
||||||
|
|
||||||
@@ -38,6 +38,6 @@ This level is for the benefit of developers. It should be used for sporadic and
|
|||||||
|
|
||||||
### `debug!()`
|
### `debug!()`
|
||||||
|
|
||||||
Use `-vv` to enable.
|
Use `-v -v` to enable.
|
||||||
|
|
||||||
For the most verbose of logging messages. It is acceptable to "spam" the log with repeated invocations of the same message. This level of logging would be combined with `--log-file`.
|
For the most verbose of logging messages. It is acceptable to "spam" the log with repeated invocations of the same message. This level of logging would be combined with `--log-file`.
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ struct MemoryZoneConfig {
|
|||||||
--memory-zone <memory-zone> User defined memory zone parameters "size=<guest_memory_region_size>,file=<backing_file>,shared=on|off,hugepages=on|off,hugepage_size=<hugepage_size>,host_numa_node=<node_id>,id=<zone_identifier>,hotplug_size=<hotpluggable_memory_size>,hotplugged_size=<hotplugged_memory_size>,prefault=on|off"
|
--memory-zone <memory-zone> User defined memory zone parameters "size=<guest_memory_region_size>,file=<backing_file>,shared=on|off,hugepages=on|off,hugepage_size=<hugepage_size>,host_numa_node=<node_id>,id=<zone_identifier>,hotplug_size=<hotpluggable_memory_size>,hotplugged_size=<hotplugged_memory_size>,prefault=on|off"
|
||||||
```
|
```
|
||||||
|
|
||||||
This parameter expects one or more occurences, allowing for a list of memory
|
This parameter expects one or more occurrences, allowing for a list of memory
|
||||||
zones to be defined. It must be used with `--memory size=0`, clearly indicating
|
zones to be defined. It must be used with `--memory size=0`, clearly indicating
|
||||||
that the memory will be described through advanced parameters.
|
that the memory will be described through advanced parameters.
|
||||||
|
|
||||||
@@ -266,11 +266,8 @@ _Example_
|
|||||||
|
|
||||||
### `file`
|
### `file`
|
||||||
|
|
||||||
Path to the file backing the memory zone. This can be either a file or a
|
Path to the file backing the memory zone. The file will be opened and used as
|
||||||
directory. In case of a file, it will be opened and used as the backing file
|
the backing file for the `mmap(2)` operation.
|
||||||
for the `mmap(2)` operation. In case of a directory, a temporary file with no
|
|
||||||
hard link on the filesystem will be created. This file will be used as the
|
|
||||||
backing file for the `mmap(2)` operation.
|
|
||||||
|
|
||||||
This option can be particularly useful when trying to back a part of the guest
|
This option can be particularly useful when trying to back a part of the guest
|
||||||
RAM with a well known file. In the context of the snapshot/restore feature, and
|
RAM with a well known file. In the context of the snapshot/restore feature, and
|
||||||
@@ -519,7 +516,7 @@ different distances, it can be described with the following example.
|
|||||||
_Example_
|
_Example_
|
||||||
|
|
||||||
```
|
```
|
||||||
--numa guest_numa_id=0,distances=[1@15,2@25] guest_numa_id=1,distances=[0@15,2@20] guest_numa_id=2,distances=[0@25,1@20]
|
--numa guest_numa_id=0,distances=[1@15,2@25] --numa guest_numa_id=1,distances=[0@15,2@20] guest_numa_id=2,distances=[0@25,1@20]
|
||||||
```
|
```
|
||||||
|
|
||||||
### `memory_zones`
|
### `memory_zones`
|
||||||
@@ -543,14 +540,14 @@ demarcate the list.
|
|||||||
|
|
||||||
Note that a memory zone must belong to a single NUMA node. The following
|
Note that a memory zone must belong to a single NUMA node. The following
|
||||||
configuration is incorrect, therefore not allowed:
|
configuration is incorrect, therefore not allowed:
|
||||||
`--numa guest_numa_id=0,memory_zones=mem0 guest_numa_id=1,memory_zones=mem0`
|
`--numa guest_numa_id=0,memory_zones=mem0 --numa guest_numa_id=1,memory_zones=mem0`
|
||||||
|
|
||||||
_Example_
|
_Example_
|
||||||
|
|
||||||
```
|
```
|
||||||
--memory size=0
|
--memory size=0
|
||||||
--memory-zone id=mem0,size=1G id=mem1,size=1G id=mem2,size=1G
|
--memory-zone id=mem0,size=1G id=mem1,size=1G --memory-zone id=mem2,size=1G
|
||||||
--numa guest_numa_id=0,memory_zones=[mem0,mem2] guest_numa_id=1,memory_zones=mem1
|
--numa guest_numa_id=0,memory_zones=[mem0,mem2] --numa guest_numa_id=1,memory_zones=mem1
|
||||||
```
|
```
|
||||||
|
|
||||||
### `sgx_epc_sections`
|
### `sgx_epc_sections`
|
||||||
@@ -570,7 +567,7 @@ _Example_
|
|||||||
|
|
||||||
```
|
```
|
||||||
--sgx-epc id=epc0,size=32M id=epc1,size=64M id=epc2,size=32M
|
--sgx-epc id=epc0,size=32M id=epc1,size=64M id=epc2,size=32M
|
||||||
--numa guest_numa_id=0,sgx_epc_sections=epc1 guest_numa_id=1,sgx_epc_sections=[epc0,epc2]
|
--numa guest_numa_id=0,sgx_epc_sections=epc1 --numa guest_numa_id=1,sgx_epc_sections=[epc0,epc2]
|
||||||
```
|
```
|
||||||
|
|
||||||
### PCI bus
|
### PCI bus
|
||||||
|
|||||||
@@ -4,20 +4,12 @@
|
|||||||
|
|
||||||
## Building a suitable binary
|
## Building a suitable binary
|
||||||
|
|
||||||
Modify the `Cargo.toml` file to add `debug = 1` to the `[profile.release]` block. It should look like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
[profile.release]
|
|
||||||
lto = true
|
|
||||||
debug = 1
|
|
||||||
```
|
|
||||||
|
|
||||||
This adds the symbol information to the release binary but does not otherwise affect the performance.
|
This adds the symbol information to the release binary but does not otherwise affect the performance.
|
||||||
|
|
||||||
The binary must also be built with frame pointers included so that the call graph can be captured by the profiler.
|
The binary must also be built with frame pointers included so that the call graph can be captured by the profiler.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cargo clean && RUSTFLAGS='-C force-frame-pointers=y' cargo build --release
|
$ cargo clean && RUSTFLAGS='-C force-frame-pointers=y' cargo build --profile profiling
|
||||||
```
|
```
|
||||||
|
|
||||||
## Profiling
|
## Profiling
|
||||||
@@ -27,13 +19,13 @@ $ cargo clean && RUSTFLAGS='-C force-frame-pointers=y' cargo build --release
|
|||||||
e.g.
|
e.g.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ perf record -g target/release/cloud-hypervisor \
|
$ perf record -g target/profiling/cloud-hypervisor \
|
||||||
--kernel ~/src/linux/vmlinux \
|
--kernel ~/src/linux/vmlinux \
|
||||||
--pmem file=~/workloads/focal.raw \
|
--pmem file=~/workloads/focal.raw \
|
||||||
--cpus boot=1 --memory size=1G \
|
--cpus boot=1 --memory size=1G \
|
||||||
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
|
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
|
||||||
--serial tty --console off \
|
--serial tty --console off \
|
||||||
--api-socket=/tmp/api1
|
--api-socket /tmp/api1
|
||||||
```
|
```
|
||||||
|
|
||||||
For analysing the samples:
|
For analysing the samples:
|
||||||
@@ -60,5 +52,5 @@ $ perf record --call-graph lbr --all-user --user-callchains -g target/release/cl
|
|||||||
--cpus boot=1 --memory size=1G \
|
--cpus boot=1 --memory size=1G \
|
||||||
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
|
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
|
||||||
--serial tty --console off \
|
--serial tty --console off \
|
||||||
--api-socket=/tmp/api1
|
--api-socket /tmp/api1
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ First thing, we must run a Cloud Hypervisor VM:
|
|||||||
At any point in time when the VM is running, one might choose to pause it:
|
At any point in time when the VM is running, one might choose to pause it:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./ch-remote --api-socket=/tmp/cloud-hypervisor.sock pause
|
./ch-remote --api-socket /tmp/cloud-hypervisor.sock pause
|
||||||
```
|
```
|
||||||
|
|
||||||
Once paused, the VM can be safely snapshot into the specified directory and
|
Once paused, the VM can be safely snapshot into the specified directory and
|
||||||
using the following command:
|
using the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./ch-remote --api-socket=/tmp/cloud-hypervisor.sock snapshot file:///home/foo/snapshot
|
./ch-remote --api-socket /tmp/cloud-hypervisor.sock snapshot file:///home/foo/snapshot
|
||||||
```
|
```
|
||||||
|
|
||||||
Given the directory was present on the system, the snapshot will succeed and
|
Given the directory was present on the system, the snapshot will succeed and
|
||||||
@@ -79,7 +79,7 @@ Or using two different commands from two terminals:
|
|||||||
./cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock
|
./cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock
|
||||||
|
|
||||||
# Second terminal
|
# Second terminal
|
||||||
./ch-remote --api-socket=/tmp/cloud-hypervisor.sock restore source_url=file:///home/foo/snapshot
|
./ch-remote --api-socket /tmp/cloud-hypervisor.sock restore source_url=file:///home/foo/snapshot
|
||||||
```
|
```
|
||||||
|
|
||||||
Remember the VM is restored in a `paused` state, which was the VM's state when
|
Remember the VM is restored in a `paused` state, which was the VM's state when
|
||||||
@@ -87,7 +87,7 @@ it was snapshot. For this reason, one must explicitly `resume` the VM before to
|
|||||||
start using it.
|
start using it.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./ch-remote --api-socket=/tmp/cloud-hypervisor.sock resume
|
./ch-remote --api-socket /tmp/cloud-hypervisor.sock resume
|
||||||
```
|
```
|
||||||
|
|
||||||
At this point, the VM is fully restored and is identical to the VM which was
|
At this point, the VM is fully restored and is identical to the VM which was
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ process should be started to listen at the input socket. Below is an
|
|||||||
example invocation of swtpm process.
|
example invocation of swtpm process.
|
||||||
|
|
||||||
```
|
```
|
||||||
swtpm socket --tmpstate dir=/var/run/swtpm \
|
swtpm socket --tpmstate dir=/var/run/swtpm \
|
||||||
--ctrl type=unixio,path="/var/run/swtpm.socket" \
|
--ctrl type=unixio,path="/var/run/swtpm.socket" \
|
||||||
--flags startup-clear \
|
--flags startup-clear \
|
||||||
--tpm2
|
--tpm2
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Tracing
|
# Tracing
|
||||||
|
|
||||||
Cloud Hypervisor has a basic tracing infrastucture, particularly focussed on
|
Cloud Hypervisor has a basic tracing infrastructure, particularly focussed on
|
||||||
the tracing of the initial VM setup.
|
the tracing of the initial VM setup.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|||||||
37
docs/uefi.md
37
docs/uefi.md
@@ -2,17 +2,19 @@
|
|||||||
|
|
||||||
Cloud Hypervisor supports UEFI boot through the utilization of the EDK II based UEFI firmware.
|
Cloud Hypervisor supports UEFI boot through the utilization of the EDK II based UEFI firmware.
|
||||||
|
|
||||||
## Building UEFI Firmware
|
## Building UEFI Firmware for x86-64
|
||||||
|
|
||||||
To avoid any unnecessary issues, it is recommended to use Ubuntu 18.04 and its default toolset. Any other compatible Linux distribution is otherwise suitable, however it is suggested to use a temporary Docker container with Ubuntu 18.04 for a quick build on an existing Linux machine.
|
To avoid any unnecessary issues, it is recommended to use Ubuntu 18.04 and its default toolset. Any other compatible Linux distribution is otherwise suitable, however it is suggested to use a temporary Docker container with Ubuntu 18.04 for a quick build on an existing Linux machine.
|
||||||
|
|
||||||
|
Please note that nasm-2.15 is required for the build to succeed.
|
||||||
|
|
||||||
The commands below will compile an OVMF firmware suitable for Cloud Hypervisor.
|
The commands below will compile an OVMF firmware suitable for Cloud Hypervisor.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install uuid-dev nasm iasl build-essential python3-distutils git
|
sudo apt-get install uuid-dev nasm iasl build-essential python3-distutils git
|
||||||
|
|
||||||
git clone https://github.com/cloud-hypervisor/edk2 -b ch
|
git clone https://github.com/tianocore/edk2
|
||||||
cd edk2
|
cd edk2
|
||||||
. edksetup.sh
|
. edksetup.sh
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
@@ -27,11 +29,40 @@ build
|
|||||||
|
|
||||||
After the successful build, the resulting firmware binaries are available under `Build/CloudHvX64/DEBUG_GCC5/FV` underneath the edk2 checkout.
|
After the successful build, the resulting firmware binaries are available under `Build/CloudHvX64/DEBUG_GCC5/FV` underneath the edk2 checkout.
|
||||||
|
|
||||||
|
## Building UEFI Firmware for AArch64
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# On an AArch64 machine:
|
||||||
|
$ sudo apt-get update
|
||||||
|
$ sudo apt-get install uuid-dev nasm iasl build-essential python3-distutils git
|
||||||
|
$ git clone --depth 1 https://github.com/tianocore/edk2.git -b master
|
||||||
|
$ cd edk2
|
||||||
|
$ git submodule update --init
|
||||||
|
$ cd ..
|
||||||
|
$ git clone --depth 1 https://github.com/tianocore/edk2-platforms.git -b master
|
||||||
|
$ git clone --depth 1 https://github.com/acpica/acpica.git -b master
|
||||||
|
|
||||||
|
# Build tools
|
||||||
|
$ export PACKAGES_PATH="$PWD/edk2:$PWD/edk2-platforms"
|
||||||
|
$ export IASL_PREFIX="$PWD/acpica/generate/unix/bin/"
|
||||||
|
$ make -C acpica
|
||||||
|
$ cd edk2/
|
||||||
|
$ . edksetup.sh
|
||||||
|
$ cd ..
|
||||||
|
$ make -C edk2/BaseTools
|
||||||
|
|
||||||
|
# Build EDK2
|
||||||
|
$ build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtCloudHv.dsc -b RELEASE
|
||||||
|
```
|
||||||
|
|
||||||
|
If the build goes well, the EDK2 binary is available at
|
||||||
|
`edk2/Build/ArmVirtCloudHv-AARCH64/RELEASE_GCC5/FV/CLOUDHV_EFI.fd`.
|
||||||
|
|
||||||
## Using OVMF Binaries
|
## Using OVMF Binaries
|
||||||
|
|
||||||
Any UEFI capable image can be booted using the Cloud Hypervisor specific firmware. Windows guests under Cloud Hypervisor only support UEFI boot, therefore OVMF is mandatory there.
|
Any UEFI capable image can be booted using the Cloud Hypervisor specific firmware. Windows guests under Cloud Hypervisor only support UEFI boot, therefore OVMF is mandatory there.
|
||||||
|
|
||||||
To make Cloud Hypervisor use UEFI boot, pass the `CLOUDHV.fd` file path as an argument to the `--kernel` option. The firmware file will be opened in read only mode.
|
To make Cloud Hypervisor use UEFI boot, pass the `CLOUDHV.fd` (for x86-64) / `CLOUDHV_EFI.fd` (for AArch64) file path as an argument to the `--kernel` option. The firmware file will be opened in read only mode.
|
||||||
|
|
||||||
# Links
|
# Links
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ VMs run in client mode. They connect to the socket created by the `dpdkvhostuser
|
|||||||
--memory size=1024M,hugepages=on,shared=true \
|
--memory size=1024M,hugepages=on,shared=true \
|
||||||
--kernel linux/arch/x86/boot/compressed/vmlinux.bin \
|
--kernel linux/arch/x86/boot/compressed/vmlinux.bin \
|
||||||
--cmdline "console=ttyS0 root=/dev/vda1 rw iommu=off" \
|
--cmdline "console=ttyS0 root=/dev/vda1 rw iommu=off" \
|
||||||
--disk path=images/focal-server-cloudimg-amd64.raw vhost_user=true,socket=/var/tmp/vhost.1,num_queues=4,queue_size=128 \
|
--disk path=images/focal-server-cloudimg-amd64.raw --disk vhost_user=true,socket=/var/tmp/vhost.1,num_queues=4,queue_size=128 \
|
||||||
--console off \
|
--console off \
|
||||||
--serial tty \
|
--serial tty \
|
||||||
--rng
|
--rng
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ This section illustrates the Windows specific aspects of the VM network configur
|
|||||||
|
|
||||||
### Basic Networking
|
### Basic Networking
|
||||||
|
|
||||||
As the simplest option, using `--net tap=` in the Cloud Hypervisor command line will create a `vmtapX` device on the host with the default IPv4 adress `192.168.249.1`. After SAC becomes available, the guest configuration can be set with
|
As the simplest option, using `--net tap=` in the Cloud Hypervisor command line will create a `vmtapX` device on the host with the default IPv4 address `192.168.249.1`. After SAC becomes available, the guest configuration can be set with
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
SAC>i 10 192.168.249.2 255.255.255.0 192.168.249.1
|
SAC>i 10 192.168.249.2 255.255.255.0 192.168.249.1
|
||||||
@@ -203,13 +203,13 @@ The Windows guest debugging process relies heavily on QEMU and [socat](http://ww
|
|||||||
|
|
||||||
The connection between both guests happens over TCP, whereby on the guest side it is automatically translated to a COM port. Because the VMs are connected through TCP, the debugging infrastructure can be distributed over the network. The serial port, while slowly transferring data, is common enough to support a wide range of cases and tools.
|
The connection between both guests happens over TCP, whereby on the guest side it is automatically translated to a COM port. Because the VMs are connected through TCP, the debugging infrastructure can be distributed over the network. The serial port, while slowly transferring data, is common enough to support a wide range of cases and tools.
|
||||||
|
|
||||||
In this excercise, [WinDbg](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/) is used. Any other debugger of choice with the ability to use serial connection can be used instead.
|
In this exercise, [WinDbg](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/) is used. Any other debugger of choice with the ability to use serial connection can be used instead.
|
||||||
|
|
||||||
### Debugger and Debuggee
|
### Debugger and Debuggee
|
||||||
|
|
||||||
#### WinDbg VM
|
#### WinDbg VM
|
||||||
|
|
||||||
For simplicity, the debugger VM is supposed to be only running under QEMU. It will require VGA and doesn't neccessarily depend on UEFI. As an OS, it can carry any supported Windows OS where the debugger of choice can be installed. The simplest way is to follow the image preparation instructions from the previous chapter, but avoid using the OVMF firmware. It is also not required to use VirtIO drivers, whereby it might be useful in some case. Though, while creating the image file for the debugger VM, be sure to choose a sufficient disk size that counts in the need to save the corresponding debug symbols and sources.
|
For simplicity, the debugger VM is supposed to be only running under QEMU. It will require VGA and doesn't necessarily depend on UEFI. As an OS, it can carry any supported Windows OS where the debugger of choice can be installed. The simplest way is to follow the image preparation instructions from the previous chapter, but avoid using the OVMF firmware. It is also not required to use VirtIO drivers, whereby it might be useful in some case. Though, while creating the image file for the debugger VM, be sure to choose a sufficient disk size that counts in the need to save the corresponding debug symbols and sources.
|
||||||
|
|
||||||
To create the debugger Windows VM, the following command can be used:
|
To create the debugger Windows VM, the following command can be used:
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ This will configure the debugging to be enabled and instruct to use the serial p
|
|||||||
bcdedit /dbgsettings serial debugport:1 baudrate:115200
|
bcdedit /dbgsettings serial debugport:1 baudrate:115200
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Turn On Kernel Debuging
|
##### Turn On Kernel Debugging
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
bcdedit /debug on
|
bcdedit /debug on
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ authors = ["The Cloud Hypervisor Authors"]
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2.137"
|
flume = "0.10.14"
|
||||||
serde = { version = "1.0.147", features = ["rc", "derive"] }
|
libc = "0.2.147"
|
||||||
serde_json = "1.0.87"
|
once_cell = "1.18.0"
|
||||||
|
serde = { version = "1.0.168", features = ["rc", "derive"] }
|
||||||
|
serde_json = "1.0.107"
|
||||||
|
|||||||
@@ -3,34 +3,17 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
static mut MONITOR: Option<(File, Instant)> = None;
|
static MONITOR: OnceCell<MonitorHandle> = OnceCell::new();
|
||||||
|
|
||||||
/// 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)]
|
#[derive(Serialize)]
|
||||||
struct Event<'a> {
|
struct Event<'a> {
|
||||||
@@ -40,18 +23,88 @@ struct Event<'a> {
|
|||||||
properties: Option<&'a HashMap<Cow<'a, str>, Cow<'a, str>>>,
|
properties: Option<&'a HashMap<Cow<'a, str>, Cow<'a, str>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Monitor {
|
||||||
|
pub rx: flume::Receiver<String>,
|
||||||
|
pub file: Option<File>,
|
||||||
|
pub broadcast: Vec<flume::Sender<Arc<String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Monitor {
|
||||||
|
pub fn new(rx: flume::Receiver<String>, file: Option<File>) -> Self {
|
||||||
|
Self {
|
||||||
|
rx,
|
||||||
|
file,
|
||||||
|
broadcast: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subscribe(&mut self) -> flume::Receiver<Arc<String>> {
|
||||||
|
let (tx, rx) = flume::unbounded();
|
||||||
|
self.broadcast.push(tx);
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MonitorHandle {
|
||||||
|
tx: flume::Sender<String>,
|
||||||
|
start: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_file_nonblocking(file: &File) -> io::Result<()> {
|
||||||
|
let fd = file.as_raw_fd();
|
||||||
|
|
||||||
|
// SAFETY: FFI call to configure the 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 {
|
||||||
|
Err(io::Error::last_os_error())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function must only be called once from the main thread before any threads
|
||||||
|
/// are created to avoid race conditions.
|
||||||
|
pub fn set_monitor(file: Option<File>) -> io::Result<Monitor> {
|
||||||
|
// There is only one caller of this function, so MONITOR is written to only once
|
||||||
|
assert!(MONITOR.get().is_none());
|
||||||
|
|
||||||
|
if let Some(ref file) = file {
|
||||||
|
set_file_nonblocking(file)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (tx, rx) = flume::unbounded();
|
||||||
|
let monitor = Monitor::new(rx, file);
|
||||||
|
|
||||||
|
MONITOR.get_or_init(|| MonitorHandle {
|
||||||
|
tx,
|
||||||
|
start: Instant::now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(monitor)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn event_log(source: &str, event: &str, properties: Option<&HashMap<Cow<str>, Cow<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() } {
|
// `MONITOR` is always in a valid state (None or Some), because it is set
|
||||||
let e = Event {
|
// only once before any threads are spawned, and it's not mutated
|
||||||
timestamp: start.elapsed(),
|
// afterwards. This function only creates immutable references to `MONITOR`.
|
||||||
|
// Because `MONITOR.tx` is `Sync`, it's safe to share `MONITOR` across
|
||||||
|
// threads, making this function thread-safe.
|
||||||
|
if let Some(monitor_handle) = MONITOR.get().as_ref() {
|
||||||
|
let event = Event {
|
||||||
|
timestamp: monitor_handle.start.elapsed(),
|
||||||
source,
|
source,
|
||||||
event,
|
event,
|
||||||
properties,
|
properties,
|
||||||
};
|
};
|
||||||
serde_json::to_writer_pretty(file, &e).ok();
|
|
||||||
|
|
||||||
let mut file = file;
|
if let Ok(event) = serde_json::to_string_pretty(&event) {
|
||||||
file.write_all(b"\n\n").ok();
|
monitor_handle.tx.send(event).ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,5 +128,4 @@ macro_rules! event {
|
|||||||
$crate::event_log($source, $event, Some(&properties))
|
$crate::event_log($source, $event, Some(&properties))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
711
fuzz/Cargo.lock
generated
711
fuzz/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -9,21 +9,21 @@ edition = "2021"
|
|||||||
cargo-fuzz = true
|
cargo-fuzz = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
block_util = { path = "../block_util" }
|
block = { path = "../block" }
|
||||||
devices = { path = "../devices" }
|
devices = { path = "../devices" }
|
||||||
epoll = "4.3.1"
|
epoll = "4.3.1"
|
||||||
libc = "0.2.135"
|
libc = "0.2.148"
|
||||||
libfuzzer-sys = "0.4.5"
|
libfuzzer-sys = "0.4.7"
|
||||||
|
linux-loader = { version = "0.9.1", features = ["elf", "bzimage", "pe"] }
|
||||||
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" }
|
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" }
|
||||||
once_cell = "1.16.0"
|
net_util = { path = "../net_util" }
|
||||||
qcow = { path = "../qcow" }
|
once_cell = "1.18.0"
|
||||||
seccompiler = "0.3.0"
|
seccompiler = "0.3.0"
|
||||||
vhdx = { path = "../vhdx" }
|
|
||||||
virtio-devices = { path = "../virtio-devices" }
|
virtio-devices = { path = "../virtio-devices" }
|
||||||
virtio-queue = "0.6.1"
|
virtio-queue = "0.9.0"
|
||||||
vmm = { path = "../vmm" }
|
vmm = { path = "../vmm" }
|
||||||
vmm-sys-util = "0.10.0"
|
vmm-sys-util = "0.11.2"
|
||||||
vm-memory = "0.9.0"
|
vm-memory = "0.12.2"
|
||||||
vm-device = { path = "../vm-device" }
|
vm-device = { path = "../vm-device" }
|
||||||
vm-virtio = { path = "../vm-virtio" }
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
|
|
||||||
@@ -31,10 +31,8 @@ vm-virtio = { path = "../vm-virtio" }
|
|||||||
path = ".."
|
path = ".."
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.5.0-tdx" }
|
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.6.0-tdx" }
|
||||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "main" }
|
|
||||||
versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch" }
|
versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch" }
|
||||||
vhost = { git = "https://github.com/rust-vmm/vhost", branch = "main" }
|
|
||||||
|
|
||||||
# Prevent this from interfering with workspaces
|
# Prevent this from interfering with workspaces
|
||||||
[workspace]
|
[workspace]
|
||||||
@@ -76,12 +74,30 @@ path = "fuzz_targets/iommu.rs"
|
|||||||
test = false
|
test = false
|
||||||
doc = false
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "linux_loader"
|
||||||
|
path = "fuzz_targets/linux_loader.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "linux_loader_cmdline"
|
||||||
|
path = "fuzz_targets/linux_loader_cmdline.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "mem"
|
name = "mem"
|
||||||
path = "fuzz_targets/mem.rs"
|
path = "fuzz_targets/mem.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "net"
|
||||||
|
path = "fuzz_targets/net.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "pmem"
|
name = "pmem"
|
||||||
path = "fuzz_targets/pmem.rs"
|
path = "fuzz_targets/pmem.rs"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const QUEUE_NUM: usize = 3;
|
|||||||
const QUEUE_SIZE: u16 = 64;
|
const QUEUE_SIZE: u16 = 64;
|
||||||
// Descriptor table alignment
|
// Descriptor table alignment
|
||||||
const DESC_TABLE_ALIGN_SIZE: u64 = 16;
|
const DESC_TABLE_ALIGN_SIZE: u64 = 16;
|
||||||
// Avalable ring alignment
|
// Available ring alignment
|
||||||
const AVAIL_RING_ALIGN_SIZE: u64 = 2;
|
const AVAIL_RING_ALIGN_SIZE: u64 = 2;
|
||||||
// Used ring alignment
|
// Used ring alignment
|
||||||
const USED_RING_ALIGN_SIZE: u64 = 4;
|
const USED_RING_ALIGN_SIZE: u64 = 4;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use block_util::{async_io::DiskFile, raw_sync::RawFileDiskSync};
|
use block::{async_io::DiskFile, raw_sync::RawFileDiskSync};
|
||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
use seccompiler::SeccompAction;
|
use seccompiler::SeccompAction;
|
||||||
use std::ffi;
|
use std::ffi;
|
||||||
@@ -57,6 +57,7 @@ fuzz_target!(|bytes| {
|
|||||||
false,
|
false,
|
||||||
2,
|
2,
|
||||||
256,
|
256,
|
||||||
|
None,
|
||||||
SeccompAction::Allow,
|
SeccompAction::Allow,
|
||||||
None,
|
None,
|
||||||
EventFd::new(EFD_NONBLOCK).unwrap(),
|
EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
use devices::legacy::Cmos;
|
use devices::legacy::Cmos;
|
||||||
use libc::EFD_NONBLOCK;
|
use libc::EFD_NONBLOCK;
|
||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::Arc;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
@@ -25,6 +27,7 @@ fuzz_target!(|bytes| {
|
|||||||
u64::from_le_bytes(below_4g),
|
u64::from_le_bytes(below_4g),
|
||||||
u64::from_le_bytes(above_4g),
|
u64::from_le_bytes(above_4g),
|
||||||
EventFd::new(EFD_NONBLOCK).unwrap(),
|
EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut i = 16;
|
let mut i = 16;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const IOVA_SPACE_SIZE: usize = (0xfeef_ffff - 0xfee0_0000) + 1;
|
|||||||
const QUEUE_SIZE: u16 = 256;
|
const QUEUE_SIZE: u16 = 256;
|
||||||
// Descriptor table alignment
|
// Descriptor table alignment
|
||||||
const DESC_TABLE_ALIGN_SIZE: u64 = 16;
|
const DESC_TABLE_ALIGN_SIZE: u64 = 16;
|
||||||
// Avalable ring alignment
|
// Available ring alignment
|
||||||
const AVAIL_RING_ALIGN_SIZE: u64 = 2;
|
const AVAIL_RING_ALIGN_SIZE: u64 = 2;
|
||||||
// Used ring alignment
|
// Used ring alignment
|
||||||
const USED_RING_ALIGN_SIZE: u64 = 4;
|
const USED_RING_ALIGN_SIZE: u64 = 4;
|
||||||
|
|||||||
50
fuzz/fuzz_targets/linux_loader.rs
Normal file
50
fuzz/fuzz_targets/linux_loader.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
//
|
||||||
|
// Copyright © 2022 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
use linux_loader::loader::KernelLoader;
|
||||||
|
use std::ffi;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io;
|
||||||
|
use std::io::{Seek, SeekFrom, Write};
|
||||||
|
use std::os::unix::io::{FromRawFd, RawFd};
|
||||||
|
use vm_memory::{bitmap::AtomicBitmap, GuestAddress};
|
||||||
|
|
||||||
|
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
||||||
|
|
||||||
|
const MEM_SIZE: usize = 256 * 1024 * 1024;
|
||||||
|
// From 'arch::x86_64::layout::HIGH_RAM_START'
|
||||||
|
const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
|
||||||
|
|
||||||
|
fuzz_target!(|bytes| {
|
||||||
|
let shm = memfd_create(&ffi::CString::new("fuzz_load_kernel").unwrap(), 0).unwrap();
|
||||||
|
let mut kernel_file: File = unsafe { File::from_raw_fd(shm) };
|
||||||
|
kernel_file.write_all(&bytes).unwrap();
|
||||||
|
kernel_file.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
|
||||||
|
let guest_memory = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), MEM_SIZE)]).unwrap();
|
||||||
|
linux_loader::loader::elf::Elf::load(
|
||||||
|
&guest_memory,
|
||||||
|
None,
|
||||||
|
&mut kernel_file,
|
||||||
|
Some(HIGH_RAM_START),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
});
|
||||||
|
|
||||||
|
fn memfd_create(name: &ffi::CStr, flags: u32) -> Result<RawFd, io::Error> {
|
||||||
|
let res = unsafe { libc::syscall(libc::SYS_memfd_create, name.as_ptr(), flags) };
|
||||||
|
|
||||||
|
if res < 0 {
|
||||||
|
Err(io::Error::last_os_error())
|
||||||
|
} else {
|
||||||
|
Ok(res as RawFd)
|
||||||
|
}
|
||||||
|
}
|
||||||
34
fuzz/fuzz_targets/linux_loader_cmdline.rs
Normal file
34
fuzz/fuzz_targets/linux_loader_cmdline.rs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
//
|
||||||
|
// Copyright © 2022 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
use vm_memory::{bitmap::AtomicBitmap, GuestAddress};
|
||||||
|
|
||||||
|
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
||||||
|
|
||||||
|
const MEM_SIZE: usize = 256 * 1024 * 1024;
|
||||||
|
// From 'arch::x86_64::layout::CMDLINE_START'
|
||||||
|
const CMDLINE_START: GuestAddress = GuestAddress(0x20000);
|
||||||
|
|
||||||
|
fuzz_target!(|bytes| {
|
||||||
|
let payload_config = vmm::config::PayloadConfig {
|
||||||
|
firmware: None,
|
||||||
|
kernel: None,
|
||||||
|
cmdline: Some(String::from_utf8_lossy(&bytes).to_string()),
|
||||||
|
initramfs: None,
|
||||||
|
};
|
||||||
|
let kernel_cmdline = match vmm::vm::Vm::generate_cmdline(&payload_config) {
|
||||||
|
Ok(cmdline) => cmdline,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
let guest_memory = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), MEM_SIZE)]).unwrap();
|
||||||
|
|
||||||
|
linux_loader::loader::load_cmdline(&guest_memory, CMDLINE_START, &kernel_cmdline).ok();
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user