mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
626 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95e3c8f8fd | ||
|
|
5f399fc06d | ||
|
|
43a6a914b8 | ||
|
|
ad3f94638f | ||
|
|
4dd635a56a | ||
|
|
1179a1a1c9 | ||
|
|
c995b72384 | ||
|
|
f73a6c8d8e | ||
|
|
363273111a | ||
|
|
061351d82d | ||
|
|
5790bcefee | ||
|
|
86c736fe76 | ||
|
|
5737e58f29 | ||
|
|
e4af58b88e | ||
|
|
093f62858f | ||
|
|
305bec056f | ||
|
|
6cac99bd76 | ||
|
|
533d3a85d1 | ||
|
|
c7eac0f65a | ||
|
|
e7e850bbdd | ||
|
|
08b197bbc1 | ||
|
|
3259234e58 | ||
|
|
05d222f0eb | ||
|
|
57bc78da4f | ||
|
|
47cff7c37f | ||
|
|
3f5c719705 | ||
|
|
706b56e97d | ||
|
|
2d9e243163 | ||
|
|
7281459bf9 | ||
|
|
9bf4696b09 | ||
|
|
f7f79642ab | ||
|
|
1cc193ea3c | ||
|
|
676fb93c1a | ||
|
|
f7e72456b9 | ||
|
|
bd22190043 | ||
|
|
92325fc073 | ||
|
|
1ca6c159ef | ||
|
|
c38596d6d3 | ||
|
|
456f9b3871 | ||
|
|
f68880600c | ||
|
|
c3a809696a | ||
|
|
a9d6807522 | ||
|
|
c4bab33020 | ||
|
|
245bce23fa | ||
|
|
67ab81874a | ||
|
|
8b2af1a2c8 | ||
|
|
41930d11f7 | ||
|
|
9c85fbb1af | ||
|
|
5357761c37 | ||
|
|
bec975a7fc | ||
|
|
92370e8ff1 | ||
|
|
4a1eb0c485 | ||
|
|
6e6cf099ef | ||
|
|
6e002defe2 | ||
|
|
a51998605a | ||
|
|
b8be33dff7 | ||
|
|
3864230430 | ||
|
|
2c6426460e | ||
|
|
1a63b4b2ff | ||
|
|
c1f4df600f | ||
|
|
34c5a081aa | ||
|
|
3bff69734e | ||
|
|
91d15c331d | ||
|
|
e454bb629a | ||
|
|
09cf8fba5b | ||
|
|
c34baac649 | ||
|
|
a132001021 | ||
|
|
2c7d6be3f4 | ||
|
|
dd8687aebb | ||
|
|
dac0638fe8 | ||
|
|
92f415ea3f | ||
|
|
78799187e8 | ||
|
|
9011ff2161 | ||
|
|
5128ee9ba6 | ||
|
|
51e095c54d | ||
|
|
4c1ee0329e | ||
|
|
a09c8329fb | ||
|
|
f0febcae3d | ||
|
|
2c282a5a54 | ||
|
|
c72414552b | ||
|
|
bd17c84d3c | ||
|
|
55212b0437 | ||
|
|
84613d4273 | ||
|
|
34385e99f2 | ||
|
|
10b79431f6 | ||
|
|
3d5e5f318b | ||
|
|
68a1bf38e5 | ||
|
|
6e0403a959 | ||
|
|
364a0972f0 | ||
|
|
9283f87f90 | ||
|
|
458721629c | ||
|
|
d70d132a5c | ||
|
|
35121c44db | ||
|
|
aa8e9cd91a | ||
|
|
5d478c534e | ||
|
|
a70c1b38e7 | ||
|
|
971f552e09 | ||
|
|
edee53ac1a | ||
|
|
1f51e4525b | ||
|
|
f0b69d56d0 | ||
|
|
623fadfa9d | ||
|
|
777b7ee11e | ||
|
|
5226ceb974 | ||
|
|
17195e1a46 | ||
|
|
0df4b1ac4f | ||
|
|
2e0ec8095c | ||
|
|
ef2bbe5012 | ||
|
|
2524b015b8 | ||
|
|
9825397350 | ||
|
|
a637940be7 | ||
|
|
684fb1dfce | ||
|
|
e718e0ad36 | ||
|
|
f87772cd36 | ||
|
|
0d9a941a2c | ||
|
|
4f4940b02a | ||
|
|
0bfa160360 | ||
|
|
530719a57a | ||
|
|
cd2c43b489 | ||
|
|
394fd230b0 | ||
|
|
abcec231eb | ||
|
|
cda1ea53a5 | ||
|
|
e2d2b2f2f0 | ||
|
|
9ee3b29a11 | ||
|
|
eb147cb3cc | ||
|
|
b630b22ed9 | ||
|
|
a7645ae9d6 | ||
|
|
254db85a33 | ||
|
|
16b0e08482 | ||
|
|
8a37e154db | ||
|
|
003e89e8cd | ||
|
|
8e010f1aa3 | ||
|
|
7c631b2d1b | ||
|
|
ba962a30d5 | ||
|
|
8c136041cb | ||
|
|
b0bf889d58 | ||
|
|
2c3e6cd93d | ||
|
|
985dcfbf3e | ||
|
|
511a100842 | ||
|
|
8a26380657 | ||
|
|
76d8d47f6a | ||
|
|
423280fb96 | ||
|
|
5f2392c095 | ||
|
|
2d9fc3beb6 | ||
|
|
4cae96f070 | ||
|
|
f6568042ce | ||
|
|
2ae5b2567b | ||
|
|
6d70c570f3 | ||
|
|
930a911862 | ||
|
|
03b22a510d | ||
|
|
62001b65e9 | ||
|
|
4be2ca4c10 | ||
|
|
5716af09a5 | ||
|
|
4ea40b4bea | ||
|
|
4528e2f1ea | ||
|
|
987ad11c90 | ||
|
|
cea708deb9 | ||
|
|
e32fa593e5 | ||
|
|
ec8fceb4a6 | ||
|
|
01aed9733c | ||
|
|
a5cd1b4fbe | ||
|
|
b268e88ba3 | ||
|
|
0659eaeba1 | ||
|
|
96528f84f9 | ||
|
|
6da5c32fd9 | ||
|
|
07cc1f6545 | ||
|
|
008f259aff | ||
|
|
aa6fefa80f | ||
|
|
87e74719ec | ||
|
|
072f06ff4c | ||
|
|
50b33db718 | ||
|
|
19dc733267 | ||
|
|
6ba949d741 | ||
|
|
9d4408ba76 | ||
|
|
f9c134471a | ||
|
|
3d5b4d0b0c | ||
|
|
ea32b67098 | ||
|
|
190d90196f | ||
|
|
fe422a45af | ||
|
|
da5fae3814 | ||
|
|
8a78043e2f | ||
|
|
fdeb778210 | ||
|
|
13c222a879 | ||
|
|
b27d9ccfab | ||
|
|
d580ed55c6 | ||
|
|
2cb8c41adc | ||
|
|
48b67ed03b | ||
|
|
e0f0065cbd | ||
|
|
1866a85a3d | ||
|
|
8338fa642f | ||
|
|
a5287c6f67 | ||
|
|
fe07617f5d | ||
|
|
1820c22ba4 | ||
|
|
f828e16d62 | ||
|
|
24998c1672 | ||
|
|
821f7994fc | ||
|
|
4182ef91e0 | ||
|
|
100c6d8142 | ||
|
|
269976c7b3 | ||
|
|
8769b78bf3 | ||
|
|
a991de9955 | ||
|
|
47c9ddfa7e | ||
|
|
2b05753716 | ||
|
|
80f4bfac00 | ||
|
|
b26b09ec0c | ||
|
|
39a6664e62 | ||
|
|
190a11f212 | ||
|
|
6ea132708c | ||
|
|
060c9de07f | ||
|
|
1433763d40 | ||
|
|
30ee2c129d | ||
|
|
9bf15ed280 | ||
|
|
ed63b352d1 | ||
|
|
8e2973fe7c | ||
|
|
3541cebbf1 | ||
|
|
1ede418dcc | ||
|
|
1b03e59152 | ||
|
|
8f56de713b | ||
|
|
2100d8c30f | ||
|
|
a3692144f0 | ||
|
|
192b19c060 | ||
|
|
67896333e3 | ||
|
|
72c8178335 | ||
|
|
1b91aa8ef3 | ||
|
|
5711f31995 | ||
|
|
aebbd1aecd | ||
|
|
06a868cb85 | ||
|
|
3f3489e38e | ||
|
|
d7edd9d51f | ||
|
|
4987d63b6a | ||
|
|
53e9c94e68 | ||
|
|
9d8bda20e5 | ||
|
|
63d7f562e5 | ||
|
|
9bd9c0cb71 | ||
|
|
d594107c0d | ||
|
|
1f13165fae | ||
|
|
12493db144 | ||
|
|
f151fdb16f | ||
|
|
bb6ca56fb0 | ||
|
|
a336533389 | ||
|
|
c4b6ed1077 | ||
|
|
3e8dda4e7d | ||
|
|
e9edb2e51c | ||
|
|
0798effcc6 | ||
|
|
6079791bca | ||
|
|
ff2330defe | ||
|
|
12b72ba3c1 | ||
|
|
92f9e20f57 | ||
|
|
3d78662498 | ||
|
|
2bc8d51a60 | ||
|
|
51002f2bae | ||
|
|
59e11f1b0b | ||
|
|
fc01e4cbec | ||
|
|
3f8186f627 | ||
|
|
6f56ef9a36 | ||
|
|
d6ed74b5b8 | ||
|
|
7fbe94a227 | ||
|
|
4a1809f596 | ||
|
|
0a4169801b | ||
|
|
20296e909a | ||
|
|
24a6a1805b | ||
|
|
dd7318e839 | ||
|
|
37444c4bab | ||
|
|
c9a39cf5b5 | ||
|
|
f6326df68b | ||
|
|
7571e93a69 | ||
|
|
ab6e1bd2d8 | ||
|
|
517ea00bd9 | ||
|
|
ea6d5a04fa | ||
|
|
799336459d | ||
|
|
f934e142ba | ||
|
|
0cde3df44a | ||
|
|
7585e16f9d | ||
|
|
ab575a54b9 | ||
|
|
d2ca7b0e87 | ||
|
|
dd9bce31e8 | ||
|
|
a6370b74d9 | ||
|
|
d172a5dddb | ||
|
|
d46517559b | ||
|
|
0463f4f156 | ||
|
|
fff62d9302 | ||
|
|
5db92f79bf | ||
|
|
06f3049d24 | ||
|
|
28e0a95450 | ||
|
|
a615c809eb | ||
|
|
a3dcaedf7e | ||
|
|
eb0b14f70e | ||
|
|
80e66657cc | ||
|
|
fd5cfb75f3 | ||
|
|
8696bc6604 | ||
|
|
93b599e59e | ||
|
|
01761c2596 | ||
|
|
a212343908 | ||
|
|
d1a406143d | ||
|
|
38380198e1 | ||
|
|
0e40a50407 | ||
|
|
262984f8fc | ||
|
|
dce82a34d0 | ||
|
|
1454d39b28 | ||
|
|
2588fc9e42 | ||
|
|
67793ca375 | ||
|
|
96deca9dc9 | ||
|
|
78b0f68b21 | ||
|
|
a007b750ff | ||
|
|
b2993fb2fa | ||
|
|
8d11bf7979 | ||
|
|
d4718a9bc8 | ||
|
|
05968f5c2c | ||
|
|
71a36e0c69 | ||
|
|
2da5e10689 | ||
|
|
a647d7863c | ||
|
|
a23d4b7cf2 | ||
|
|
f3209e4f78 | ||
|
|
596d6453c5 | ||
|
|
1307d31ede | ||
|
|
2f21827430 | ||
|
|
8da7c13e26 | ||
|
|
aaf86ef209 | ||
|
|
59f98a2edc | ||
|
|
cb52cf91df | ||
|
|
5814193722 | ||
|
|
10ee003d66 | ||
|
|
8f402687ce | ||
|
|
f72f16ee50 | ||
|
|
034aa514d7 | ||
|
|
4e48f429eb | ||
|
|
50b0493371 | ||
|
|
df418a2153 | ||
|
|
a072b9a356 | ||
|
|
691fe0ca68 | ||
|
|
aaa3a114dd | ||
|
|
77e042237d | ||
|
|
f16d45e86e | ||
|
|
3eb6b69dd2 | ||
|
|
fa2b5ca12b | ||
|
|
eac44e6af0 | ||
|
|
40055595ff | ||
|
|
149c08981b | ||
|
|
f1f6814774 | ||
|
|
58f71b0c44 | ||
|
|
1968805ba2 | ||
|
|
d0225fe68f | ||
|
|
87007a288f | ||
|
|
f67484a714 | ||
|
|
3c63779302 | ||
|
|
1c22c4a57b | ||
|
|
4af98f4cb2 | ||
|
|
c58c686f3e | ||
|
|
9b13d63f28 | ||
|
|
b5aeb1f63c | ||
|
|
af2ce3e0cc | ||
|
|
7fd1b9a284 | ||
|
|
e69acd1dc3 | ||
|
|
de764456ce | ||
|
|
0095556847 | ||
|
|
a0065452d8 | ||
|
|
56ca26e72c | ||
|
|
21b9806cad | ||
|
|
d374101f38 | ||
|
|
461e31e6d8 | ||
|
|
d22e7e2638 | ||
|
|
960d702255 | ||
|
|
1105243aca | ||
|
|
317f8002d7 | ||
|
|
278b57ba49 | ||
|
|
d7f87425cd | ||
|
|
573868c035 | ||
|
|
2798286278 | ||
|
|
bcc314eb8b | ||
|
|
af28569611 | ||
|
|
1a5dcc5e70 | ||
|
|
29b089296d | ||
|
|
6e4bf84383 | ||
|
|
226ecf47bb | ||
|
|
0e3733e938 | ||
|
|
f811e36443 | ||
|
|
a64ba04e78 | ||
|
|
b686a5bb24 | ||
|
|
ea4693a091 | ||
|
|
3698b8e74c | ||
|
|
6bb33601d0 | ||
|
|
3d88996e5b | ||
|
|
3899cd0e30 | ||
|
|
9d93df4c8f | ||
|
|
4c27572b87 | ||
|
|
19fea1ad88 | ||
|
|
9a96ea44be | ||
|
|
e11b9d6449 | ||
|
|
ce5fe7f89d | ||
|
|
0dd0364bf8 | ||
|
|
3081d01fc3 | ||
|
|
fa58b725cb | ||
|
|
a38b4c7f17 | ||
|
|
5aa1540c5d | ||
|
|
7da8ae9c0f | ||
|
|
108e8f9dff | ||
|
|
5ab2feae70 | ||
|
|
c99660a8f9 | ||
|
|
08c1bb4b00 | ||
|
|
5bd177dde8 | ||
|
|
52ed1082fb | ||
|
|
03a23309b9 | ||
|
|
fa0965a11a | ||
|
|
524f26abef | ||
|
|
846d0976e3 | ||
|
|
4718dc1b72 | ||
|
|
0a9895e009 | ||
|
|
297b41d615 | ||
|
|
396aba7a52 | ||
|
|
35aaa1333a | ||
|
|
6768a13d95 | ||
|
|
0d50d0b257 | ||
|
|
f5a2f8473b | ||
|
|
03bb59db69 | ||
|
|
9e1fb3bfa1 | ||
|
|
4de422ad69 | ||
|
|
c441bb2968 | ||
|
|
5cb5115456 | ||
|
|
0fbba66b21 | ||
|
|
5388fa1ced | ||
|
|
655d512523 | ||
|
|
7d45473ff7 | ||
|
|
294d5fbb08 | ||
|
|
8c796e6d5d | ||
|
|
630f5c1f14 | ||
|
|
5cbf907200 | ||
|
|
bfeab76059 | ||
|
|
12143ca72f | ||
|
|
ba05bdd085 | ||
|
|
171b28ce52 | ||
|
|
5b929cb277 | ||
|
|
ee0b0d43d8 | ||
|
|
061482340e | ||
|
|
c43ae1dc9d | ||
|
|
7db3002e1d | ||
|
|
07b7457c2e | ||
|
|
dc9142f86c | ||
|
|
6fa7c84d2e | ||
|
|
cc734e2e44 | ||
|
|
e3e83362d6 | ||
|
|
730cf1e944 | ||
|
|
1caa69d0cc | ||
|
|
abcbf43433 | ||
|
|
af74de2779 | ||
|
|
dfebc8929d | ||
|
|
9988379f6b | ||
|
|
4d12192490 | ||
|
|
9f9cfeb5be | ||
|
|
3fa1e77adc | ||
|
|
b8424ea506 | ||
|
|
0c2fe29f61 | ||
|
|
b445d219ca | ||
|
|
6de283f42c | ||
|
|
f892789481 | ||
|
|
f96554cbe9 | ||
|
|
3509b5bf4a | ||
|
|
cf463b88b7 | ||
|
|
ca8eda5399 | ||
|
|
45b770ea9f | ||
|
|
d2c4ceb99a | ||
|
|
caf7cae2fa | ||
|
|
81eca69b40 | ||
|
|
8c07294691 | ||
|
|
2f9436bc12 | ||
|
|
62fade97d1 | ||
|
|
5a8df3622c | ||
|
|
27fda753e1 | ||
|
|
74ca38f7a9 | ||
|
|
fa686fdfc7 | ||
|
|
c827a1c009 | ||
|
|
eefd7fcaaf | ||
|
|
62303d8c33 | ||
|
|
293cf90e3d | ||
|
|
8f98fabd0b | ||
|
|
07821c08c0 | ||
|
|
0cb2c86ff4 | ||
|
|
d359c8cdce | ||
|
|
c801929fd1 | ||
|
|
66da3b9970 | ||
|
|
6ddbd60d9d | ||
|
|
2fc4de6c65 | ||
|
|
03eeb36b74 | ||
|
|
2fe7f54ece | ||
|
|
1cc540c880 | ||
|
|
a1af4238ae | ||
|
|
d2e798944a | ||
|
|
d99f294281 | ||
|
|
44c9ce598d | ||
|
|
5b42aa0bcf | ||
|
|
703e0cab04 | ||
|
|
abb88cf47f | ||
|
|
2e22b8bc3a | ||
|
|
778c05d678 | ||
|
|
5b29eba636 | ||
|
|
e6e78e5986 | ||
|
|
a2df4d7660 | ||
|
|
bc4a1fd16c | ||
|
|
463c9b8e56 | ||
|
|
835847bea5 | ||
|
|
3e4ed5621e | ||
|
|
2624f17ffe | ||
|
|
21f05ebb4f | ||
|
|
1485e17268 | ||
|
|
b57cc3d79f | ||
|
|
a322e2d6f4 | ||
|
|
dd0b95ba5c | ||
|
|
b6667f948e | ||
|
|
4f22f67307 | ||
|
|
eeae63b459 | ||
|
|
abf5748f64 | ||
|
|
23b5f7b1c5 | ||
|
|
a66fef407c | ||
|
|
7c39f37855 | ||
|
|
fe24a7a24f | ||
|
|
73e1451a12 | ||
|
|
1180f757b3 | ||
|
|
2932658acd | ||
|
|
1f7b809619 | ||
|
|
32482f6634 | ||
|
|
6fd5b0f696 | ||
|
|
ef88b2778e | ||
|
|
8b55d1e2d4 | ||
|
|
2df285effd | ||
|
|
4ea4391f45 | ||
|
|
8f8d3c0139 | ||
|
|
408467a05f | ||
|
|
f27b028c18 | ||
|
|
eaa2194699 | ||
|
|
9973d28849 | ||
|
|
1cdb91a40f | ||
|
|
57e331db0e | ||
|
|
090fcebfec | ||
|
|
b2f40afc69 | ||
|
|
72452707ee | ||
|
|
49a389ff12 | ||
|
|
2a9b60dcce | ||
|
|
bbd8d3b71d | ||
|
|
b9163bf431 | ||
|
|
909e1bc3f0 | ||
|
|
4f18476cca | ||
|
|
3d7661664d | ||
|
|
50bac1694f | ||
|
|
dd8a5a7de8 | ||
|
|
dba92c28f7 | ||
|
|
4e298d1abf | ||
|
|
149c342867 | ||
|
|
4054a49e2d | ||
|
|
1001d807ff | ||
|
|
e4a5219f53 | ||
|
|
a458351d8b | ||
|
|
5a27bf878c | ||
|
|
310dafb4d7 | ||
|
|
78895dcc37 | ||
|
|
1757d83db3 | ||
|
|
9cef779cc7 | ||
|
|
a10d1ed78e | ||
|
|
dc7f0a42bc | ||
|
|
53098ae40f | ||
|
|
51cf346828 | ||
|
|
0042447fb9 | ||
|
|
e8c330e220 | ||
|
|
ba78e331c6 | ||
|
|
b6d2ac2c2e | ||
|
|
f6fb442fa3 | ||
|
|
0c2f2d3ec1 | ||
|
|
584c088094 | ||
|
|
5b715f483d | ||
|
|
1e602bd9a6 | ||
|
|
6b57f301db | ||
|
|
cd0db3146f | ||
|
|
ac3ef0849e | ||
|
|
bd8db86b0c | ||
|
|
7b5f06788a | ||
|
|
9a7f278716 | ||
|
|
fb457954bb | ||
|
|
c4063d26be | ||
|
|
9006013c60 | ||
|
|
30cf1eed5e | ||
|
|
c6b023e58d | ||
|
|
46f0e0d14a | ||
|
|
261bfac4d4 | ||
|
|
ec9b9ab542 | ||
|
|
8c9b34c9f1 | ||
|
|
5b822191c0 | ||
|
|
ab7b294688 | ||
|
|
7e419784cd | ||
|
|
48bed965c0 | ||
|
|
0d6cef4521 | ||
|
|
81f8a27ef6 | ||
|
|
e3bd5e9b35 | ||
|
|
80b2c98a68 | ||
|
|
3c05626ad1 | ||
|
|
906580ee92 | ||
|
|
c6889388a3 | ||
|
|
f38fc94115 | ||
|
|
6ecca29aa6 | ||
|
|
2ef04671be | ||
|
|
453bc31994 | ||
|
|
6e6966df8f | ||
|
|
f32c2638f6 | ||
|
|
fbe1cd6404 | ||
|
|
778a72f52f | ||
|
|
76256230d6 | ||
|
|
8cd80ea36b | ||
|
|
5e937c8b88 | ||
|
|
710535343b | ||
|
|
59c5b0a1cd | ||
|
|
c13019d5b5 | ||
|
|
c5774685f4 | ||
|
|
2df8d2fa1e | ||
|
|
dd00bd6ef2 | ||
|
|
2752149bb0 | ||
|
|
1bc74e43f8 | ||
|
|
8acaeb5c82 | ||
|
|
5eaf510f90 | ||
|
|
df1d6eaaee | ||
|
|
a6a3d247da | ||
|
|
838a857311 | ||
|
|
95229b24ab | ||
|
|
71b0eb3c21 | ||
|
|
337cbf3d33 | ||
|
|
675140f627 | ||
|
|
894a4dee6e | ||
|
|
f57d00cd66 | ||
|
|
59a1719b6e | ||
|
|
39ab10244b | ||
|
|
b5d856b31a |
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@@ -16,3 +16,8 @@ updates:
|
|||||||
allow:
|
allow:
|
||||||
- dependency-type: direct
|
- dependency-type: direct
|
||||||
- dependency-type: indirect
|
- dependency-type: indirect
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
open-pull-requests-limit: 1
|
||||||
2
.github/workflows/audit.yaml
vendored
2
.github/workflows/audit.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
name: Audit
|
name: Audit
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: actions-rust-lang/audit@v1
|
- uses: actions-rust-lang/audit@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
10
.github/workflows/build.yaml
vendored
10
.github/workflows/build.yaml
vendored
@@ -15,13 +15,13 @@ jobs:
|
|||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
- nightly
|
- nightly
|
||||||
- "1.77.0"
|
- "1.88.0"
|
||||||
target:
|
target:
|
||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
- x86_64-unknown-linux-musl
|
- x86_64-unknown-linux-musl
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -52,6 +52,12 @@ jobs:
|
|||||||
- name: Build (default features + pvmemcontrol)
|
- name: Build (default features + pvmemcontrol)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo rustc --locked --bin cloud-hypervisor --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
|
- name: Build (default features + fw_cfg)
|
||||||
|
run: cargo rustc --locked --bin cloud-hypervisor --features "fw_cfg" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
|
- name: Build (default features + ivshmem)
|
||||||
|
run: cargo rustc --locked --bin cloud-hypervisor --features "ivshmem" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
- name: Build (mshv)
|
- name: Build (mshv)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/dco.yaml
vendored
4
.github/workflows/dco.yaml
vendored
@@ -6,9 +6,9 @@ jobs:
|
|||||||
name: DCO Check ("Signed-Off-By")
|
name: DCO Check ("Signed-Off-By")
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Set up Python 3.x
|
- name: Set up Python 3.x
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
- name: Check DCO
|
- name: Check DCO
|
||||||
|
|||||||
16
.github/workflows/docker-image.yaml
vendored
16
.github/workflows/docker-image.yaml
vendored
@@ -18,16 +18,16 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to ghcr
|
- name: Login to ghcr
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -36,17 +36,17 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
# generate Docker tags based on the following events/attributes
|
# generate Docker tags based on the following events/attributes
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=20241022-0
|
type=raw,value=20250815-0
|
||||||
type=sha
|
type=sha
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
if: ${{ github.event_name == 'push' }}
|
if: ${{ github.event_name == 'push' }}
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
file: ./resources/Dockerfile
|
file: ./resources/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build only
|
- name: Build only
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
file: ./resources/Dockerfile
|
file: ./resources/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|||||||
2
.github/workflows/formatting.yaml
vendored
2
.github/workflows/formatting.yaml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/fuzz-build.yaml
vendored
2
.github/workflows/fuzz-build.yaml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
|
|||||||
4
.github/workflows/gitlint.yaml
vendored
4
.github/workflows/gitlint.yaml
vendored
@@ -8,12 +8,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up Python 3.10
|
- name: Set up Python 3.10
|
||||||
uses: actions/setup-python@v3
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
|||||||
2
.github/workflows/hadolint.yaml
vendored
2
.github/workflows/hadolint.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Lint Dockerfile
|
- name: Lint Dockerfile
|
||||||
uses: hadolint/hadolint-action@master
|
uses: hadolint/hadolint-action@master
|
||||||
|
|||||||
6
.github/workflows/integration-arm64.yaml
vendored
6
.github/workflows/integration-arm64.yaml
vendored
@@ -6,14 +6,14 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 120
|
||||||
name: Tests (ARM64)
|
name: Tests (ARM64)
|
||||||
runs-on: bookworm-arm64
|
runs-on: bookworm-arm64
|
||||||
steps:
|
steps:
|
||||||
- name: Fix workspace permissions
|
- name: Fix workspace permissions
|
||||||
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
|
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Run unit tests (musl)
|
- name: Run unit tests (musl)
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
- name: Load openvswitch module
|
- name: Load openvswitch module
|
||||||
run: sudo modprobe openvswitch
|
run: sudo modprobe openvswitch
|
||||||
- name: Run integration tests (musl)
|
- name: Run integration tests (musl)
|
||||||
timeout-minutes: 30
|
timeout-minutes: 60
|
||||||
run: scripts/dev_cli.sh tests --integration --libc musl
|
run: scripts/dev_cli.sh tests --integration --libc musl
|
||||||
- name: Install Azure CLI
|
- name: Install Azure CLI
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
|||||||
13
.github/workflows/integration-metrics.yaml
vendored
13
.github/workflows/integration-metrics.yaml
vendored
@@ -7,23 +7,14 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Tests (Metrics)
|
name: Tests (Metrics)
|
||||||
runs-on: garm-jammy-16
|
runs-on: bare-metal-9950x
|
||||||
env:
|
env:
|
||||||
METRICS_PUBLISH_KEY: ${{ secrets.METRICS_PUBLISH_KEY }}
|
METRICS_PUBLISH_KEY: ${{ secrets.METRICS_PUBLISH_KEY }}
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install Docker
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get -y install ca-certificates curl gnupg
|
|
||||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt install -y docker-ce docker-ce-cli
|
|
||||||
- name: Run metrics tests
|
- name: Run metrics tests
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
run: scripts/dev_cli.sh tests --metrics -- -- --report-file /root/workloads/metrics.json
|
run: scripts/dev_cli.sh tests --metrics -- -- --report-file /root/workloads/metrics.json
|
||||||
|
|||||||
16
.github/workflows/integration-rate-limiter.yaml
vendored
16
.github/workflows/integration-rate-limiter.yaml
vendored
@@ -7,28 +7,18 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Tests (Rate-Limiter)
|
name: Tests (Rate-Limiter)
|
||||||
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'garm-jammy-16' }}
|
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'bare-metal-9950x' }}
|
||||||
env:
|
env:
|
||||||
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install Docker
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get -y install ca-certificates curl gnupg
|
|
||||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt install -y docker-ce docker-ce-cli
|
|
||||||
- name: Run rate-limiter integration tests
|
- name: Run rate-limiter integration tests
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
timeout-minutes: 10
|
timeout-minutes: 20
|
||||||
run: scripts/dev_cli.sh tests --integration-rate-limiter
|
run: scripts/dev_cli.sh tests --integration-rate-limiter
|
||||||
- name: Skipping build for PR
|
- name: Skipping build for PR
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|||||||
2
.github/workflows/integration-vfio.yaml
vendored
2
.github/workflows/integration-vfio.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
|
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Run VFIO integration tests
|
- name: Run VFIO integration tests
|
||||||
|
|||||||
2
.github/workflows/integration-windows.yaml
vendored
2
.github/workflows/integration-windows.yaml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install Docker
|
- name: Install Docker
|
||||||
|
|||||||
2
.github/workflows/integration-x86-64.yaml
vendored
2
.github/workflows/integration-x86-64.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install Docker
|
- name: Install Docker
|
||||||
|
|||||||
45
.github/workflows/lychee.yaml
vendored
Normal file
45
.github/workflows/lychee.yaml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: Link Check (lychee)
|
||||||
|
on: pull_request
|
||||||
|
jobs:
|
||||||
|
link_check:
|
||||||
|
name: Link Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Code checkout
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
# Fetch the entire history so git diff can compare against the base branch
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Get changed files in PR
|
||||||
|
id: changed-files
|
||||||
|
uses: tj-actions/changed-files@v46 # Using a dedicated action for robustness
|
||||||
|
with:
|
||||||
|
# Compare the HEAD of the PR with the merge-base (where the PR branches off)
|
||||||
|
base_sha: ${{ github.event.pull_request.base.sha }}
|
||||||
|
|
||||||
|
# NEW STEP: Print all changed-files outputs for verification
|
||||||
|
- name: Verify Changed Files
|
||||||
|
run: |
|
||||||
|
echo "--- tj-actions/changed-files Outputs ---"
|
||||||
|
echo "any_changed: ${{ steps.changed-files.outputs.any_changed }}"
|
||||||
|
echo "all_changed_files: ${{ steps.changed-files.outputs.all_changed_files }}"
|
||||||
|
echo "added_files: ${{ steps.changed-files.outputs.added_files }}"
|
||||||
|
echo "modified_files: ${{ steps.changed-files.outputs.modified_files }}"
|
||||||
|
echo "deleted_files: ${{ steps.changed-files.outputs.deleted_files }}"
|
||||||
|
echo "renamed_files: ${{ steps.changed-files.outputs.renamed_files }}"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
# This will also show if the all_changed_files string is empty or not
|
||||||
|
if [ -n "${{ steps.changed-files.outputs.all_changed_files }}" ]; then
|
||||||
|
echo "Detected changes: all_changed_files output is NOT empty."
|
||||||
|
else
|
||||||
|
echo "No changes detected: all_changed_files output IS empty."
|
||||||
|
fi
|
||||||
|
- name: Link Availability Check (Diff Only)
|
||||||
|
# MODIFIED: Only run lychee if the 'all_changed_files' output is not an empty string
|
||||||
|
if: ${{ steps.changed-files.outputs.all_changed_files != '' }}
|
||||||
|
uses: lycheeverse/lychee-action@master
|
||||||
|
with:
|
||||||
|
# Pass the space-separated list of changed files to lychee
|
||||||
|
args: --verbose --config .lychee.toml ${{ steps.changed-files.outputs.all_changed_files }}
|
||||||
|
failIfEmpty: false
|
||||||
|
fail: true
|
||||||
2
.github/workflows/openapi.yaml
vendored
2
.github/workflows/openapi.yaml
vendored
@@ -6,7 +6,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: openapitools/openapi-generator-cli
|
container: openapitools/openapi-generator-cli
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Validate OpenAPI
|
- name: Validate OpenAPI
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
32
.github/workflows/package-consistency.yaml
vendored
Normal file
32
.github/workflows/package-consistency.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Cloud Hypervisor Consistency
|
||||||
|
on: [pull_request, merge_group]
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Rust VMM Consistency Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Code checkout
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: sudo apt install -y python3
|
||||||
|
|
||||||
|
- name: Install Rust toolchain stable
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
|
||||||
|
- name: Check Rust VMM Package Consistency of root Workspace
|
||||||
|
run: python3 scripts/package-consistency-check.py github.com/rust-vmm
|
||||||
|
|
||||||
|
- name: Check Rust VMM Package Consistency of fuzz Workspace
|
||||||
|
run: |
|
||||||
|
pushd fuzz
|
||||||
|
python3 ../scripts/package-consistency-check.py github.com/rust-vmm
|
||||||
|
popd
|
||||||
30
.github/workflows/preview-riscv64-build.yaml
vendored
Normal file
30
.github/workflows/preview-riscv64-build.yaml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: Cloud Hypervisor RISC-V 64-bit kvm build Preview
|
||||||
|
on: [pull_request, merge_group]
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Cargo
|
||||||
|
runs-on: riscv64-qemu-host
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Code checkout
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
run: /opt/scripts/exec-in-qemu.sh rustup default 1.88.0
|
||||||
|
|
||||||
|
- name: Build test (kvm)
|
||||||
|
run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked --no-default-features --features "kvm"
|
||||||
|
|
||||||
|
- name: Clippy test (kvm)
|
||||||
|
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked --no-default-features --features "kvm"
|
||||||
|
|
||||||
|
- name: Check no files were modified
|
||||||
|
run: test -z "$(git status --porcelain)"
|
||||||
39
.github/workflows/preview-riscv64-modules.yaml
vendored
Normal file
39
.github/workflows/preview-riscv64-modules.yaml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: Cloud Hypervisor RISC-V 64-bit Preview
|
||||||
|
on: [pull_request, merge_group]
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Cargo
|
||||||
|
runs-on: riscv64-qemu-host
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
module:
|
||||||
|
- hypervisor
|
||||||
|
- arch
|
||||||
|
- vm-allocator
|
||||||
|
- devices
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Code checkout
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
run: /opt/scripts/exec-in-qemu.sh rustup default 1.88.0
|
||||||
|
|
||||||
|
- name: Build ${{ matrix.module }} Module (kvm)
|
||||||
|
run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
|
- name: Clippy ${{ matrix.module }} Module (kvm)
|
||||||
|
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
|
- name: Test ${{ matrix.module }} Module (kvm)
|
||||||
|
run: /opt/scripts/exec-in-qemu.sh cargo test --locked -p ${{ matrix.module }} --no-default-features --features "kvm"
|
||||||
|
|
||||||
|
- name: Check no files were modified
|
||||||
|
run: test -z "$(git status --porcelain)"
|
||||||
130
.github/workflows/quality.yaml
vendored
130
.github/workflows/quality.yaml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -50,74 +50,112 @@ jobs:
|
|||||||
git checkout ${{ github.sha }}
|
git checkout ${{ github.sha }}
|
||||||
|
|
||||||
- name: Clippy (kvm)
|
- name: Clippy (kvm)
|
||||||
uses: actions-rs/cargo@v1
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
with:
|
with:
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
- name: Clippy (default features)
|
target: ${{ matrix.target }}
|
||||||
uses: actions-rs/cargo@v1
|
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
with:
|
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (default features + guest_debug)
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (default features + pvmemcontrol)
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (default features + tracing)
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (mshv)
|
- name: Clippy (mshv)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
- name: Clippy (mshv + kvm)
|
- name: Clippy (mshv + kvm)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
|
- name: Clippy (default features)
|
||||||
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
|
with:
|
||||||
|
command: clippy
|
||||||
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
args: --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
|
- name: Clippy (default features + guest_debug)
|
||||||
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
|
with:
|
||||||
|
command: clippy
|
||||||
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
args: --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
|
- name: Clippy (default features + pvmemcontrol)
|
||||||
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
|
with:
|
||||||
|
command: clippy
|
||||||
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
args: --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
|
- name: Clippy (default features + tracing)
|
||||||
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
|
with:
|
||||||
|
command: clippy
|
||||||
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
args: --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
- name: Clippy (default features + fw_cfg)
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
|
command: clippy
|
||||||
|
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "fw_cfg" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
|
- name: Clippy (default features + ivshmem)
|
||||||
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
|
with:
|
||||||
|
command: clippy
|
||||||
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
args: --locked --all --all-targets --tests --examples --features "ivshmem" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
- name: Clippy (sev_snp)
|
- name: Clippy (sev_snp)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
uses: actions-rs/cargo@v1
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
with:
|
with:
|
||||||
command: clippy
|
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 -W clippy::assertions_on_result_states
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
args: --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
- name: Clippy (igvm)
|
- name: Clippy (igvm)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
uses: actions-rs/cargo@v1
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
args: --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
- name: Clippy (kvm + tdx)
|
- name: Clippy (kvm + tdx)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
uses: actions-rs/cargo@v1
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
args: --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||||
|
|
||||||
- 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)"
|
||||||
@@ -127,6 +165,6 @@ jobs:
|
|||||||
name: Typos / Spellcheck
|
name: Typos / Spellcheck
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
# Executes "typos ."
|
# Executes "typos ."
|
||||||
- uses: crate-ci/typos@v1.16.11
|
- uses: crate-ci/typos@v1.36.2
|
||||||
|
|||||||
12
.github/workflows/release.yaml
vendored
12
.github/workflows/release.yaml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Install musl-gcc
|
- name: Install musl-gcc
|
||||||
if: contains(matrix.platform.target, 'musl')
|
if: contains(matrix.platform.target, 'musl')
|
||||||
run: sudo apt install -y musl-tools
|
run: sudo apt install -y musl-tools
|
||||||
@@ -39,13 +39,13 @@ jobs:
|
|||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
||||||
run: rsync -rv --exclude=.git . ../cloud-hypervisor-${{ github.event.ref }}
|
run: rsync -rv --exclude=.git . ../cloud-hypervisor-${{ github.event.ref }}
|
||||||
- name: Build ${{ matrix.platform.target }}
|
- name: Build ${{ matrix.platform.target }}
|
||||||
uses: houseabsolute/actions-rust-cross@v0
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
with:
|
with:
|
||||||
command: build
|
command: build
|
||||||
target: ${{ matrix.platform.target }}
|
target: ${{ matrix.platform.target }}
|
||||||
args: ${{ matrix.platform.args }}
|
args: ${{ matrix.platform.args }}
|
||||||
strip: true
|
strip: true
|
||||||
toolchain: "1.77.0"
|
toolchain: "1.88.0"
|
||||||
- name: Copy Release Binaries
|
- name: Copy Release Binaries
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
cp target/${{ matrix.platform.target }}/release/ch-remote ./${{ matrix.platform.name_ch_remote }}
|
cp target/${{ matrix.platform.target }}/release/ch-remote ./${{ matrix.platform.name_ch_remote }}
|
||||||
- name: Upload Release Artifacts
|
- name: Upload Release Artifacts
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Artifacts for ${{ matrix.platform.target }}
|
name: Artifacts for ${{ matrix.platform.target }}
|
||||||
path: |
|
path: |
|
||||||
@@ -80,13 +80,13 @@ jobs:
|
|||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
||||||
id: upload-release-cloud-hypervisor-vendored-sources
|
id: upload-release-cloud-hypervisor-vendored-sources
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
path: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||||
name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
files: |
|
files: |
|
||||||
|
|||||||
4
.github/workflows/reuse.yaml
vendored
4
.github/workflows/reuse.yaml
vendored
@@ -7,6 +7,6 @@ jobs:
|
|||||||
name: REUSE Compliance Check
|
name: REUSE Compliance Check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: REUSE Compliance Check
|
- name: REUSE Compliance Check
|
||||||
uses: fsfe/reuse-action@v3
|
uses: fsfe/reuse-action@v5
|
||||||
|
|||||||
2
.github/workflows/shlint.yaml
vendored
2
.github/workflows/shlint.yaml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Run the shell script checkers
|
- name: Run the shell script checkers
|
||||||
uses: luizm/action-sh-checker@master
|
uses: luizm/action-sh-checker@master
|
||||||
env:
|
env:
|
||||||
|
|||||||
2
.github/workflows/taplo.yaml
vendored
2
.github/workflows/taplo.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Install build dependencies
|
- name: Install build dependencies
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@
|
|||||||
/rpm/SOURCES
|
/rpm/SOURCES
|
||||||
/.vscode
|
/.vscode
|
||||||
/vendor
|
/vendor
|
||||||
|
__pycache__
|
||||||
|
|||||||
6
.gitlint
6
.gitlint
@@ -1,6 +1,7 @@
|
|||||||
[general]
|
[general]
|
||||||
extra-path=scripts/gitlint/rules.py
|
extra-path=scripts/gitlint/rules
|
||||||
regex-style-search=true
|
regex-style-search=true
|
||||||
|
ignore=body-max-line-length
|
||||||
|
|
||||||
[ignore-by-author-name]
|
[ignore-by-author-name]
|
||||||
regex=dependabot
|
regex=dependabot
|
||||||
@@ -10,6 +11,3 @@ ignore=all
|
|||||||
[title-max-length]
|
[title-max-length]
|
||||||
line-length=72
|
line-length=72
|
||||||
|
|
||||||
# default 80
|
|
||||||
[body-max-line-length]
|
|
||||||
line-length=72
|
|
||||||
|
|||||||
27
.lychee.toml
Normal file
27
.lychee.toml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
verbose = "info"
|
||||||
|
|
||||||
|
exclude = [
|
||||||
|
# Availability of links below should be manually verified.
|
||||||
|
# Page for intel TDX support, returns 403 while querying.
|
||||||
|
'^https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html',
|
||||||
|
# Page for TPM, returns 403 while querying.
|
||||||
|
'^https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p05p_r14_pub.pdf',
|
||||||
|
|
||||||
|
# GitHub user smibarber referenced in `CREDITS.md` no longer exist
|
||||||
|
'^https://github.com/smibarber',
|
||||||
|
|
||||||
|
# OSDev has added bot protection and accesses my result in 403 Forbidden.
|
||||||
|
'^https://wiki.osdev.org',
|
||||||
|
# Exclude all pages with $ in the URL since $XXX is a variable
|
||||||
|
"\\$.*",
|
||||||
|
# Exclude local files
|
||||||
|
"file://.*",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Exclude loopback addresses
|
||||||
|
exclude_loopback = true
|
||||||
|
|
||||||
|
|
||||||
|
max_retries = 3
|
||||||
|
|
||||||
|
retry_wait_time = 5
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
edition = "2021"
|
edition = "2024"
|
||||||
group_imports="StdExternalCrate"
|
group_imports="StdExternalCrate"
|
||||||
imports_granularity="Module"
|
imports_granularity="Module"
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ extend-exclude = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[default.extend-words]
|
[default.extend-words]
|
||||||
ba = "ba"
|
|
||||||
CLASSE = "CLASSE"
|
CLASSE = "CLASSE"
|
||||||
conectix = "conectix"
|
|
||||||
Dake = "Dake"
|
Dake = "Dake"
|
||||||
EXTINT = "EXTINT"
|
EXTINT = "EXTINT"
|
||||||
INOUT = "INOUT"
|
INOUT = "INOUT"
|
||||||
|
SME = "SME" # Secure Memory Encryption
|
||||||
|
THR = "THR" # Transmitter Holding Register
|
||||||
|
TRANSLATER = "TRANSLATER"
|
||||||
|
ba = "ba"
|
||||||
|
conectix = "conectix"
|
||||||
liness = "liness"
|
liness = "liness"
|
||||||
outout = "outout"
|
outout = "outout"
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ License](https://opensource.org/licenses/Apache-2.0).
|
|||||||
|
|
||||||
## Coding Style
|
## Coding Style
|
||||||
|
|
||||||
We follow the [Rust Style](https://github.com/rust-dev-tools/fmt-rfcs/blob/master/guide/guide.md)
|
We follow the [Rust Style](https://github.com/rust-lang/rust/tree/HEAD/src/doc/style-guide/src)
|
||||||
convention and enforce it through the Continuous Integration (CI) process calling into `rustfmt`
|
convention and enforce it through the Continuous Integration (CI) process calling into `rustfmt`
|
||||||
for each submitted Pull Request (PR).
|
for each submitted Pull Request (PR).
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ commit you make.
|
|||||||
|
|
||||||
## Certificate of Origin
|
## Certificate of Origin
|
||||||
|
|
||||||
In order to get a clear contribution chain of trust we use the [signed-off-by language](https://01.org/community/signed-process)
|
In order to get a clear contribution chain of trust we use the [signed-off-by language](https://web.archive.org/web/20230406041855/https://01.org/community/signed-process)
|
||||||
used by the Linux kernel project.
|
used by the Linux kernel project.
|
||||||
|
|
||||||
## Patch format
|
## Patch format
|
||||||
@@ -79,12 +79,12 @@ you want to merge your changes to `cloud-hypervisor`:
|
|||||||
|
|
||||||
1. Fork the [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) project
|
1. Fork the [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) project
|
||||||
into your github organization.
|
into your github organization.
|
||||||
2. Within your fork, create a branch for your contribution.
|
1. Within your fork, create a branch for your contribution.
|
||||||
3. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
|
1. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
|
||||||
against the main branch of the Cloud Hypervisor repository.
|
against the main branch of the Cloud Hypervisor repository.
|
||||||
4. To update your pull request amend existing commits whenever applicable and
|
1. To update your pull request amend existing commits whenever applicable and
|
||||||
then push the new changes to your pull request branch.
|
then push the new changes to your pull request branch.
|
||||||
5. Once the pull request is approved it can be integrated.
|
1. Once the pull request is approved it can be integrated.
|
||||||
|
|
||||||
## Issue tracking
|
## Issue tracking
|
||||||
|
|
||||||
@@ -114,3 +114,13 @@ Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
|
|||||||
|
|
||||||
Then, after the corresponding PR is merged, GitHub will automatically close that issue when parsing the
|
Then, after the corresponding PR is merged, GitHub will automatically close that issue when parsing the
|
||||||
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
||||||
|
|
||||||
|
## AI Generated Code
|
||||||
|
|
||||||
|
Our policy is to decline any contributions known to contain contents
|
||||||
|
generated or derived from using Large Language Models (LLMs). This
|
||||||
|
includes ChatGPT, Gemini, Claude, Copilot and similar tools.
|
||||||
|
|
||||||
|
The goal is to avoid ambiguity in license compliance and optimize the
|
||||||
|
use of limited project resources, especially for code review and
|
||||||
|
maintenance. This policy can be revisited as LLMs evolve and mature.
|
||||||
|
|||||||
1187
Cargo.lock
generated
1187
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
99
Cargo.toml
99
Cargo.toml
@@ -3,19 +3,19 @@ authors = ["The Cloud Hypervisor Authors"]
|
|||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
default-run = "cloud-hypervisor"
|
default-run = "cloud-hypervisor"
|
||||||
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV"
|
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||||
license = "Apache-2.0 AND BSD-3-Clause"
|
license = "Apache-2.0 AND BSD-3-Clause"
|
||||||
name = "cloud-hypervisor"
|
name = "cloud-hypervisor"
|
||||||
version = "42.0.0"
|
version = "48.0.0"
|
||||||
# Minimum buildable version:
|
# Minimum buildable version:
|
||||||
# Keep in sync with version in .github/workflows/build.yaml
|
# Keep in sync with version in .github/workflows/build.yaml
|
||||||
# Policy on MSRV (see #4318):
|
# Policy on MSRV (see #4318):
|
||||||
# Can only be bumped by:
|
# Can only be bumped if satisfying any of the following:
|
||||||
# a.) A dependency requires it,
|
# a.) A dependency requires it,
|
||||||
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
||||||
# c.) There is a security issue that is addressed by the toolchain update.
|
# c.) There is a security issue that is addressed by the toolchain update.
|
||||||
rust-version = "1.77.0"
|
rust-version = "1.88.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
@@ -29,34 +29,34 @@ inherits = "release"
|
|||||||
strip = false
|
strip = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.87"
|
anyhow = { workspace = true }
|
||||||
api_client = { path = "api_client" }
|
api_client = { path = "api_client" }
|
||||||
clap = { version = "4.5.13", features = ["string"] }
|
clap = { workspace = true, features = ["string"] }
|
||||||
dhat = { version = "0.3.3", optional = true }
|
dhat = { workspace = true, optional = true }
|
||||||
epoll = "4.3.3"
|
env_logger = { workspace = true }
|
||||||
|
epoll = { workspace = true }
|
||||||
event_monitor = { path = "event_monitor" }
|
event_monitor = { path = "event_monitor" }
|
||||||
hypervisor = { path = "hypervisor" }
|
hypervisor = { path = "hypervisor" }
|
||||||
libc = "0.2.158"
|
libc = { workspace = true }
|
||||||
log = { version = "0.4.22", features = ["std"] }
|
log = { workspace = true, features = ["std"] }
|
||||||
option_parser = { path = "option_parser" }
|
option_parser = { path = "option_parser" }
|
||||||
seccompiler = { workspace = true }
|
seccompiler = { workspace = true }
|
||||||
serde_json = "1.0.120"
|
serde_json = { workspace = true }
|
||||||
signal-hook = "0.3.17"
|
signal-hook = { workspace = true }
|
||||||
thiserror = "1.0.62"
|
thiserror = { workspace = true }
|
||||||
tpm = { path = "tpm" }
|
tpm = { path = "tpm" }
|
||||||
tracer = { path = "tracer" }
|
tracer = { path = "tracer" }
|
||||||
vm-memory = { workspace = true }
|
vm-memory = { workspace = true }
|
||||||
vmm = { path = "vmm" }
|
vmm = { path = "vmm" }
|
||||||
vmm-sys-util = { workspace = true }
|
vmm-sys-util = { workspace = true }
|
||||||
zbus = { version = "4.4.0", optional = true }
|
zbus = { version = "5.7.1", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dirs = "5.0.1"
|
dirs = { workspace = true }
|
||||||
net_util = { path = "net_util" }
|
net_util = { path = "net_util" }
|
||||||
once_cell = "1.20.2"
|
serde_json = { workspace = true }
|
||||||
serde_json = "1.0.120"
|
|
||||||
test_infra = { path = "test_infra" }
|
test_infra = { path = "test_infra" }
|
||||||
wait-timeout = "0.2.0"
|
wait-timeout = { workspace = true }
|
||||||
|
|
||||||
# Please adjust `vmm::feature_list()` accordingly when changing the
|
# Please adjust `vmm::feature_list()` accordingly when changing the
|
||||||
# feature list below
|
# feature list below
|
||||||
@@ -64,9 +64,11 @@ wait-timeout = "0.2.0"
|
|||||||
dbus_api = ["vmm/dbus_api", "zbus"]
|
dbus_api = ["vmm/dbus_api", "zbus"]
|
||||||
default = ["io_uring", "kvm"]
|
default = ["io_uring", "kvm"]
|
||||||
dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling
|
dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling
|
||||||
|
fw_cfg = ["vmm/fw_cfg"]
|
||||||
guest_debug = ["vmm/guest_debug"]
|
guest_debug = ["vmm/guest_debug"]
|
||||||
igvm = ["mshv", "vmm/igvm"]
|
igvm = ["mshv", "vmm/igvm"]
|
||||||
io_uring = ["vmm/io_uring"]
|
io_uring = ["vmm/io_uring"]
|
||||||
|
ivshmem = ["vmm/ivshmem"]
|
||||||
kvm = ["vmm/kvm"]
|
kvm = ["vmm/kvm"]
|
||||||
mshv = ["vmm/mshv"]
|
mshv = ["vmm/mshv"]
|
||||||
pvmemcontrol = ["vmm/pvmemcontrol"]
|
pvmemcontrol = ["vmm/pvmemcontrol"]
|
||||||
@@ -100,21 +102,54 @@ members = [
|
|||||||
"vm-virtio",
|
"vm-virtio",
|
||||||
"vmm",
|
"vmm",
|
||||||
]
|
]
|
||||||
|
package.edition = "2024"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
# rust-vmm crates
|
||||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||||
kvm-bindings = "0.9.1"
|
kvm-bindings = "0.12.0"
|
||||||
kvm-ioctls = "0.18.0"
|
kvm-ioctls = "0.22.0"
|
||||||
linux-loader = "0.12.0"
|
# TODO: update to 0.13.1+
|
||||||
mshv-bindings = { git = "https://github.com/rust-vmm/mshv", tag = "v0.3.0" }
|
linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "main" }
|
||||||
mshv-ioctls = { git = "https://github.com/rust-vmm/mshv", tag = "v0.3.0" }
|
mshv-bindings = "0.6.0"
|
||||||
seccompiler = "0.4.0"
|
mshv-ioctls = "0.6.0"
|
||||||
vfio-bindings = { git = "https://github.com/rust-vmm/vfio", branch = "main" }
|
seccompiler = "0.5.0"
|
||||||
vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false }
|
vfio-bindings = { version = "0.6.0", default-features = false }
|
||||||
vfio_user = { git = "https://github.com/rust-vmm/vfio-user", branch = "main" }
|
vfio-ioctls = { version = "0.5.1", default-features = false }
|
||||||
vhost = "0.12.0"
|
vfio_user = { version = "0.1.1", default-features = false }
|
||||||
virtio-bindings = "0.2.2"
|
vhost = { version = "0.14.0", default-features = false }
|
||||||
virtio-queue = "0.13.0"
|
vhost-user-backend = { version = "0.20.0", default-features = false }
|
||||||
|
virtio-bindings = "0.2.6"
|
||||||
|
virtio-queue = "0.16.0"
|
||||||
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
||||||
vm-memory = "0.15.0"
|
vm-memory = "0.16.1"
|
||||||
vmm-sys-util = "0.12.1"
|
vmm-sys-util = "0.14.0"
|
||||||
|
|
||||||
|
# igvm crates
|
||||||
|
# TODO: bump to 0.3.5 release
|
||||||
|
igvm = { git = "https://github.com/microsoft/igvm", branch = "main" }
|
||||||
|
igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" }
|
||||||
|
|
||||||
|
# serde crates
|
||||||
|
serde = "1.0.208"
|
||||||
|
serde_json = "1.0.143"
|
||||||
|
serde_with = { version = "3.14.0", default-features = false }
|
||||||
|
|
||||||
|
# other crates
|
||||||
|
anyhow = "1.0.98"
|
||||||
|
bitflags = "2.9.4"
|
||||||
|
byteorder = "1.5.0"
|
||||||
|
cfg-if = "1.0.0"
|
||||||
|
clap = "4.5.47"
|
||||||
|
dhat = "0.3.3"
|
||||||
|
dirs = "6.0.0"
|
||||||
|
env_logger = "0.11.8"
|
||||||
|
epoll = "4.3.3"
|
||||||
|
flume = "0.11.1"
|
||||||
|
libc = "0.2.167"
|
||||||
|
log = "0.4.22"
|
||||||
|
signal-hook = "0.3.18"
|
||||||
|
thiserror = "2.0.12"
|
||||||
|
uuid = { version = "1.18.1" }
|
||||||
|
wait-timeout = "0.2.1"
|
||||||
|
zerocopy = { version = "0.8.26", default-features = false }
|
||||||
|
|||||||
38
README.md
38
README.md
@@ -59,9 +59,13 @@ based on the [Rust VMM](https://github.com/rust-vmm) crates.
|
|||||||
|
|
||||||
### Architectures
|
### Architectures
|
||||||
|
|
||||||
Cloud Hypervisor supports the `x86-64` and `AArch64` architectures. There are
|
Cloud Hypervisor supports the `x86-64`, `AArch64` and `riscv64`
|
||||||
minor differences in functionality between the two architectures
|
architectures, with functionality varying across these platforms. The
|
||||||
(see [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125)).
|
functionality differences between `x86-64` and `AArch64` are documented
|
||||||
|
in [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125).
|
||||||
|
The `riscv64` architecture support is experimental and offers limited
|
||||||
|
functionality. For more details and instructions, please refer to [riscv
|
||||||
|
documentation](docs/riscv.md).
|
||||||
|
|
||||||
### Guest OS
|
### Guest OS
|
||||||
|
|
||||||
@@ -149,7 +153,7 @@ interface will be enabled as per `network-config` details.
|
|||||||
$ 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 \
|
--firmware ./hypervisor-fw \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
@@ -171,6 +175,18 @@ $ ./cloud-hypervisor \
|
|||||||
--console off
|
--console off
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Booting: `--firmware` vs `--kernel`
|
||||||
|
|
||||||
|
The following scenarios are supported by Cloud Hypervisor to bootstrap a VM, i.e.,
|
||||||
|
to load a payload/bootitem(s):
|
||||||
|
|
||||||
|
- Provide firmware
|
||||||
|
- Provide kernel \[+ cmdline\]\ [+ initrd\]
|
||||||
|
|
||||||
|
Please note that our Cloud Hypervisor firmware (`hypervisor-fw`) has a Xen PVH
|
||||||
|
boot entry, therefore it can also be booted via the `--kernel` parameter, as
|
||||||
|
seen in some examples.
|
||||||
|
|
||||||
### Custom Kernel and Disk Image
|
### Custom Kernel and Disk Image
|
||||||
|
|
||||||
#### Building your Kernel
|
#### Building your Kernel
|
||||||
@@ -181,14 +197,9 @@ 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-6.2 linux-cloud-hypervisor
|
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.12.8 linux-cloud-hypervisor
|
||||||
$ pushd linux-cloud-hypervisor
|
$ pushd linux-cloud-hypervisor
|
||||||
# Use the x86-64 cloud-hypervisor kernel config to build your kernel for x86-64
|
$ make ch_defconfig
|
||||||
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-x86_64
|
|
||||||
# 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
|
# 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
|
# Do native build of the AArch64 kernel
|
||||||
@@ -302,8 +313,9 @@ Further details can be found in the [release documentation](docs/releases.md).
|
|||||||
As of 2023-01-03, the following cloud images are supported:
|
As of 2023-01-03, the following cloud images are supported:
|
||||||
|
|
||||||
- [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,arm64}.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,arm64}.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/))
|
- [Ubuntu Noble](https://cloud-images.ubuntu.com/noble/current/) (noble-server-cloudimg-{amd64,arm64}.img)
|
||||||
|
- [Fedora 36](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/) ([Fedora-Cloud-Base-36-1.5.x86_64.raw.xz](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/x86_64/images/) / [Fedora-Cloud-Base-36-1.5.aarch64.raw.xz](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/aarch64/images/))
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
name = "api_client"
|
name = "api_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1.0.62"
|
thiserror = { workspace = true }
|
||||||
vmm-sys-util = { workspace = true }
|
vmm-sys-util = { workspace = true }
|
||||||
|
|||||||
@@ -11,18 +11,24 @@ use vmm_sys_util::sock_ctrl_msg::ScmSocket;
|
|||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Error writing to or reading from HTTP socket: {0}")]
|
#[error("Error writing to or reading from HTTP socket")]
|
||||||
Socket(std::io::Error),
|
Socket(#[source] std::io::Error),
|
||||||
#[error("Error sending file descriptors: {0}")]
|
#[error("Error sending file descriptors")]
|
||||||
SocketSendFds(vmm_sys_util::errno::Error),
|
SocketSendFds(#[source] vmm_sys_util::errno::Error),
|
||||||
#[error("Error parsing HTTP status code: {0}")]
|
#[error("Error parsing HTTP status code")]
|
||||||
StatusCodeParsing(std::num::ParseIntError),
|
StatusCodeParsing(#[source] std::num::ParseIntError),
|
||||||
#[error("HTTP output is missing protocol statement")]
|
#[error("HTTP output is missing protocol statement")]
|
||||||
MissingProtocol,
|
MissingProtocol,
|
||||||
#[error("Error parsing HTTP Content-Length field: {0}")]
|
#[error("Error parsing HTTP Content-Length field")]
|
||||||
ContentLengthParsing(std::num::ParseIntError),
|
ContentLengthParsing(#[source] std::num::ParseIntError),
|
||||||
#[error("Server responded with an error: {0:?}: {1:?}")]
|
#[error("Server responded with error {0:?}: {1:?}")]
|
||||||
ServerResponse(StatusCode, Option<String>),
|
ServerResponse(
|
||||||
|
StatusCode,
|
||||||
|
// TODO: Move `api` module from `vmm` to dedicated crate and use a common type definition
|
||||||
|
Option<
|
||||||
|
String, /* Untyped: Currently Vec<String> of error messages from top to root cause */
|
||||||
|
>,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
@@ -32,6 +38,7 @@ pub enum StatusCode {
|
|||||||
NoContent,
|
NoContent,
|
||||||
BadRequest,
|
BadRequest,
|
||||||
NotFound,
|
NotFound,
|
||||||
|
TooManyRequests,
|
||||||
InternalServerError,
|
InternalServerError,
|
||||||
NotImplemented,
|
NotImplemented,
|
||||||
Unknown,
|
Unknown,
|
||||||
@@ -45,6 +52,7 @@ impl StatusCode {
|
|||||||
204 => StatusCode::NoContent,
|
204 => StatusCode::NoContent,
|
||||||
400 => StatusCode::BadRequest,
|
400 => StatusCode::BadRequest,
|
||||||
404 => StatusCode::NotFound,
|
404 => StatusCode::NotFound,
|
||||||
|
429 => StatusCode::TooManyRequests,
|
||||||
500 => StatusCode::InternalServerError,
|
500 => StatusCode::InternalServerError,
|
||||||
501 => StatusCode::NotImplemented,
|
501 => StatusCode::NotImplemented,
|
||||||
_ => StatusCode::Unknown,
|
_ => StatusCode::Unknown,
|
||||||
@@ -110,12 +118,11 @@ fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(body_offset) = body_offset {
|
if let Some(body_offset) = body_offset
|
||||||
if let Some(content_length) = content_length {
|
&& let Some(content_length) = content_length
|
||||||
if res.len() >= content_length + body_offset {
|
&& res.len() >= content_length + body_offset
|
||||||
break;
|
{
|
||||||
}
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..])));
|
let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..])));
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Chromium OS Authors"]
|
authors = ["The Chromium OS Authors"]
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
name = "arch"
|
name = "arch"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
fw_cfg = []
|
||||||
kvm = ["hypervisor/kvm"]
|
kvm = ["hypervisor/kvm"]
|
||||||
sev_snp = []
|
sev_snp = []
|
||||||
tdx = []
|
tdx = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.87"
|
anyhow = { workspace = true }
|
||||||
byteorder = "1.5.0"
|
byteorder = { workspace = true }
|
||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
libc = "0.2.158"
|
libc = { workspace = true }
|
||||||
linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] }
|
linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] }
|
||||||
log = "0.4.22"
|
log = { workspace = true }
|
||||||
serde = { version = "1.0.208", features = ["derive", "rc"] }
|
serde = { workspace = true, features = ["derive", "rc"] }
|
||||||
thiserror = "1.0.62"
|
thiserror = { workspace = true }
|
||||||
uuid = "1.8.0"
|
uuid = { workspace = true }
|
||||||
vm-memory = { workspace = true, features = ["backend-bitmap", "backend-mmap"] }
|
vm-memory = { workspace = true, features = ["backend-bitmap", "backend-mmap"] }
|
||||||
vm-migration = { path = "../vm-migration" }
|
|
||||||
vmm-sys-util = { workspace = true, features = ["with-serde"] }
|
vmm-sys-util = { workspace = true, features = ["with-serde"] }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
||||||
fdt_parser = { version = "0.1.5", package = "fdt" }
|
fdt_parser = { version = "0.1.5", package = "fdt" }
|
||||||
vm-fdt = { workspace = true }
|
vm-fdt = { workspace = true }
|
||||||
|
|||||||
@@ -15,14 +15,18 @@ use std::{cmp, fs, result, str};
|
|||||||
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use hypervisor::arch::aarch64::gic::Vgic;
|
use hypervisor::arch::aarch64::gic::Vgic;
|
||||||
|
use hypervisor::arch::aarch64::regs::{
|
||||||
|
AARCH64_ARCH_TIMER_HYP_IRQ, AARCH64_ARCH_TIMER_PHYS_NONSECURE_IRQ,
|
||||||
|
AARCH64_ARCH_TIMER_PHYS_SECURE_IRQ, AARCH64_ARCH_TIMER_VIRT_IRQ, AARCH64_PMU_IRQ,
|
||||||
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
||||||
|
|
||||||
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
||||||
use super::layout::{
|
use super::layout::{
|
||||||
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
GIC_V2M_COMPATIBLE, IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE,
|
||||||
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
MEM_PCI_IO_START, PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT, SPI_BASE, SPI_NUM,
|
||||||
};
|
};
|
||||||
use crate::{NumaNodes, PciSpaceInfo};
|
use crate::{NumaNodes, PciSpaceInfo};
|
||||||
|
|
||||||
@@ -59,9 +63,6 @@ const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
|
|||||||
const IRQ_TYPE_EDGE_RISING: u32 = 1;
|
const IRQ_TYPE_EDGE_RISING: u32 = 1;
|
||||||
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
||||||
|
|
||||||
// PMU PPI interrupt number
|
|
||||||
pub const AARCH64_PMU_IRQ: u32 = 7;
|
|
||||||
|
|
||||||
// Keys and Buttons
|
// Keys and Buttons
|
||||||
// System Power Down
|
// System Power Down
|
||||||
const KEY_POWER: u32 = 116;
|
const KEY_POWER: u32 = 116;
|
||||||
@@ -80,8 +81,8 @@ pub trait DeviceInfoForFdt {
|
|||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failure in writing FDT in memory.
|
/// Failure in writing FDT in memory.
|
||||||
#[error("Failure in writing FDT in memory: {0}")]
|
#[error("Failure in writing FDT in memory")]
|
||||||
WriteFdtToMemory(GuestMemoryError),
|
WriteFdtToMemory(#[source] GuestMemoryError),
|
||||||
}
|
}
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
@@ -109,11 +110,8 @@ pub fn get_cache_size(cache_level: CacheLevel) -> u32 {
|
|||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
let file_path = Path::new(&file_directory);
|
||||||
if !file_path.exists() {
|
if !file_path.exists() {
|
||||||
warn!("File: {} does not exist.", file_directory);
|
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
info!("File: {} exist.", file_directory);
|
|
||||||
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
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"
|
// The content of the file is as simple as a size, like: "32K"
|
||||||
let src = src.trim();
|
let src = src.trim();
|
||||||
@@ -143,11 +141,8 @@ pub fn get_cache_coherency_line_size(cache_level: CacheLevel) -> u32 {
|
|||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
let file_path = Path::new(&file_directory);
|
||||||
if !file_path.exists() {
|
if !file_path.exists() {
|
||||||
warn!("File: {} does not exist.", file_directory);
|
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
info!("File: {} exist.", file_directory);
|
|
||||||
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||||
src.trim().parse::<u32>().unwrap()
|
src.trim().parse::<u32>().unwrap()
|
||||||
}
|
}
|
||||||
@@ -166,11 +161,8 @@ pub fn get_cache_number_of_sets(cache_level: CacheLevel) -> u32 {
|
|||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
let file_path = Path::new(&file_directory);
|
||||||
if !file_path.exists() {
|
if !file_path.exists() {
|
||||||
warn!("File: {} does not exist.", file_directory);
|
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
info!("File: {} exist.", file_directory);
|
|
||||||
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||||
src.trim().parse::<u32>().unwrap()
|
src.trim().parse::<u32>().unwrap()
|
||||||
}
|
}
|
||||||
@@ -194,11 +186,8 @@ pub fn get_cache_shared(cache_level: CacheLevel) -> bool {
|
|||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
let file_path = Path::new(&file_directory);
|
||||||
if !file_path.exists() {
|
if !file_path.exists() {
|
||||||
warn!("File: {} does not exist.", file_directory);
|
|
||||||
result = false;
|
result = false;
|
||||||
} else {
|
} else {
|
||||||
info!("File: {} exist.", file_directory);
|
|
||||||
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||||
let src = src.trim();
|
let src = src.trim();
|
||||||
if src.is_empty() {
|
if src.is_empty() {
|
||||||
@@ -217,7 +206,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
|||||||
guest_mem: &GuestMemoryMmap,
|
guest_mem: &GuestMemoryMmap,
|
||||||
cmdline: &str,
|
cmdline: &str,
|
||||||
vcpu_mpidr: Vec<u64>,
|
vcpu_mpidr: Vec<u64>,
|
||||||
vcpu_topology: Option<(u8, u8, u8)>,
|
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
||||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||||
gic_device: &Arc<Mutex<dyn Vgic>>,
|
gic_device: &Arc<Mutex<dyn Vgic>>,
|
||||||
initrd: &Option<InitramfsConfig>,
|
initrd: &Option<InitramfsConfig>,
|
||||||
@@ -230,8 +219,8 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
|||||||
let mut fdt = FdtWriter::new().unwrap();
|
let mut fdt = FdtWriter::new().unwrap();
|
||||||
|
|
||||||
// For an explanation why these nodes were introduced in the blob take a look at
|
// For an explanation why these nodes were introduced in the blob take a look at
|
||||||
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L845
|
// the "Device Node Requirements" chapter of the Devicetree Specification.
|
||||||
// Look for "Required nodes and properties".
|
// https://www.devicetree.org/specifications/
|
||||||
|
|
||||||
// Header or the root node as per above mentioned documentation.
|
// Header or the root node as per above mentioned documentation.
|
||||||
let root_node = fdt.begin_node("")?;
|
let root_node = fdt.begin_node("")?;
|
||||||
@@ -279,7 +268,7 @@ pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> R
|
|||||||
fn create_cpu_nodes(
|
fn create_cpu_nodes(
|
||||||
fdt: &mut FdtWriter,
|
fdt: &mut FdtWriter,
|
||||||
vcpu_mpidr: &[u64],
|
vcpu_mpidr: &[u64],
|
||||||
vcpu_topology: Option<(u8, u8, u8)>,
|
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
||||||
numa_nodes: &NumaNodes,
|
numa_nodes: &NumaNodes,
|
||||||
) -> FdtWriterResult<()> {
|
) -> FdtWriterResult<()> {
|
||||||
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/arm/cpus.yaml.
|
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/arm/cpus.yaml.
|
||||||
@@ -288,8 +277,11 @@ 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, cores_per_package, packages) = vcpu_topology.unwrap_or((1, 1, 1));
|
let (threads_per_core, cores_per_die, dies_per_package, packages) =
|
||||||
let max_cpus: u32 = (threads_per_core * cores_per_package * packages).into();
|
vcpu_topology.unwrap_or((1, 1, 1, 1));
|
||||||
|
let cores_per_package = cores_per_die * dies_per_package;
|
||||||
|
let max_cpus: u32 =
|
||||||
|
threads_per_core as u32 * cores_per_die as u32 * dies_per_package as u32 * packages as u32;
|
||||||
|
|
||||||
// Add cache info.
|
// Add cache info.
|
||||||
// L1 Data Cache Info.
|
// L1 Data Cache Info.
|
||||||
@@ -321,7 +313,6 @@ fn create_cpu_nodes(
|
|||||||
if !cache_exist {
|
if !cache_exist {
|
||||||
warn!("cache sysfs system does not exist.");
|
warn!("cache sysfs system does not exist.");
|
||||||
} else {
|
} else {
|
||||||
info!("cache sysfs system exists.");
|
|
||||||
// L1 Data Cache Info.
|
// L1 Data Cache Info.
|
||||||
l1_d_cache_size = get_cache_size(CacheLevel::L1D);
|
l1_d_cache_size = get_cache_size(CacheLevel::L1D);
|
||||||
l1_d_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1D);
|
l1_d_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1D);
|
||||||
@@ -369,7 +360,7 @@ fn create_cpu_nodes(
|
|||||||
if numa_nodes.len() > 1 {
|
if numa_nodes.len() > 1 {
|
||||||
for numa_node_idx in 0..numa_nodes.len() {
|
for numa_node_idx in 0..numa_nodes.len() {
|
||||||
let numa_node = numa_nodes.get(&(numa_node_idx as u32));
|
let numa_node = numa_nodes.get(&(numa_node_idx as u32));
|
||||||
if numa_node.unwrap().cpus.contains(&(cpu_id as u8)) {
|
if numa_node.unwrap().cpus.contains(&(cpu_id as u32)) {
|
||||||
fdt.property_u32("numa-node-id", numa_node_idx as u32)?;
|
fdt.property_u32("numa-node-id", numa_node_idx as u32)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -422,9 +413,6 @@ fn create_cpu_nodes(
|
|||||||
|
|
||||||
fdt.end_node(l2_cache_node)?;
|
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)?;
|
||||||
@@ -461,7 +449,8 @@ fn create_cpu_nodes(
|
|||||||
}
|
}
|
||||||
|
|
||||||
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_die, dies_per_package, packages) = topology;
|
||||||
|
let cores_per_package = cores_per_die * dies_per_package;
|
||||||
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.
|
||||||
@@ -585,15 +574,14 @@ fn create_memory_node(
|
|||||||
&& (first_region_end <= &mem_32bit_reserved_start))
|
&& (first_region_end <= &mem_32bit_reserved_start))
|
||||||
{
|
{
|
||||||
panic!(
|
panic!(
|
||||||
"Unexpected first memory region layout: (start: 0x{:08x}, end: 0x{:08x}).
|
"Unexpected first memory region layout: (start: 0x{first_region_start:08x}, end: 0x{first_region_end:08x}).
|
||||||
ram_start: 0x{:08x}, mem_32bit_reserved_start: 0x{:08x}",
|
ram_start: 0x{ram_start:08x}, mem_32bit_reserved_start: 0x{mem_32bit_reserved_start:08x}"
|
||||||
first_region_start, first_region_end, ram_start, mem_32bit_reserved_start
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mem_size = first_region_end - ram_start;
|
let mem_size = first_region_end - ram_start;
|
||||||
let mem_reg_prop = [ram_start, mem_size];
|
let mem_reg_prop = [ram_start, mem_size];
|
||||||
let memory_node_name = format!("memory@{:x}", ram_start);
|
let memory_node_name = format!("memory@{ram_start: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)?;
|
||||||
@@ -606,14 +594,13 @@ fn create_memory_node(
|
|||||||
|
|
||||||
if second_region_start != &ram_64bit_start {
|
if second_region_start != &ram_64bit_start {
|
||||||
panic!(
|
panic!(
|
||||||
"Unexpected second memory region layout: start: 0x{:08x}, ram_64bit_start: 0x{:08x}",
|
"Unexpected second memory region layout: start: 0x{second_region_start:08x}, ram_64bit_start: 0x{ram_64bit_start:08x}"
|
||||||
second_region_start, ram_64bit_start
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mem_size = second_region_end - ram_64bit_start;
|
let mem_size = second_region_end - ram_64bit_start;
|
||||||
let mem_reg_prop = [ram_64bit_start, mem_size];
|
let mem_reg_prop = [ram_64bit_start, mem_size];
|
||||||
let memory_node_name = format!("memory@{:x}", ram_64bit_start);
|
let memory_node_name = format!("memory@{ram_64bit_start: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)?;
|
||||||
@@ -670,11 +657,19 @@ fn create_gic_node(fdt: &mut FdtWriter, gic_device: &Arc<Mutex<dyn Vgic>>) -> Fd
|
|||||||
|
|
||||||
if gic_device.lock().unwrap().msi_compatible() {
|
if gic_device.lock().unwrap().msi_compatible() {
|
||||||
let msic_node = fdt.begin_node("msic")?;
|
let msic_node = fdt.begin_node("msic")?;
|
||||||
fdt.property_string("compatible", gic_device.lock().unwrap().msi_compatibility())?;
|
let msi_compatibility = gic_device.lock().unwrap().msi_compatibility().to_string();
|
||||||
|
|
||||||
|
fdt.property_string("compatible", msi_compatibility.as_str())?;
|
||||||
fdt.property_null("msi-controller")?;
|
fdt.property_null("msi-controller")?;
|
||||||
fdt.property_u32("phandle", MSI_PHANDLE)?;
|
fdt.property_u32("phandle", MSI_PHANDLE)?;
|
||||||
let msi_reg_prop = gic_device.lock().unwrap().msi_properties();
|
let msi_reg_prop = gic_device.lock().unwrap().msi_properties();
|
||||||
fdt.property_array_u64("reg", &msi_reg_prop)?;
|
fdt.property_array_u64("reg", &msi_reg_prop)?;
|
||||||
|
|
||||||
|
if msi_compatibility == GIC_V2M_COMPATIBLE {
|
||||||
|
fdt.property_u32("arm,msi-base-spi", SPI_BASE)?;
|
||||||
|
fdt.property_u32("arm,msi-num-spis", SPI_NUM)?;
|
||||||
|
}
|
||||||
|
|
||||||
fdt.end_node(msic_node)?;
|
fdt.end_node(msic_node)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -701,9 +696,14 @@ fn create_clock_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
|
|||||||
|
|
||||||
fn create_timer_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
|
fn create_timer_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
|
||||||
// See
|
// See
|
||||||
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/interrupt-controller/arch_timer.txt
|
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/timer/arm%2Carch_timer.yaml
|
||||||
// These are fixed interrupt numbers for the timer device.
|
// These are fixed interrupt numbers for the timer device.
|
||||||
let irqs = [13, 14, 11, 10];
|
let irqs = [
|
||||||
|
AARCH64_ARCH_TIMER_PHYS_SECURE_IRQ,
|
||||||
|
AARCH64_ARCH_TIMER_PHYS_NONSECURE_IRQ,
|
||||||
|
AARCH64_ARCH_TIMER_VIRT_IRQ,
|
||||||
|
AARCH64_ARCH_TIMER_HYP_IRQ,
|
||||||
|
];
|
||||||
let compatible = "arm,armv8-timer";
|
let compatible = "arm,armv8-timer";
|
||||||
|
|
||||||
let mut timer_reg_cells: Vec<u32> = Vec::new();
|
let mut timer_reg_cells: Vec<u32> = Vec::new();
|
||||||
@@ -838,6 +838,21 @@ fn create_gpio_node<T: DeviceInfoForFdt + Clone + Debug>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://www.kernel.org/doc/Documentation/devicetree/bindings/arm/fw-cfg.txt
|
||||||
|
#[cfg(feature = "fw_cfg")]
|
||||||
|
fn create_fw_cfg_node<T: DeviceInfoForFdt + Clone + Debug>(
|
||||||
|
fdt: &mut FdtWriter,
|
||||||
|
dev_info: &T,
|
||||||
|
) -> FdtWriterResult<()> {
|
||||||
|
// FwCfg node
|
||||||
|
let fw_cfg_node = fdt.begin_node(&format!("fw-cfg@{:x}", dev_info.addr()))?;
|
||||||
|
fdt.property("compatible", b"qemu,fw-cfg-mmio\0")?;
|
||||||
|
fdt.property_array_u64("reg", &[dev_info.addr(), dev_info.length()])?;
|
||||||
|
fdt.end_node(fw_cfg_node)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||||
fdt: &mut FdtWriter,
|
fdt: &mut FdtWriter,
|
||||||
dev_info: &HashMap<(DeviceType, String), T, S>,
|
dev_info: &HashMap<(DeviceType, String), T, S>,
|
||||||
@@ -853,6 +868,8 @@ fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Buil
|
|||||||
DeviceType::Virtio(_) => {
|
DeviceType::Virtio(_) => {
|
||||||
ordered_virtio_device.push(info);
|
ordered_virtio_device.push(info);
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "fw_cfg")]
|
||||||
|
DeviceType::FwCfg => create_fw_cfg_node(fdt, info)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -982,39 +999,39 @@ fn create_pci_nodes(
|
|||||||
fdt.property_array_u32("msi-map", &msi_map)?;
|
fdt.property_array_u32("msi-map", &msi_map)?;
|
||||||
fdt.property_u32("msi-parent", MSI_PHANDLE)?;
|
fdt.property_u32("msi-parent", MSI_PHANDLE)?;
|
||||||
|
|
||||||
if pci_device_info_elem.pci_segment_id == 0 {
|
if pci_device_info_elem.pci_segment_id == 0
|
||||||
if let Some(virtio_iommu_bdf) = virtio_iommu_bdf {
|
&& let Some(virtio_iommu_bdf) = virtio_iommu_bdf
|
||||||
// See kernel document Documentation/devicetree/bindings/pci/pci-iommu.txt
|
{
|
||||||
// for 'iommu-map' attribute setting.
|
// See kernel document Documentation/devicetree/bindings/pci/pci-iommu.txt
|
||||||
let iommu_map = [
|
// for 'iommu-map' attribute setting.
|
||||||
0_u32,
|
let iommu_map = [
|
||||||
VIRTIO_IOMMU_PHANDLE,
|
0_u32,
|
||||||
0_u32,
|
VIRTIO_IOMMU_PHANDLE,
|
||||||
virtio_iommu_bdf,
|
0_u32,
|
||||||
virtio_iommu_bdf + 1,
|
virtio_iommu_bdf,
|
||||||
VIRTIO_IOMMU_PHANDLE,
|
virtio_iommu_bdf + 1,
|
||||||
virtio_iommu_bdf + 1,
|
VIRTIO_IOMMU_PHANDLE,
|
||||||
0xffff - virtio_iommu_bdf,
|
virtio_iommu_bdf + 1,
|
||||||
];
|
0xffff - virtio_iommu_bdf,
|
||||||
fdt.property_array_u32("iommu-map", &iommu_map)?;
|
];
|
||||||
|
fdt.property_array_u32("iommu-map", &iommu_map)?;
|
||||||
|
|
||||||
// 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@{virtio_iommu_bdf:x}");
|
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")?;
|
||||||
|
|
||||||
// 'reg' is a five-cell address encoded as
|
// 'reg' is a five-cell address encoded as
|
||||||
// (phys.hi phys.mid phys.lo size.hi size.lo). phys.hi should contain the
|
// (phys.hi phys.mid phys.lo size.hi size.lo). phys.hi should contain the
|
||||||
// device's BDF as 0b00000000 bbbbbbbb dddddfff 00000000. The other cells
|
// device's BDF as 0b00000000 bbbbbbbb dddddfff 00000000. The other cells
|
||||||
// should be zero.
|
// should be zero.
|
||||||
let reg = [virtio_iommu_bdf << 8, 0_u32, 0_u32, 0_u32, 0_u32];
|
let reg = [virtio_iommu_bdf << 8, 0_u32, 0_u32, 0_u32, 0_u32];
|
||||||
fdt.property_array_u32("reg", ®)?;
|
fdt.property_array_u32("reg", ®)?;
|
||||||
fdt.property_u32("phandle", VIRTIO_IOMMU_PHANDLE)?;
|
fdt.property_u32("phandle", VIRTIO_IOMMU_PHANDLE)?;
|
||||||
|
|
||||||
fdt.end_node(virtio_iommu_node)?;
|
fdt.end_node(virtio_iommu_node)?;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fdt.end_node(pci_node)?;
|
fdt.end_node(pci_node)?;
|
||||||
@@ -1109,10 +1126,7 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
|||||||
// - At first, try to convert it to CStr and print,
|
// - At first, try to convert it to CStr and print,
|
||||||
// - If failed, print it as u32 array.
|
// - If failed, print it as u32 array.
|
||||||
let value_result = match CStr::from_bytes_with_nul(value) {
|
let value_result = match CStr::from_bytes_with_nul(value) {
|
||||||
Ok(value_cstr) => match value_cstr.to_str() {
|
Ok(value_cstr) => value_cstr.to_str().ok(),
|
||||||
Ok(value_str) => Some(value_str),
|
|
||||||
Err(_e) => None,
|
|
||||||
},
|
|
||||||
Err(_e) => None,
|
Err(_e) => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -138,3 +138,12 @@ pub const IRQ_BASE: u32 = 32;
|
|||||||
|
|
||||||
/// Number of supported interrupts
|
/// Number of supported interrupts
|
||||||
pub const IRQ_NUM: u32 = 256;
|
pub const IRQ_NUM: u32 = 256;
|
||||||
|
|
||||||
|
/// Base SPI interrupt number
|
||||||
|
pub const SPI_BASE: u32 = 32;
|
||||||
|
|
||||||
|
/// Total number of SPIs
|
||||||
|
pub const SPI_NUM: u32 = 64;
|
||||||
|
|
||||||
|
/// GICv2M compatible string
|
||||||
|
pub const GIC_V2M_COMPATIBLE: &str = "arm,gic-v2m-frame";
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
pub mod fdt;
|
pub mod fdt;
|
||||||
/// Layout for this aarch64 system.
|
/// Layout for this aarch64 system.
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
/// Module for system registers definition
|
|
||||||
pub mod regs;
|
|
||||||
/// Module for loading UEFI binary.
|
/// Module for loading UEFI binary.
|
||||||
pub mod uefi;
|
pub mod uefi;
|
||||||
|
|
||||||
@@ -16,7 +14,8 @@ use std::fmt::Debug;
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use hypervisor::arch::aarch64::gic::Vgic;
|
use hypervisor::arch::aarch64::gic::Vgic;
|
||||||
use log::{log_enabled, Level};
|
use hypervisor::arch::aarch64::regs::MPIDR_EL1;
|
||||||
|
use log::{Level, log_enabled};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
||||||
|
|
||||||
@@ -33,8 +32,8 @@ pub enum Error {
|
|||||||
SetupFdt,
|
SetupFdt,
|
||||||
|
|
||||||
/// Failed to write FDT to memory.
|
/// Failed to write FDT to memory.
|
||||||
#[error("Failed to write FDT to memory: {0}")]
|
#[error("Failed to write FDT to memory")]
|
||||||
WriteFdtToMemory(fdt::Error),
|
WriteFdtToMemory(#[source] fdt::Error),
|
||||||
|
|
||||||
/// Failed to create a GIC.
|
/// Failed to create a GIC.
|
||||||
#[error("Failed to create a GIC")]
|
#[error("Failed to create a GIC")]
|
||||||
@@ -45,24 +44,18 @@ pub enum Error {
|
|||||||
InitramfsAddress,
|
InitramfsAddress,
|
||||||
|
|
||||||
/// Error configuring the general purpose registers
|
/// Error configuring the general purpose registers
|
||||||
#[error("Error configuring the general purpose registers: {0}")]
|
#[error("Error configuring the general purpose registers")]
|
||||||
RegsConfiguration(hypervisor::HypervisorCpuError),
|
RegsConfiguration(#[source] hypervisor::HypervisorCpuError),
|
||||||
|
|
||||||
/// Error configuring the MPIDR register
|
/// Error configuring the MPIDR register
|
||||||
#[error("Error configuring the MPIDR register: {0}")]
|
#[error("Error configuring the MPIDR register")]
|
||||||
VcpuRegMpidr(hypervisor::HypervisorCpuError),
|
VcpuRegMpidr(#[source] hypervisor::HypervisorCpuError),
|
||||||
|
|
||||||
/// Error initializing PMU for vcpu
|
/// Error initializing PMU for vcpu
|
||||||
#[error("Error initializing PMU for vcpu")]
|
#[error("Error initializing PMU for vcpu")]
|
||||||
VcpuInitPmu,
|
VcpuInitPmu,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for super::Error {
|
|
||||||
fn from(e: Error) -> super::Error {
|
|
||||||
super::Error::PlatformSpecific(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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.
|
/// executing code.
|
||||||
@@ -74,7 +67,7 @@ pub struct EntryPoint {
|
|||||||
/// Configure the specified VCPU, and return its MPIDR.
|
/// Configure the specified VCPU, and return its MPIDR.
|
||||||
pub fn configure_vcpu(
|
pub fn configure_vcpu(
|
||||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||||
id: u8,
|
id: u32,
|
||||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
||||||
) -> super::Result<u64> {
|
) -> super::Result<u64> {
|
||||||
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
||||||
@@ -86,9 +79,7 @@ pub fn configure_vcpu(
|
|||||||
.map_err(Error::RegsConfiguration)?;
|
.map_err(Error::RegsConfiguration)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mpidr = vcpu
|
let mpidr = vcpu.get_sys_reg(MPIDR_EL1).map_err(Error::VcpuRegMpidr)?;
|
||||||
.get_sys_reg(regs::MPIDR_EL1)
|
|
||||||
.map_err(Error::VcpuRegMpidr)?;
|
|
||||||
Ok(mpidr)
|
Ok(mpidr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +126,7 @@ pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Bui
|
|||||||
guest_mem: &GuestMemoryMmap,
|
guest_mem: &GuestMemoryMmap,
|
||||||
cmdline: &str,
|
cmdline: &str,
|
||||||
vcpu_mpidr: Vec<u64>,
|
vcpu_mpidr: Vec<u64>,
|
||||||
vcpu_topology: Option<(u8, u8, u8)>,
|
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
||||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||||
initrd: &Option<super::InitramfsConfig>,
|
initrd: &Option<super::InitramfsConfig>,
|
||||||
pci_space_info: &[PciSpaceInfo],
|
pci_space_info: &[PciSpaceInfo],
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
// AArch64 system register encoding:
|
|
||||||
// See https://developer.arm.com/documentation/ddi0487 (chapter D12)
|
|
||||||
//
|
|
||||||
// 31 22 21 20 19 18 16 15 12 11 8 7 5 4 0
|
|
||||||
// +----------+---+-----+-----+-----+-----+-----+----+
|
|
||||||
// |1101010100| L | op0 | op1 | CRn | CRm | op2 | Rt |
|
|
||||||
// +----------+---+-----+-----+-----+-----+-----+----+
|
|
||||||
//
|
|
||||||
// Notes:
|
|
||||||
// - L and Rt are reserved as implementation defined fields, ignored.
|
|
||||||
|
|
||||||
const SYSREG_HEAD: u32 = 0b1101010100u32 << 22;
|
|
||||||
const SYSREG_OP0_SHIFT: u32 = 19;
|
|
||||||
const SYSREG_OP0_MASK: u32 = 0b11u32 << 19;
|
|
||||||
const SYSREG_OP1_SHIFT: u32 = 16;
|
|
||||||
const SYSREG_OP1_MASK: u32 = 0b111u32 << 16;
|
|
||||||
const SYSREG_CRN_SHIFT: u32 = 12;
|
|
||||||
const SYSREG_CRN_MASK: u32 = 0b1111u32 << 12;
|
|
||||||
const SYSREG_CRM_SHIFT: u32 = 8;
|
|
||||||
const SYSREG_CRM_MASK: u32 = 0b1111u32 << 8;
|
|
||||||
const SYSREG_OP2_SHIFT: u32 = 5;
|
|
||||||
const SYSREG_OP2_MASK: u32 = 0b111u32 << 5;
|
|
||||||
|
|
||||||
/// Define the ID of system registers
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! arm64_sys_reg {
|
|
||||||
($name: tt, $op0: tt, $op1: tt, $crn: tt, $crm: tt, $op2: tt) => {
|
|
||||||
pub const $name: u32 = SYSREG_HEAD
|
|
||||||
| ((($op0 as u32) << SYSREG_OP0_SHIFT) & SYSREG_OP0_MASK as u32)
|
|
||||||
| ((($op1 as u32) << SYSREG_OP1_SHIFT) & SYSREG_OP1_MASK as u32)
|
|
||||||
| ((($crn as u32) << SYSREG_CRN_SHIFT) & SYSREG_CRN_MASK as u32)
|
|
||||||
| ((($crm as u32) << SYSREG_CRM_SHIFT) & SYSREG_CRM_MASK as u32)
|
|
||||||
| ((($op2 as u32) << SYSREG_OP2_SHIFT) & SYSREG_OP2_MASK as u32);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
arm64_sys_reg!(MPIDR_EL1, 3, 0, 0, 0, 5);
|
|
||||||
arm64_sys_reg!(ID_AA64MMFR0_EL1, 3, 0, 0, 7, 0);
|
|
||||||
arm64_sys_reg!(TTBR1_EL1, 3, 0, 2, 0, 1);
|
|
||||||
arm64_sys_reg!(TCR_EL1, 3, 0, 2, 0, 2);
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||||
// Copyright © 2020, Oracle and/or its affiliates.
|
// Copyright © 2020, Oracle and/or its affiliates.
|
||||||
//
|
//
|
||||||
@@ -5,7 +6,7 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
//! Implements platform specific functionality.
|
//! Implements platform specific functionality.
|
||||||
//! Supported platforms: x86_64, aarch64.
|
//! Supported platforms: x86_64, aarch64, riscv64.
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
@@ -17,9 +18,6 @@ use std::{fmt, result};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
use crate::x86_64::SgxEpcSection;
|
|
||||||
|
|
||||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
|
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||||
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
|
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||||
|
|
||||||
@@ -27,11 +25,14 @@ type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitma
|
|||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
#[error("Platform specific error (x86_64): {0:?}")]
|
#[error("Platform specific error (x86_64)")]
|
||||||
PlatformSpecific(x86_64::Error),
|
PlatformSpecific(#[from] x86_64::Error),
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
#[error("Platform specific error (aarch64): {0:?}")]
|
#[error("Platform specific error (aarch64)")]
|
||||||
PlatformSpecific(aarch64::Error),
|
PlatformSpecific(#[from] aarch64::Error),
|
||||||
|
#[cfg(target_arch = "riscv64")]
|
||||||
|
#[error("Platform specific error (riscv64)")]
|
||||||
|
PlatformSpecific(#[from] riscv64::Error),
|
||||||
#[error("The memory map table extends past the end of guest memory")]
|
#[error("The memory map table extends past the end of guest memory")]
|
||||||
MemmapTablePastRamEnd,
|
MemmapTablePastRamEnd,
|
||||||
#[error("Error writing memory map table to guest memory")]
|
#[error("Error writing memory map table to guest memory")]
|
||||||
@@ -42,7 +43,7 @@ pub enum Error {
|
|||||||
StartInfoSetup,
|
StartInfoSetup,
|
||||||
#[error("Failed to compute initramfs address")]
|
#[error("Failed to compute initramfs address")]
|
||||||
InitramfsAddress,
|
InitramfsAddress,
|
||||||
#[error("Error writing module entry to guest memory: {0}")]
|
#[error("Error writing module entry to guest memory")]
|
||||||
ModlistSetup(#[source] vm_memory::GuestMemoryError),
|
ModlistSetup(#[source] vm_memory::GuestMemoryError),
|
||||||
#[error("RSDP extends past the end of guest memory")]
|
#[error("RSDP extends past the end of guest memory")]
|
||||||
RsdpPastRamEnd,
|
RsdpPastRamEnd,
|
||||||
@@ -80,9 +81,20 @@ pub mod aarch64;
|
|||||||
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub use aarch64::{
|
pub use aarch64::{
|
||||||
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt,
|
_NSIG, EntryPoint, arch_memory_regions, configure_system, configure_vcpu,
|
||||||
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
fdt::DeviceInfoForFdt, get_host_cpu_phys_bits, initramfs_load_addr, layout,
|
||||||
layout::IRQ_BASE, uefi, EntryPoint, _NSIG,
|
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, uefi,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Module for riscv64 related functionality.
|
||||||
|
#[cfg(target_arch = "riscv64")]
|
||||||
|
pub mod riscv64;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "riscv64")]
|
||||||
|
pub use riscv64::{
|
||||||
|
_NSIG, EntryPoint, arch_memory_regions, configure_system, configure_vcpu,
|
||||||
|
fdt::DeviceInfoForFdt, get_host_cpu_phys_bits, initramfs_load_addr, layout,
|
||||||
|
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, uefi,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
@@ -90,10 +102,9 @@ pub mod x86_64;
|
|||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub use x86_64::{
|
pub use x86_64::{
|
||||||
arch_memory_regions, configure_system, configure_vcpu, generate_common_cpuid,
|
_NSIG, CpuidConfig, CpuidFeatureEntry, EntryPoint, arch_memory_regions, configure_system,
|
||||||
generate_ram_ranges, get_host_cpu_phys_bits, initramfs_load_addr, layout,
|
configure_vcpu, generate_common_cpuid, generate_ram_ranges, get_host_cpu_phys_bits,
|
||||||
layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs, CpuidConfig, CpuidFeatureEntry,
|
initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs,
|
||||||
EntryPoint, _NSIG,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
||||||
@@ -108,12 +119,10 @@ fn pagesize() -> usize {
|
|||||||
pub struct NumaNode {
|
pub struct NumaNode {
|
||||||
pub memory_regions: Vec<Arc<GuestRegionMmap>>,
|
pub memory_regions: Vec<Arc<GuestRegionMmap>>,
|
||||||
pub hotplug_regions: Vec<Arc<GuestRegionMmap>>,
|
pub hotplug_regions: Vec<Arc<GuestRegionMmap>>,
|
||||||
pub cpus: Vec<u8>,
|
pub cpus: Vec<u32>,
|
||||||
pub pci_segments: Vec<u16>,
|
pub pci_segments: Vec<u16>,
|
||||||
pub distances: BTreeMap<u32, u8>,
|
pub distances: BTreeMap<u32, u8>,
|
||||||
pub memory_zones: Vec<String>,
|
pub memory_zones: Vec<String>,
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
pub sgx_epc_sections: Vec<SgxEpcSection>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type NumaNodes = BTreeMap<u32, NumaNode>;
|
pub type NumaNodes = BTreeMap<u32, NumaNode>;
|
||||||
@@ -132,7 +141,7 @@ pub enum DeviceType {
|
|||||||
/// Device Type: Virtio.
|
/// Device Type: Virtio.
|
||||||
Virtio(u32),
|
Virtio(u32),
|
||||||
/// Device Type: Serial.
|
/// Device Type: Serial.
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||||
Serial,
|
Serial,
|
||||||
/// Device Type: RTC.
|
/// Device Type: RTC.
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
@@ -140,6 +149,9 @@ pub enum DeviceType {
|
|||||||
/// Device Type: GPIO.
|
/// Device Type: GPIO.
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
Gpio,
|
Gpio,
|
||||||
|
/// Device Type: fw_cfg.
|
||||||
|
#[cfg(feature = "fw_cfg")]
|
||||||
|
FwCfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default (smallest) memory page size for the supported architectures.
|
/// Default (smallest) memory page size for the supported architectures.
|
||||||
@@ -153,7 +165,7 @@ impl fmt::Display for DeviceType {
|
|||||||
|
|
||||||
/// Structure to describe MMIO device information
|
/// Structure to describe MMIO device information
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||||
pub struct MmioDeviceInfo {
|
pub struct MmioDeviceInfo {
|
||||||
pub addr: u64,
|
pub addr: u64,
|
||||||
pub len: u64,
|
pub len: u64,
|
||||||
@@ -162,7 +174,7 @@ pub struct MmioDeviceInfo {
|
|||||||
|
|
||||||
/// Structure to describe PCI space information
|
/// Structure to describe PCI space information
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||||
pub struct PciSpaceInfo {
|
pub struct PciSpaceInfo {
|
||||||
pub pci_segment_id: u16,
|
pub pci_segment_id: u16,
|
||||||
pub mmio_config_address: u64,
|
pub mmio_config_address: u64,
|
||||||
@@ -170,7 +182,7 @@ pub struct PciSpaceInfo {
|
|||||||
pub pci_device_space_size: u64,
|
pub pci_device_space_size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||||
impl DeviceInfoForFdt for MmioDeviceInfo {
|
impl DeviceInfoForFdt for MmioDeviceInfo {
|
||||||
fn addr(&self) -> u64 {
|
fn addr(&self) -> u64 {
|
||||||
self.addr
|
self.addr
|
||||||
|
|||||||
481
arch/src/riscv64/fdt.rs
Normal file
481
arch/src/riscv64/fdt.rs
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||||
|
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||||
|
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the THIRD-PARTY file.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::{cmp, result, str};
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
use hypervisor::arch::riscv64::aia::Vaia;
|
||||||
|
use thiserror::Error;
|
||||||
|
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||||
|
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
||||||
|
|
||||||
|
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
||||||
|
use super::layout::{
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
use crate::PciSpaceInfo;
|
||||||
|
|
||||||
|
const AIA_APLIC_PHANDLE: u32 = 1;
|
||||||
|
const AIA_IMSIC_PHANDLE: u32 = 2;
|
||||||
|
const CPU_INTC_BASE_PHANDLE: u32 = 3;
|
||||||
|
const CPU_BASE_PHANDLE: u32 = 256 + CPU_INTC_BASE_PHANDLE;
|
||||||
|
// Read the documentation specified when appending the root node to the FDT.
|
||||||
|
const ADDRESS_CELLS: u32 = 0x2;
|
||||||
|
const SIZE_CELLS: u32 = 0x2;
|
||||||
|
|
||||||
|
// From https://elixir.bootlin.com/linux/v6.10/source/include/dt-bindings/interrupt-controller/irq.h#L14
|
||||||
|
const _IRQ_TYPE_EDGE_RISING: u32 = 1;
|
||||||
|
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
||||||
|
|
||||||
|
const S_MODE_EXT_IRQ: u32 = 9;
|
||||||
|
|
||||||
|
/// Trait for devices to be added to the Flattened Device Tree.
|
||||||
|
pub trait DeviceInfoForFdt {
|
||||||
|
/// Returns the address where this device will be loaded.
|
||||||
|
fn addr(&self) -> u64;
|
||||||
|
/// Returns the associated interrupt for this device.
|
||||||
|
fn irq(&self) -> u32;
|
||||||
|
/// Returns the amount of memory that needs to be reserved for this device.
|
||||||
|
fn length(&self) -> u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors thrown while configuring the Flattened Device Tree for riscv64.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Failure in writing FDT in memory.
|
||||||
|
#[error("Failure in writing FDT in memory")]
|
||||||
|
WriteFdtToMemory(#[source] GuestMemoryError),
|
||||||
|
}
|
||||||
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// Creates the flattened device tree for this riscv64 VM.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||||
|
guest_mem: &GuestMemoryMmap,
|
||||||
|
cmdline: &str,
|
||||||
|
num_vcpu: u32,
|
||||||
|
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||||
|
aia_device: &Arc<Mutex<dyn Vaia>>,
|
||||||
|
initrd: &Option<InitramfsConfig>,
|
||||||
|
pci_space_info: &[PciSpaceInfo],
|
||||||
|
) -> FdtWriterResult<Vec<u8>> {
|
||||||
|
// Allocate stuff necessary for the holding the blob.
|
||||||
|
let mut fdt = FdtWriter::new()?;
|
||||||
|
|
||||||
|
// For an explanation why these nodes were introduced in the blob take a look at
|
||||||
|
// https://github.com/devicetree-org/devicetree-specification/releases/tag/v0.4
|
||||||
|
// In chapter 3.
|
||||||
|
|
||||||
|
// Header or the root node as per above mentioned documentation.
|
||||||
|
let root_node = fdt.begin_node("")?;
|
||||||
|
fdt.property_string("compatible", "linux,dummy-virt")?;
|
||||||
|
// For info on #address-cells and size-cells resort to Table 3.1 Root Node
|
||||||
|
// Properties
|
||||||
|
fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
|
||||||
|
fdt.property_u32("#size-cells", SIZE_CELLS)?;
|
||||||
|
create_cpu_nodes(&mut fdt, num_vcpu)?;
|
||||||
|
create_memory_node(&mut fdt, guest_mem)?;
|
||||||
|
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
||||||
|
create_aia_node(&mut fdt, aia_device)?;
|
||||||
|
create_devices_node(&mut fdt, device_info)?;
|
||||||
|
create_pci_nodes(&mut fdt, pci_space_info)?;
|
||||||
|
|
||||||
|
// End Header node.
|
||||||
|
fdt.end_node(root_node)?;
|
||||||
|
|
||||||
|
let fdt_final = fdt.finish()?;
|
||||||
|
|
||||||
|
Ok(fdt_final)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||||
|
// Write FDT to memory.
|
||||||
|
guest_mem
|
||||||
|
.write_slice(fdt_final.as_slice(), super::layout::FDT_START)
|
||||||
|
.map_err(Error::WriteFdtToMemory)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
|
||||||
|
fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> FdtWriterResult<()> {
|
||||||
|
// See https://elixir.bootlin.com/linux/v6.10/source/Documentation/devicetree/bindings/riscv/cpus.yaml
|
||||||
|
let cpus = fdt.begin_node("cpus")?;
|
||||||
|
// As per documentation, on RISC-V 64-bit systems value should be set to 1.
|
||||||
|
fdt.property_u32("#address-cells", 0x01)?;
|
||||||
|
fdt.property_u32("#size-cells", 0x0)?;
|
||||||
|
// TODO: Retrieve CPU frequency from cpu timer regs
|
||||||
|
let timebase_frequency: u32 = 0x989680;
|
||||||
|
fdt.property_u32("timebase-frequency", timebase_frequency)?;
|
||||||
|
|
||||||
|
for cpu_index in 0..num_cpus {
|
||||||
|
let cpu = fdt.begin_node(&format!("cpu@{cpu_index:x}"))?;
|
||||||
|
fdt.property_string("device_type", "cpu")?;
|
||||||
|
fdt.property_string("compatible", "riscv")?;
|
||||||
|
fdt.property_string("mmu-type", "sv48")?;
|
||||||
|
fdt.property_string("riscv,isa", "rv64imafdc_smaia_ssaia")?;
|
||||||
|
fdt.property_string("status", "okay")?;
|
||||||
|
fdt.property_u32("reg", cpu_index)?;
|
||||||
|
fdt.property_u32("phandle", CPU_BASE_PHANDLE + cpu_index)?;
|
||||||
|
|
||||||
|
// interrupt controller node
|
||||||
|
let intc_node = fdt.begin_node("interrupt-controller")?;
|
||||||
|
fdt.property_string("compatible", "riscv,cpu-intc")?;
|
||||||
|
fdt.property_u32("#interrupt-cells", 1u32)?;
|
||||||
|
fdt.property_null("interrupt-controller")?;
|
||||||
|
fdt.property_u32("phandle", CPU_INTC_BASE_PHANDLE + cpu_index)?;
|
||||||
|
fdt.end_node(intc_node)?;
|
||||||
|
|
||||||
|
fdt.end_node(cpu)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fdt.end_node(cpus)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_memory_node(fdt: &mut FdtWriter, guest_mem: &GuestMemoryMmap) -> FdtWriterResult<()> {
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
for (start, size) in guest_mem
|
||||||
|
.iter()
|
||||||
|
.map(|m| (m.start_addr().raw_value(), m.len()))
|
||||||
|
{
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut mem_reg_property = Vec::new();
|
||||||
|
for region in ram_regions {
|
||||||
|
let mem_size = region.1 - region.0;
|
||||||
|
mem_reg_property.push(region.0);
|
||||||
|
mem_reg_property.push(mem_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ram_start = super::layout::RAM_START.raw_value();
|
||||||
|
let memory_node_name = format!("memory@{ram_start:x}");
|
||||||
|
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||||
|
fdt.property_string("device_type", "memory")?;
|
||||||
|
fdt.property_array_u64("reg", &mem_reg_property)?;
|
||||||
|
fdt.end_node(memory_node)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_chosen_node(
|
||||||
|
fdt: &mut FdtWriter,
|
||||||
|
cmdline: &str,
|
||||||
|
initrd: &Option<InitramfsConfig>,
|
||||||
|
) -> FdtWriterResult<()> {
|
||||||
|
let chosen_node = fdt.begin_node("chosen")?;
|
||||||
|
fdt.property_string("bootargs", cmdline)?;
|
||||||
|
|
||||||
|
if let Some(initrd_config) = initrd {
|
||||||
|
let initrd_start = initrd_config.address.raw_value();
|
||||||
|
let initrd_end = initrd_config.address.raw_value() + initrd_config.size as u64;
|
||||||
|
fdt.property_u64("linux,initrd-start", initrd_start)?;
|
||||||
|
fdt.property_u64("linux,initrd-end", initrd_end)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fdt.end_node(chosen_node)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_aia_node(fdt: &mut FdtWriter, aia_device: &Arc<Mutex<dyn Vaia>>) -> FdtWriterResult<()> {
|
||||||
|
// IMSIC
|
||||||
|
if aia_device.lock().unwrap().msi_compatible() {
|
||||||
|
use super::layout::IMSIC_START;
|
||||||
|
let imsic_name = format!("imsics@{:x}", IMSIC_START.0);
|
||||||
|
let imsic_node = fdt.begin_node(&imsic_name)?;
|
||||||
|
|
||||||
|
fdt.property_string(
|
||||||
|
"compatible",
|
||||||
|
aia_device.lock().unwrap().imsic_compatibility(),
|
||||||
|
)?;
|
||||||
|
let imsic_reg_prop = aia_device.lock().unwrap().imsic_properties();
|
||||||
|
fdt.property_array_u32("reg", &imsic_reg_prop)?;
|
||||||
|
fdt.property_u32("#interrupt-cells", 0u32)?;
|
||||||
|
fdt.property_null("interrupt-controller")?;
|
||||||
|
fdt.property_null("msi-controller")?;
|
||||||
|
// TODO complete num-ids
|
||||||
|
fdt.property_u32("riscv,num-ids", 2047u32)?;
|
||||||
|
fdt.property_u32("phandle", AIA_IMSIC_PHANDLE)?;
|
||||||
|
|
||||||
|
let mut irq_cells = Vec::new();
|
||||||
|
let num_cpus = aia_device.lock().unwrap().vcpu_count();
|
||||||
|
for i in 0..num_cpus {
|
||||||
|
irq_cells.push(CPU_INTC_BASE_PHANDLE + i);
|
||||||
|
irq_cells.push(S_MODE_EXT_IRQ);
|
||||||
|
}
|
||||||
|
fdt.property_array_u32("interrupts-extended", &irq_cells)?;
|
||||||
|
|
||||||
|
fdt.end_node(imsic_node)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// APLIC
|
||||||
|
use super::layout::APLIC_START;
|
||||||
|
let aplic_name = format!("aplic@{:x}", APLIC_START.0);
|
||||||
|
let aplic_node = fdt.begin_node(&aplic_name)?;
|
||||||
|
|
||||||
|
fdt.property_string(
|
||||||
|
"compatible",
|
||||||
|
aia_device.lock().unwrap().aplic_compatibility(),
|
||||||
|
)?;
|
||||||
|
let reg_cells = aia_device.lock().unwrap().aplic_properties();
|
||||||
|
fdt.property_array_u32("reg", ®_cells)?;
|
||||||
|
fdt.property_u32("#interrupt-cells", 2u32)?;
|
||||||
|
fdt.property_null("interrupt-controller")?;
|
||||||
|
// TODO complete num-srcs
|
||||||
|
fdt.property_u32("riscv,num-sources", 96u32)?;
|
||||||
|
fdt.property_u32("phandle", AIA_APLIC_PHANDLE)?;
|
||||||
|
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
|
||||||
|
|
||||||
|
fdt.end_node(aplic_node)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_serial_node<T: DeviceInfoForFdt + Clone + Debug>(
|
||||||
|
fdt: &mut FdtWriter,
|
||||||
|
dev_info: &T,
|
||||||
|
) -> FdtWriterResult<()> {
|
||||||
|
let serial_reg_prop = [dev_info.addr(), dev_info.length()];
|
||||||
|
let irq = [dev_info.irq() - IRQ_BASE, IRQ_TYPE_LEVEL_HI];
|
||||||
|
|
||||||
|
let serial_node = fdt.begin_node(&format!("serial@{:x}", dev_info.addr()))?;
|
||||||
|
fdt.property_string("compatible", "ns16550a")?;
|
||||||
|
fdt.property_array_u64("reg", &serial_reg_prop)?;
|
||||||
|
fdt.property_u32("clock-frequency", 3686400)?;
|
||||||
|
fdt.property_u32("interrupt-parent", AIA_APLIC_PHANDLE)?;
|
||||||
|
fdt.property_array_u32("interrupts", &irq)?;
|
||||||
|
fdt.end_node(serial_node)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||||
|
fdt: &mut FdtWriter,
|
||||||
|
dev_info: &HashMap<(DeviceType, String), T, S>,
|
||||||
|
) -> FdtWriterResult<()> {
|
||||||
|
for ((device_type, _device_id), info) in dev_info {
|
||||||
|
match device_type {
|
||||||
|
DeviceType::Serial => create_serial_node(fdt, info)?,
|
||||||
|
DeviceType::Virtio(_) => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_pci_nodes(fdt: &mut FdtWriter, pci_device_info: &[PciSpaceInfo]) -> FdtWriterResult<()> {
|
||||||
|
// Add node for PCIe controller.
|
||||||
|
// See Documentation/devicetree/bindings/pci/host-generic-pci.txt in the kernel
|
||||||
|
// and https://elinux.org/Device_Tree_Usage.
|
||||||
|
// In multiple PCI segments setup, each PCI segment needs a PCI node.
|
||||||
|
for pci_device_info_elem in pci_device_info.iter() {
|
||||||
|
// EDK2 requires the PCIe high space above 4G address.
|
||||||
|
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
||||||
|
// could fall below 4G.
|
||||||
|
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
||||||
|
// But the address written in ACPI is not impacted.
|
||||||
|
let (pci_device_base_64bit, pci_device_size_64bit) =
|
||||||
|
if pci_device_info_elem.pci_device_space_start < PCI_HIGH_BASE.raw_value() {
|
||||||
|
(
|
||||||
|
PCI_HIGH_BASE.raw_value(),
|
||||||
|
pci_device_info_elem.pci_device_space_size
|
||||||
|
- (PCI_HIGH_BASE.raw_value() - pci_device_info_elem.pci_device_space_start),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
pci_device_info_elem.pci_device_space_start,
|
||||||
|
pci_device_info_elem.pci_device_space_size,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
// There is no specific requirement of the 32bit MMIO range, and
|
||||||
|
// therefore at least we can make these ranges 4K aligned.
|
||||||
|
let pci_device_size_32bit: u64 =
|
||||||
|
MEM_32BIT_DEVICES_SIZE / ((1 << 12) * pci_device_info.len() as u64) * (1 << 12);
|
||||||
|
let pci_device_base_32bit: u64 = MEM_32BIT_DEVICES_START.0
|
||||||
|
+ pci_device_size_32bit * pci_device_info_elem.pci_segment_id as u64;
|
||||||
|
|
||||||
|
let ranges = [
|
||||||
|
// io addresses. Since AArch64 will not use IO address,
|
||||||
|
// we can set the same IO address range for every segment.
|
||||||
|
0x1000000,
|
||||||
|
0_u32,
|
||||||
|
0_u32,
|
||||||
|
(MEM_PCI_IO_START.0 >> 32) as u32,
|
||||||
|
MEM_PCI_IO_START.0 as u32,
|
||||||
|
(MEM_PCI_IO_SIZE >> 32) as u32,
|
||||||
|
MEM_PCI_IO_SIZE as u32,
|
||||||
|
// mmio addresses
|
||||||
|
0x2000000, // (ss = 10: 32-bit memory space)
|
||||||
|
(pci_device_base_32bit >> 32) as u32, // PCI address
|
||||||
|
pci_device_base_32bit as u32,
|
||||||
|
(pci_device_base_32bit >> 32) as u32, // CPU address
|
||||||
|
pci_device_base_32bit as u32,
|
||||||
|
(pci_device_size_32bit >> 32) as u32, // size
|
||||||
|
pci_device_size_32bit as u32,
|
||||||
|
// device addresses
|
||||||
|
0x3000000, // (ss = 11: 64-bit memory space)
|
||||||
|
(pci_device_base_64bit >> 32) as u32, // PCI address
|
||||||
|
pci_device_base_64bit as u32,
|
||||||
|
(pci_device_base_64bit >> 32) as u32, // CPU address
|
||||||
|
pci_device_base_64bit as u32,
|
||||||
|
(pci_device_size_64bit >> 32) as u32, // size
|
||||||
|
pci_device_size_64bit as u32,
|
||||||
|
];
|
||||||
|
let bus_range = [0, 0]; // Only bus 0
|
||||||
|
let reg = [
|
||||||
|
pci_device_info_elem.mmio_config_address,
|
||||||
|
PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
||||||
|
];
|
||||||
|
// See kernel document Documentation/devicetree/bindings/pci/pci-msi.txt
|
||||||
|
let msi_map = [
|
||||||
|
// rid-base: A single cell describing the first RID matched by the entry.
|
||||||
|
0x0,
|
||||||
|
// msi-controller: A single phandle to an MSI controller.
|
||||||
|
AIA_IMSIC_PHANDLE,
|
||||||
|
// msi-base: An msi-specifier describing the msi-specifier produced for the
|
||||||
|
// first RID matched by the entry.
|
||||||
|
(pci_device_info_elem.pci_segment_id as u32) << 8,
|
||||||
|
// length: A single cell describing how many consecutive RIDs are matched
|
||||||
|
// following the rid-base.
|
||||||
|
0x100,
|
||||||
|
];
|
||||||
|
|
||||||
|
let pci_node_name = format!("pci@{:x}", pci_device_info_elem.mmio_config_address);
|
||||||
|
let pci_node = fdt.begin_node(&pci_node_name)?;
|
||||||
|
|
||||||
|
fdt.property_string("compatible", "pci-host-ecam-generic")?;
|
||||||
|
fdt.property_string("device_type", "pci")?;
|
||||||
|
fdt.property_array_u32("ranges", &ranges)?;
|
||||||
|
fdt.property_array_u32("bus-range", &bus_range)?;
|
||||||
|
fdt.property_u32(
|
||||||
|
"linux,pci-domain",
|
||||||
|
pci_device_info_elem.pci_segment_id as u32,
|
||||||
|
)?;
|
||||||
|
fdt.property_u32("#address-cells", 3)?;
|
||||||
|
fdt.property_u32("#size-cells", 2)?;
|
||||||
|
fdt.property_array_u64("reg", ®)?;
|
||||||
|
fdt.property_u32("#interrupt-cells", 1)?;
|
||||||
|
fdt.property_null("interrupt-map")?;
|
||||||
|
fdt.property_null("interrupt-map-mask")?;
|
||||||
|
fdt.property_null("dma-coherent")?;
|
||||||
|
fdt.property_array_u32("msi-map", &msi_map)?;
|
||||||
|
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
|
||||||
|
|
||||||
|
fdt.end_node(pci_node)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the DTB binary and print for debugging
|
||||||
|
pub fn print_fdt(dtb: &[u8]) {
|
||||||
|
match fdt_parser::Fdt::new(dtb) {
|
||||||
|
Ok(fdt) => {
|
||||||
|
if let Some(root) = fdt.find_node("/") {
|
||||||
|
debug!("Printing the FDT:");
|
||||||
|
print_node(root, 0);
|
||||||
|
} else {
|
||||||
|
debug!("Failed to find root node in FDT for debugging.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => debug!("Failed to parse FDT for debugging."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
||||||
|
debug!("{:indent$}{}/", "", node.name, indent = n_spaces);
|
||||||
|
for property in node.properties() {
|
||||||
|
let name = property.name;
|
||||||
|
|
||||||
|
// If the property is 'compatible', its value requires special handling.
|
||||||
|
// The u8 array could contain multiple null-terminated strings.
|
||||||
|
// We copy the original array and simply replace all 'null' characters with spaces.
|
||||||
|
let value = if name == "compatible" {
|
||||||
|
let mut compatible = vec![0u8; 256];
|
||||||
|
let handled_value = property
|
||||||
|
.value
|
||||||
|
.iter()
|
||||||
|
.map(|&c| if c == 0 { b' ' } else { c })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let len = cmp::min(255, handled_value.len());
|
||||||
|
compatible[..len].copy_from_slice(&handled_value[..len]);
|
||||||
|
compatible[..(len + 1)].to_vec()
|
||||||
|
} else {
|
||||||
|
property.value.to_vec()
|
||||||
|
};
|
||||||
|
let value = &value;
|
||||||
|
|
||||||
|
// Now the value can be either:
|
||||||
|
// - A null-terminated C string, or
|
||||||
|
// - Binary data
|
||||||
|
// We follow a very simple logic to present the value:
|
||||||
|
// - At first, try to convert it to CStr and print,
|
||||||
|
// - If failed, print it as u32 array.
|
||||||
|
let value_result = match CStr::from_bytes_with_nul(value) {
|
||||||
|
Ok(value_cstr) => value_cstr.to_str().ok(),
|
||||||
|
Err(_e) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(value_str) = value_result {
|
||||||
|
debug!(
|
||||||
|
"{:indent$}{} : {:#?}",
|
||||||
|
"",
|
||||||
|
name,
|
||||||
|
value_str,
|
||||||
|
indent = (n_spaces + 2)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let mut array = Vec::with_capacity(256);
|
||||||
|
array.resize(value.len() / 4, 0u32);
|
||||||
|
BigEndian::read_u32_into(value, &mut array);
|
||||||
|
debug!(
|
||||||
|
"{:indent$}{} : {:X?}",
|
||||||
|
"",
|
||||||
|
name,
|
||||||
|
array,
|
||||||
|
indent = (n_spaces + 2)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print children nodes if there is any
|
||||||
|
for child in node.children() {
|
||||||
|
print_node(child, n_spaces + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
112
arch/src/riscv64/layout.rs
Normal file
112
arch/src/riscv64/layout.rs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||||
|
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||||
|
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
//
|
||||||
|
// Memory layout of RISC-V 64-bit guest:
|
||||||
|
//
|
||||||
|
// Physical +---------------------------------------------------------------+
|
||||||
|
// address | |
|
||||||
|
// end | |
|
||||||
|
// ~ ~ ~ ~
|
||||||
|
// | |
|
||||||
|
// | Highmem PCI MMIO space |
|
||||||
|
// | |
|
||||||
|
// RAM end +---------------------------------------------------------------+
|
||||||
|
// (dynamic, | |
|
||||||
|
// including | |
|
||||||
|
// hotplug ~ ~ ~ ~
|
||||||
|
// memory) | |
|
||||||
|
// | DRAM |
|
||||||
|
// | |
|
||||||
|
// | |
|
||||||
|
// | |
|
||||||
|
// | |
|
||||||
|
// 1 GB +---------------------------------------------------------------+
|
||||||
|
// | |
|
||||||
|
// | PCI MMCONFIG space |
|
||||||
|
// | |
|
||||||
|
// 768 MB +---------------------------------------------------------------+
|
||||||
|
// | |
|
||||||
|
// | |
|
||||||
|
// | PCI MMIO space |
|
||||||
|
// | |
|
||||||
|
// 256 MB +---------------------------------------------------------------|
|
||||||
|
// | |
|
||||||
|
// | Legacy devices space |
|
||||||
|
// | |
|
||||||
|
// 128 MB +---------------------------------------------------------------|
|
||||||
|
// | |
|
||||||
|
// | IMSICs |
|
||||||
|
// | |
|
||||||
|
// 64 MB +---------------------------------------------------------------+
|
||||||
|
// | |
|
||||||
|
// | APLICs |
|
||||||
|
// | |
|
||||||
|
// 4 MB +---------------------------------------------------------------+
|
||||||
|
// | UEFI flash |
|
||||||
|
// 0 GB +---------------------------------------------------------------+
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
use vm_memory::GuestAddress;
|
||||||
|
|
||||||
|
/// 0x0 ~ 0x40_0000 (4 MiB) is reserved to UEFI
|
||||||
|
/// UEFI binary size is required less than 3 MiB, reserving 4 MiB is enough.
|
||||||
|
pub const UEFI_START: GuestAddress = GuestAddress(0);
|
||||||
|
pub const UEFI_SIZE: u64 = 0x040_0000;
|
||||||
|
|
||||||
|
/// AIA related devices
|
||||||
|
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/kvm.h
|
||||||
|
/// 0x40_0000 ~ 0x0400_0000 (64 MiB) resides APLICs
|
||||||
|
pub const APLIC_START: GuestAddress = GuestAddress(0x40_0000);
|
||||||
|
pub const APLIC_SIZE: u64 = 0x4000;
|
||||||
|
|
||||||
|
/// 0x0400_0000 ~ 0x0800_0000 (64 MiB) resides IMSICs
|
||||||
|
pub const IMSIC_START: GuestAddress = GuestAddress(0x0400_0000);
|
||||||
|
pub const IMSIC_SIZE: u64 = 0x1000;
|
||||||
|
|
||||||
|
/// Below this address will reside the AIA, above this address will reside the MMIO devices.
|
||||||
|
const MAPPED_IO_START: GuestAddress = GuestAddress(0x0800_0000);
|
||||||
|
|
||||||
|
/// Space 0x0800_0000 ~ 0x1000_0000 is reserved for legacy devices.
|
||||||
|
pub const LEGACY_SERIAL_MAPPED_IO_START: GuestAddress = MAPPED_IO_START;
|
||||||
|
|
||||||
|
/// Space 0x0905_0000 ~ 0x0906_0000 is reserved for pcie io address
|
||||||
|
pub const MEM_PCI_IO_START: GuestAddress = GuestAddress(0x0905_0000);
|
||||||
|
pub const MEM_PCI_IO_SIZE: u64 = 0x1_0000;
|
||||||
|
|
||||||
|
/// Starting from 0x1000_0000 (256MiB) to 0x3000_0000 (768MiB) is used for PCIE MMIO
|
||||||
|
pub const MEM_32BIT_DEVICES_START: GuestAddress = GuestAddress(0x1000_0000);
|
||||||
|
pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x2000_0000;
|
||||||
|
|
||||||
|
/// PCI MMCONFIG space (start: after the device space at 768MiB, length: 256MiB)
|
||||||
|
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x3000_0000);
|
||||||
|
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
|
||||||
|
// One bus with potentially 256 devices (32 slots x 8 functions).
|
||||||
|
pub const PCI_MMIO_CONFIG_SIZE_PER_SEGMENT: u64 = 4096 * 256;
|
||||||
|
|
||||||
|
/// Start of RAM.
|
||||||
|
pub const RAM_START: GuestAddress = GuestAddress(0x4000_0000);
|
||||||
|
|
||||||
|
/// Kernel command line maximum size on RISC-V.
|
||||||
|
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/setup.h
|
||||||
|
pub const CMDLINE_MAX_SIZE: usize = 1024;
|
||||||
|
|
||||||
|
/// FDT is at the beginning of RAM.
|
||||||
|
pub const FDT_START: GuestAddress = RAM_START;
|
||||||
|
pub const FDT_MAX_SIZE: u64 = 0x1_0000;
|
||||||
|
|
||||||
|
/// Kernel start after FDT
|
||||||
|
pub const KERNEL_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE);
|
||||||
|
|
||||||
|
/// Pci high memory base
|
||||||
|
pub const PCI_HIGH_BASE: GuestAddress = GuestAddress(0x2_0000_0000);
|
||||||
|
|
||||||
|
/// First usable interrupt on riscv64
|
||||||
|
pub const IRQ_BASE: u32 = 0;
|
||||||
|
|
||||||
|
// As per https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/asm/kvm_host.h#L31
|
||||||
|
/// Number of supported interrupts
|
||||||
|
pub const IRQ_NUM: u32 = 1023;
|
||||||
169
arch/src/riscv64/mod.rs
Normal file
169
arch/src/riscv64/mod.rs
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||||
|
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||||
|
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
/// Module for the flattened device tree.
|
||||||
|
pub mod fdt;
|
||||||
|
/// Layout for this riscv64 system.
|
||||||
|
pub mod layout;
|
||||||
|
/// Module for loading UEFI binary.
|
||||||
|
pub mod uefi;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use hypervisor::arch::riscv64::aia::Vaia;
|
||||||
|
use log::{Level, log_enabled};
|
||||||
|
use thiserror::Error;
|
||||||
|
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
||||||
|
|
||||||
|
pub use self::fdt::DeviceInfoForFdt;
|
||||||
|
use crate::{DeviceType, GuestMemoryMmap, PciSpaceInfo, RegionType};
|
||||||
|
|
||||||
|
pub const _NSIG: i32 = 65;
|
||||||
|
|
||||||
|
/// Errors thrown while configuring riscv64 system.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Failed to create a FDT.
|
||||||
|
#[error("Failed to create a FDT")]
|
||||||
|
SetupFdt,
|
||||||
|
|
||||||
|
/// Failed to write FDT to memory.
|
||||||
|
#[error("Failed to write FDT to memory")]
|
||||||
|
WriteFdtToMemory(#[source] fdt::Error),
|
||||||
|
|
||||||
|
/// Failed to create a AIA.
|
||||||
|
#[error("Failed to create a AIA")]
|
||||||
|
SetupAia,
|
||||||
|
|
||||||
|
/// Failed to compute the initramfs address.
|
||||||
|
#[error("Failed to compute the initramfs address")]
|
||||||
|
InitramfsAddress,
|
||||||
|
|
||||||
|
/// Error configuring the general purpose registers
|
||||||
|
#[error("Error configuring the general purpose registers")]
|
||||||
|
RegsConfiguration(#[source] hypervisor::HypervisorCpuError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
/// Specifies the entry point address where the guest must start
|
||||||
|
/// executing code.
|
||||||
|
pub struct EntryPoint {
|
||||||
|
/// Address in guest memory where the guest must start execution
|
||||||
|
pub entry_addr: GuestAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the specified VCPU, and return its MPIDR.
|
||||||
|
pub fn configure_vcpu(
|
||||||
|
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||||
|
id: u32,
|
||||||
|
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
||||||
|
) -> super::Result<()> {
|
||||||
|
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
||||||
|
vcpu.setup_regs(
|
||||||
|
id,
|
||||||
|
kernel_entry_point.entry_addr.raw_value(),
|
||||||
|
layout::FDT_START.raw_value(),
|
||||||
|
)
|
||||||
|
.map_err(Error::RegsConfiguration)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
||||||
|
vec![
|
||||||
|
// 0 MiB ~ 256 MiB: AIA and legacy devices
|
||||||
|
(
|
||||||
|
GuestAddress(0),
|
||||||
|
layout::MEM_32BIT_DEVICES_START.0 as usize,
|
||||||
|
RegionType::Reserved,
|
||||||
|
),
|
||||||
|
// 256 MiB ~ 768 MiB: MMIO space
|
||||||
|
(
|
||||||
|
layout::MEM_32BIT_DEVICES_START,
|
||||||
|
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
||||||
|
RegionType::SubRegion,
|
||||||
|
),
|
||||||
|
// 768 MiB ~ 1 GiB: reserved. The leading 256M for PCIe MMCONFIG space
|
||||||
|
(
|
||||||
|
layout::PCI_MMCONFIG_START,
|
||||||
|
layout::PCI_MMCONFIG_SIZE as usize,
|
||||||
|
RegionType::Reserved,
|
||||||
|
),
|
||||||
|
// 1GiB ~ inf: RAM
|
||||||
|
(layout::RAM_START, usize::MAX, RegionType::Ram),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configures the system and should be called once per vm before starting vcpu threads.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||||
|
guest_mem: &GuestMemoryMmap,
|
||||||
|
cmdline: &str,
|
||||||
|
num_vcpu: u32,
|
||||||
|
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||||
|
initrd: &Option<super::InitramfsConfig>,
|
||||||
|
pci_space_info: &[PciSpaceInfo],
|
||||||
|
aia_device: &Arc<Mutex<dyn Vaia>>,
|
||||||
|
) -> super::Result<()> {
|
||||||
|
let fdt_final = fdt::create_fdt(
|
||||||
|
guest_mem,
|
||||||
|
cmdline,
|
||||||
|
num_vcpu,
|
||||||
|
device_info,
|
||||||
|
aia_device,
|
||||||
|
initrd,
|
||||||
|
pci_space_info,
|
||||||
|
)
|
||||||
|
.map_err(|_| Error::SetupFdt)?;
|
||||||
|
|
||||||
|
if log_enabled!(Level::Debug) {
|
||||||
|
fdt::print_fdt(&fdt_final);
|
||||||
|
}
|
||||||
|
|
||||||
|
fdt::write_fdt_to_memory(fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the memory address where the initramfs could be loaded.
|
||||||
|
pub fn initramfs_load_addr(
|
||||||
|
guest_mem: &GuestMemoryMmap,
|
||||||
|
initramfs_size: usize,
|
||||||
|
) -> super::Result<u64> {
|
||||||
|
let round_to_pagesize = |size| (size + (super::PAGE_SIZE - 1)) & !(super::PAGE_SIZE - 1);
|
||||||
|
match guest_mem
|
||||||
|
.last_addr()
|
||||||
|
.checked_sub(round_to_pagesize(initramfs_size) as u64 - 1)
|
||||||
|
{
|
||||||
|
Some(offset) => {
|
||||||
|
if guest_mem.address_in_range(offset) {
|
||||||
|
Ok(offset.raw_value())
|
||||||
|
} else {
|
||||||
|
Err(super::Error::PlatformSpecific(Error::InitramfsAddress))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Err(super::Error::PlatformSpecific(Error::InitramfsAddress)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_host_cpu_phys_bits(_hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
|
||||||
|
40
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_arch_memory_regions_dram() {
|
||||||
|
let regions = arch_memory_regions();
|
||||||
|
assert_eq!(4, regions.len());
|
||||||
|
assert_eq!(layout::RAM_START, regions[3].0);
|
||||||
|
assert_eq!(RegionType::Ram, regions[3].2);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
arch/src/riscv64/uefi.rs
Normal file
50
arch/src/riscv64/uefi.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
use std::os::fd::AsFd;
|
||||||
|
use std::result;
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
use vm_memory::{GuestAddress, GuestMemory};
|
||||||
|
|
||||||
|
/// Errors thrown while loading UEFI binary
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Unable to seek to UEFI image start.
|
||||||
|
#[error("Unable to seek to UEFI image start")]
|
||||||
|
SeekUefiStart,
|
||||||
|
/// Unable to seek to UEFI image end.
|
||||||
|
#[error("Unable to seek to UEFI image end")]
|
||||||
|
SeekUefiEnd,
|
||||||
|
/// UEFI image too big.
|
||||||
|
#[error("UEFI image too big")]
|
||||||
|
UefiTooBig,
|
||||||
|
/// Unable to read UEFI image
|
||||||
|
#[error("Unable to read UEFI image")]
|
||||||
|
ReadUefiImage,
|
||||||
|
}
|
||||||
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
|
pub fn load_uefi<F, M: GuestMemory>(
|
||||||
|
guest_mem: &M,
|
||||||
|
guest_addr: GuestAddress,
|
||||||
|
uefi_image: &mut F,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
F: Read + Seek + AsFd,
|
||||||
|
{
|
||||||
|
let uefi_size = uefi_image
|
||||||
|
.seek(SeekFrom::End(0))
|
||||||
|
.map_err(|_| Error::SeekUefiEnd)? as usize;
|
||||||
|
|
||||||
|
// edk2 image on virtual platform is smaller than 3M
|
||||||
|
if uefi_size > 0x300000 {
|
||||||
|
return Err(Error::UefiTooBig);
|
||||||
|
}
|
||||||
|
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
|
||||||
|
guest_mem
|
||||||
|
.read_exact_volatile_from(guest_addr, &mut uefi_image.as_fd(), uefi_size)
|
||||||
|
.map_err(|_| Error::ReadUefiImage)
|
||||||
|
}
|
||||||
@@ -12,10 +12,9 @@ pub mod layout;
|
|||||||
mod mpspec;
|
mod mpspec;
|
||||||
mod mptable;
|
mod mptable;
|
||||||
pub mod regs;
|
pub mod regs;
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use hypervisor::arch::x86::{CpuIdEntry, CPUID_FLAG_VALID_INDEX};
|
use hypervisor::arch::x86::{CPUID_FLAG_VALID_INDEX, CpuIdEntry};
|
||||||
use hypervisor::{CpuVendor, HypervisorCpuError, HypervisorError};
|
use hypervisor::{CpuVendor, HypervisorCpuError, HypervisorError};
|
||||||
use linux_loader::loader::bootparam::{boot_params, setup_header};
|
use linux_loader::loader::bootparam::{boot_params, setup_header};
|
||||||
use linux_loader::loader::elf::start_info::{
|
use linux_loader::loader::elf::start_info::{
|
||||||
@@ -24,7 +23,7 @@ use linux_loader::loader::elf::start_info::{
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vm_memory::{
|
use vm_memory::{
|
||||||
Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
||||||
GuestMemoryRegion, GuestUsize,
|
GuestMemoryRegion,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{GuestMemoryMmap, InitramfsConfig, RegionType};
|
use crate::{GuestMemoryMmap, InitramfsConfig, RegionType};
|
||||||
@@ -33,6 +32,10 @@ use std::arch::x86_64;
|
|||||||
#[cfg(feature = "tdx")]
|
#[cfg(feature = "tdx")]
|
||||||
pub mod tdx;
|
pub mod tdx;
|
||||||
|
|
||||||
|
// While modern architectures support more than 255 CPUs via x2APIC,
|
||||||
|
// legacy devices such as mptable support at most 254 CPUs.
|
||||||
|
pub const MAX_SUPPORTED_CPUS_LEGACY: u32 = 254;
|
||||||
|
|
||||||
// CPUID feature bits
|
// CPUID feature bits
|
||||||
#[cfg(feature = "kvm")]
|
#[cfg(feature = "kvm")]
|
||||||
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
||||||
@@ -57,6 +60,8 @@ 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;
|
||||||
|
|
||||||
|
const KVM_FEATURE_MSI_EXT_DEST_ID: u8 = 15;
|
||||||
|
|
||||||
pub const _NSIG: i32 = 65;
|
pub const _NSIG: i32 = 65;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
@@ -73,55 +78,7 @@ pub struct EntryPoint {
|
|||||||
const E820_RAM: u32 = 1;
|
const E820_RAM: u32 = 1;
|
||||||
const E820_RESERVED: u32 = 2;
|
const E820_RESERVED: u32 = 2;
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SgxEpcSection {
|
|
||||||
start: GuestAddress,
|
|
||||||
size: GuestUsize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SgxEpcSection {
|
|
||||||
pub fn new(start: GuestAddress, size: GuestUsize) -> Self {
|
|
||||||
SgxEpcSection { start, size }
|
|
||||||
}
|
|
||||||
pub fn start(&self) -> GuestAddress {
|
|
||||||
self.start
|
|
||||||
}
|
|
||||||
pub fn size(&self) -> GuestUsize {
|
|
||||||
self.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SgxEpcRegion {
|
|
||||||
start: GuestAddress,
|
|
||||||
size: GuestUsize,
|
|
||||||
epc_sections: BTreeMap<String, SgxEpcSection>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SgxEpcRegion {
|
|
||||||
pub fn new(start: GuestAddress, size: GuestUsize) -> Self {
|
|
||||||
SgxEpcRegion {
|
|
||||||
start,
|
|
||||||
size,
|
|
||||||
epc_sections: BTreeMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn start(&self) -> GuestAddress {
|
|
||||||
self.start
|
|
||||||
}
|
|
||||||
pub fn size(&self) -> GuestUsize {
|
|
||||||
self.size
|
|
||||||
}
|
|
||||||
pub fn epc_sections(&self) -> &BTreeMap<String, SgxEpcSection> {
|
|
||||||
&self.epc_sections
|
|
||||||
}
|
|
||||||
pub fn insert(&mut self, id: String, epc_section: SgxEpcSection) {
|
|
||||||
self.epc_sections.insert(id, epc_section);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CpuidConfig {
|
pub struct CpuidConfig {
|
||||||
pub sgx_epc_sections: Option<Vec<SgxEpcSection>>,
|
|
||||||
pub phys_bits: u8,
|
pub phys_bits: u8,
|
||||||
pub kvm_hyperv: bool,
|
pub kvm_hyperv: bool,
|
||||||
#[cfg(feature = "tdx")]
|
#[cfg(feature = "tdx")]
|
||||||
@@ -132,94 +89,76 @@ pub struct CpuidConfig {
|
|||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Error writing MP table to memory.
|
/// Error writing MP table to memory.
|
||||||
#[error("Error writing MP table to memory: {0}")]
|
#[error("Error writing MP table to memory")]
|
||||||
MpTableSetup(mptable::Error),
|
MpTableSetup(#[source] mptable::Error),
|
||||||
|
|
||||||
/// Error configuring the general purpose registers
|
/// Error configuring the general purpose registers
|
||||||
#[error("Error configuring the general purpose registers: {0}")]
|
#[error("Error configuring the general purpose registers")]
|
||||||
RegsConfiguration(regs::Error),
|
RegsConfiguration(#[source] regs::Error),
|
||||||
|
|
||||||
/// Error configuring the special registers
|
/// Error configuring the special registers
|
||||||
#[error("Error configuring the special registers: {0}")]
|
#[error("Error configuring the special registers")]
|
||||||
SregsConfiguration(regs::Error),
|
SregsConfiguration(#[source] regs::Error),
|
||||||
|
|
||||||
/// Error configuring the floating point related registers
|
/// Error configuring the floating point related registers
|
||||||
#[error("Error configuring the floating point related registers: {0}")]
|
#[error("Error configuring the floating point related registers")]
|
||||||
FpuConfiguration(regs::Error),
|
FpuConfiguration(#[source] regs::Error),
|
||||||
|
|
||||||
/// Error configuring the MSR registers
|
/// Error configuring the MSR registers
|
||||||
#[error("Error configuring the MSR registers: {0}")]
|
#[error("Error configuring the MSR registers")]
|
||||||
MsrsConfiguration(regs::Error),
|
MsrsConfiguration(#[source] regs::Error),
|
||||||
|
|
||||||
/// Failed to set supported CPUs.
|
/// Failed to set supported CPUs.
|
||||||
#[error("Failed to set supported CPUs: {0}")]
|
#[error("Failed to set supported CPUs")]
|
||||||
SetSupportedCpusFailed(anyhow::Error),
|
SetSupportedCpusFailed(#[source] anyhow::Error),
|
||||||
|
|
||||||
/// Cannot set the local interruption due to bad configuration.
|
/// Cannot set the local interruption due to bad configuration.
|
||||||
#[error("Cannot set the local interruption due to bad configuration: {0}")]
|
#[error("Cannot set the local interruption due to bad configuration")]
|
||||||
LocalIntConfiguration(anyhow::Error),
|
LocalIntConfiguration(#[source] anyhow::Error),
|
||||||
|
|
||||||
/// Error setting up SMBIOS table
|
/// Error setting up SMBIOS table
|
||||||
#[error("Error setting up SMBIOS table: {0}")]
|
#[error("Error setting up SMBIOS table")]
|
||||||
SmbiosSetup(smbios::Error),
|
SmbiosSetup(#[source] smbios::Error),
|
||||||
|
|
||||||
/// Could not find any SGX EPC section
|
|
||||||
#[error("Could not find any SGX EPC section")]
|
|
||||||
NoSgxEpcSection,
|
|
||||||
|
|
||||||
/// Missing SGX CPU feature
|
|
||||||
#[error("Missing SGX CPU feature")]
|
|
||||||
MissingSgxFeature,
|
|
||||||
|
|
||||||
/// Missing SGX_LC CPU feature
|
|
||||||
#[error("Missing SGX_LC CPU feature")]
|
|
||||||
MissingSgxLaunchControlFeature,
|
|
||||||
|
|
||||||
/// Error getting supported CPUID through the hypervisor (kvm/mshv) API
|
/// Error getting supported CPUID through the hypervisor (kvm/mshv) API
|
||||||
#[error("Error getting supported CPUID through the hypervisor API: {0}")]
|
#[error("Error getting supported CPUID through the hypervisor API")]
|
||||||
CpuidGetSupported(HypervisorError),
|
CpuidGetSupported(#[source] HypervisorError),
|
||||||
|
|
||||||
/// Error populating CPUID with KVM HyperV emulation details
|
/// Error populating CPUID with KVM HyperV emulation details
|
||||||
#[error("Error populating CPUID with KVM HyperV emulation details: {0}")]
|
#[error("Error populating CPUID with KVM HyperV emulation details")]
|
||||||
CpuidKvmHyperV(vmm_sys_util::fam::Error),
|
CpuidKvmHyperV(#[source] vmm_sys_util::fam::Error),
|
||||||
|
|
||||||
/// Error populating CPUID with CPU identification
|
/// Error populating CPUID with CPU identification
|
||||||
#[error("Error populating CPUID with CPU identification: {0}")]
|
#[error("Error populating CPUID with CPU identification")]
|
||||||
CpuidIdentification(vmm_sys_util::fam::Error),
|
CpuidIdentification(#[source] vmm_sys_util::fam::Error),
|
||||||
|
|
||||||
/// Error checking CPUID compatibility
|
/// Error checking CPUID compatibility
|
||||||
#[error("Error checking CPUID compatibility")]
|
#[error("Error checking CPUID compatibility")]
|
||||||
CpuidCheckCompatibility,
|
CpuidCheckCompatibility,
|
||||||
|
|
||||||
// Error writing EBDA address
|
// Error writing EBDA address
|
||||||
#[error("Error writing EBDA address: {0}")]
|
#[error("Error writing EBDA address")]
|
||||||
EbdaSetup(vm_memory::GuestMemoryError),
|
EbdaSetup(#[source] vm_memory::GuestMemoryError),
|
||||||
|
|
||||||
// Error getting CPU TSC frequency
|
// Error getting CPU TSC frequency
|
||||||
#[error("Error getting CPU TSC frequency: {0}")]
|
#[error("Error getting CPU TSC frequency")]
|
||||||
GetTscFrequency(HypervisorCpuError),
|
GetTscFrequency(#[source] HypervisorCpuError),
|
||||||
|
|
||||||
/// Error retrieving TDX capabilities through the hypervisor (kvm/mshv) API
|
/// Error retrieving TDX capabilities through the hypervisor (kvm/mshv) API
|
||||||
#[cfg(feature = "tdx")]
|
#[cfg(feature = "tdx")]
|
||||||
#[error("Error retrieving TDX capabilities through the hypervisor API: {0}")]
|
#[error("Error retrieving TDX capabilities through the hypervisor API")]
|
||||||
TdxCapabilities(HypervisorError),
|
TdxCapabilities(#[source] HypervisorError),
|
||||||
|
|
||||||
/// Failed to configure E820 map for bzImage
|
/// Failed to configure E820 map for bzImage
|
||||||
#[error("Failed to configure E820 map for bzImage")]
|
#[error("Failed to configure E820 map for bzImage")]
|
||||||
E820Configuration,
|
E820Configuration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for super::Error {
|
pub fn get_x2apic_id(cpu_id: u32, topology: Option<(u16, u16, u16, u16)>) -> u32 {
|
||||||
fn from(e: Error) -> super::Error {
|
|
||||||
super::Error::PlatformSpecific(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_x2apic_id(cpu_id: u32, topology: Option<(u8, u8, u8)>) -> u32 {
|
|
||||||
if let Some(t) = topology {
|
if let Some(t) = topology {
|
||||||
let thread_mask_width = u8::BITS - (t.0 - 1).leading_zeros();
|
let thread_mask_width = u16::BITS - (t.0 - 1).leading_zeros();
|
||||||
let core_mask_width = u8::BITS - (t.1 - 1).leading_zeros();
|
let core_mask_width = u16::BITS - (t.1 - 1).leading_zeros();
|
||||||
let die_mask_width = u8::BITS - (t.2 - 1).leading_zeros();
|
let die_mask_width = u16::BITS - (t.2 - 1).leading_zeros();
|
||||||
|
|
||||||
let thread_id = cpu_id % (t.0 as u32);
|
let thread_id = cpu_id % (t.0 as u32);
|
||||||
let core_id = cpu_id / (t.0 as u32) % (t.1 as u32);
|
let core_id = cpu_id / (t.0 as u32) % (t.1 as u32);
|
||||||
@@ -235,6 +174,13 @@ pub fn get_x2apic_id(cpu_id: u32, topology: Option<(u8, u8, u8)>) -> u32 {
|
|||||||
cpu_id
|
cpu_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_max_x2apic_id(topology: (u16, u16, u16, u16)) -> u32 {
|
||||||
|
get_x2apic_id(
|
||||||
|
(topology.0 as u32 * topology.1 as u32 * topology.2 as u32 * topology.3 as u32) - 1,
|
||||||
|
Some(topology),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum CpuidReg {
|
pub enum CpuidReg {
|
||||||
EAX,
|
EAX,
|
||||||
@@ -460,7 +406,7 @@ impl CpuidFeatureEntry {
|
|||||||
feature_reg: CpuidReg::EDX,
|
feature_reg: CpuidReg::EDX,
|
||||||
compatible_check: CpuidCompatibleCheck::BitwiseSubset,
|
compatible_check: CpuidCompatibleCheck::BitwiseSubset,
|
||||||
},
|
},
|
||||||
// KVM CPUID bits: https://www.kernel.org/doc/html/latest/virt/kvm/cpuid.html
|
// KVM CPUID bits: https://www.kernel.org/doc/html/latest/virt/kvm/x86/cpuid.html
|
||||||
// Leaf 0x4000_0000, EAX/EBX/ECX/EDX, KVM CPUID SIGNATURE
|
// Leaf 0x4000_0000, EAX/EBX/ECX/EDX, KVM CPUID SIGNATURE
|
||||||
CpuidFeatureEntry {
|
CpuidFeatureEntry {
|
||||||
function: 0x4000_0000,
|
function: 0x4000_0000,
|
||||||
@@ -579,9 +525,13 @@ impl CpuidFeatureEntry {
|
|||||||
error!(
|
error!(
|
||||||
"Detected incompatible CPUID entry: leaf={:#02x} (subleaf={:#02x}), register='{:?}', \
|
"Detected incompatible CPUID entry: leaf={:#02x} (subleaf={:#02x}), register='{:?}', \
|
||||||
compatible_check='{:?}', source VM feature='{:#04x}', destination VM feature'{:#04x}'.",
|
compatible_check='{:?}', source VM feature='{:#04x}', destination VM feature'{:#04x}'.",
|
||||||
entry.function, entry.index, entry.feature_reg,
|
entry.function,
|
||||||
entry.compatible_check, src_vm_feature, dest_vm_feature
|
entry.index,
|
||||||
);
|
entry.feature_reg,
|
||||||
|
entry.compatible_check,
|
||||||
|
src_vm_feature,
|
||||||
|
dest_vm_feature
|
||||||
|
);
|
||||||
|
|
||||||
compatible = false;
|
compatible = false;
|
||||||
}
|
}
|
||||||
@@ -601,7 +551,7 @@ pub fn generate_common_cpuid(
|
|||||||
config: &CpuidConfig,
|
config: &CpuidConfig,
|
||||||
) -> super::Result<Vec<CpuIdEntry>> {
|
) -> super::Result<Vec<CpuIdEntry>> {
|
||||||
// SAFETY: cpuid called with valid leaves
|
// SAFETY: cpuid called with valid leaves
|
||||||
if unsafe { x86_64::__cpuid(1) }.ecx & 1 << HYPERVISOR_ECX_BIT == 1 << HYPERVISOR_ECX_BIT {
|
if unsafe { x86_64::__cpuid(1) }.ecx & (1 << HYPERVISOR_ECX_BIT) == 1 << HYPERVISOR_ECX_BIT {
|
||||||
// SAFETY: cpuid called with valid leaves
|
// SAFETY: cpuid called with valid leaves
|
||||||
let hypervisor_cpuid = unsafe { x86_64::__cpuid(0x4000_0000) };
|
let hypervisor_cpuid = unsafe { x86_64::__cpuid(0x4000_0000) };
|
||||||
|
|
||||||
@@ -668,10 +618,6 @@ pub fn generate_common_cpuid(
|
|||||||
|
|
||||||
CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches);
|
CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches);
|
||||||
|
|
||||||
if let Some(sgx_epc_sections) = &config.sgx_epc_sections {
|
|
||||||
update_cpuid_sgx(&mut cpuid, sgx_epc_sections)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tdx")]
|
#[cfg(feature = "tdx")]
|
||||||
let tdx_capabilities = if config.tdx {
|
let tdx_capabilities = if config.tdx {
|
||||||
let caps = hypervisor
|
let caps = hypervisor
|
||||||
@@ -689,7 +635,7 @@ pub fn generate_common_cpuid(
|
|||||||
// Clear AMX related bits if the AMX feature is not enabled
|
// Clear AMX related bits if the AMX feature is not enabled
|
||||||
0x7 => {
|
0x7 => {
|
||||||
if !config.amx && entry.index == 0 {
|
if !config.amx && entry.index == 0 {
|
||||||
entry.edx &= !(1 << AMX_BF16 | 1 << AMX_TILE | 1 << AMX_INT8)
|
entry.edx &= !((1 << AMX_BF16) | (1 << AMX_TILE) | (1 << AMX_INT8))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0xd =>
|
0xd =>
|
||||||
@@ -744,15 +690,19 @@ pub fn generate_common_cpuid(
|
|||||||
entry.eax = (entry.eax & 0xffff_ff00) | (config.phys_bits as u32 & 0xff);
|
entry.eax = (entry.eax & 0xffff_ff00) | (config.phys_bits as u32 & 0xff);
|
||||||
}
|
}
|
||||||
0x4000_0001 => {
|
0x4000_0001 => {
|
||||||
|
// Enable KVM_FEATURE_MSI_EXT_DEST_ID. This allows the guest to target
|
||||||
|
// device interrupts to cpus with APIC IDs > 254 without interrupt remapping.
|
||||||
|
entry.eax |= 1 << KVM_FEATURE_MSI_EXT_DEST_ID;
|
||||||
|
|
||||||
// These features are not supported by TDX
|
// These features are not supported by TDX
|
||||||
#[cfg(feature = "tdx")]
|
#[cfg(feature = "tdx")]
|
||||||
if config.tdx {
|
if config.tdx {
|
||||||
entry.eax &= !(1 << KVM_FEATURE_CLOCKSOURCE_BIT
|
entry.eax &= !((1 << KVM_FEATURE_CLOCKSOURCE_BIT)
|
||||||
| 1 << KVM_FEATURE_CLOCKSOURCE2_BIT
|
| (1 << KVM_FEATURE_CLOCKSOURCE2_BIT)
|
||||||
| 1 << KVM_FEATURE_CLOCKSOURCE_STABLE_BIT
|
| (1 << KVM_FEATURE_CLOCKSOURCE_STABLE_BIT)
|
||||||
| 1 << KVM_FEATURE_ASYNC_PF_BIT
|
| (1 << KVM_FEATURE_ASYNC_PF_BIT)
|
||||||
| 1 << KVM_FEATURE_ASYNC_PF_VMEXIT_BIT
|
| (1 << KVM_FEATURE_ASYNC_PF_VMEXIT_BIT)
|
||||||
| 1 << KVM_FEATURE_STEAL_TIME_BIT)
|
| (1 << KVM_FEATURE_STEAL_TIME_BIT))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -801,10 +751,10 @@ pub fn generate_common_cpuid(
|
|||||||
});
|
});
|
||||||
cpuid.push(CpuIdEntry {
|
cpuid.push(CpuIdEntry {
|
||||||
function: 0x4000_0003,
|
function: 0x4000_0003,
|
||||||
eax: 1 << 1 // AccessPartitionReferenceCounter
|
eax: (1 << 1) // AccessPartitionReferenceCounter
|
||||||
| 1 << 2 // AccessSynicRegs
|
| (1 << 2) // AccessSynicRegs
|
||||||
| 1 << 3 // AccessSyntheticTimerRegs
|
| (1 << 3) // AccessSyntheticTimerRegs
|
||||||
| 1 << 9, // AccessPartitionReferenceTsc
|
| (1 << 9), // AccessPartitionReferenceTsc
|
||||||
edx: 1 << 3, // CPU dynamic partitioning
|
edx: 1 << 3, // CPU dynamic partitioning
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
@@ -826,14 +776,14 @@ 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: u32,
|
||||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
||||||
cpuid: Vec<CpuIdEntry>,
|
cpuid: Vec<CpuIdEntry>,
|
||||||
kvm_hyperv: bool,
|
kvm_hyperv: bool,
|
||||||
cpu_vendor: CpuVendor,
|
cpu_vendor: CpuVendor,
|
||||||
topology: Option<(u8, u8, u8)>,
|
topology: (u16, u16, u16, u16),
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
let x2apic_id = get_x2apic_id(id as u32, topology);
|
let x2apic_id = get_x2apic_id(id, Some(topology));
|
||||||
|
|
||||||
// Per vCPU CPUID changes; common are handled via generate_common_cpuid()
|
// Per vCPU CPUID changes; common are handled via generate_common_cpuid()
|
||||||
let mut cpuid = cpuid;
|
let mut cpuid = cpuid;
|
||||||
@@ -855,36 +805,31 @@ pub fn configure_vcpu(
|
|||||||
}
|
}
|
||||||
assert!(apic_id_patched);
|
assert!(apic_id_patched);
|
||||||
|
|
||||||
if let Some(t) = topology {
|
update_cpuid_topology(
|
||||||
update_cpuid_topology(&mut cpuid, t.0, t.1, t.2, cpu_vendor, id);
|
&mut cpuid, topology.0, topology.1, topology.2, topology.3, cpu_vendor, id,
|
||||||
}
|
);
|
||||||
|
|
||||||
// The TSC frequency CPUID leaf should not be included when running with HyperV emulation
|
// The TSC frequency CPUID leaf should not be included when running with HyperV emulation
|
||||||
if !kvm_hyperv {
|
if !kvm_hyperv && let Some(tsc_khz) = vcpu.tsc_khz().map_err(Error::GetTscFrequency)? {
|
||||||
if let Some(tsc_khz) = vcpu.tsc_khz().map_err(Error::GetTscFrequency)? {
|
// Need to check that the TSC doesn't vary with dynamic frequency
|
||||||
// Need to check that the TSC doesn't vary with dynamic frequency
|
// SAFETY: cpuid called with valid leaves
|
||||||
// SAFETY: cpuid called with valid leaves
|
if unsafe { std::arch::x86_64::__cpuid(0x8000_0007) }.edx & (1u32 << INVARIANT_TSC_EDX_BIT)
|
||||||
if unsafe { std::arch::x86_64::__cpuid(0x8000_0007) }.edx
|
> 0
|
||||||
& (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);
|
||||||
CpuidPatch::set_cpuid_reg(
|
cpuid.push(CpuIdEntry {
|
||||||
&mut cpuid,
|
function: 0x4000_0010,
|
||||||
0x4000_0000,
|
eax: tsc_khz,
|
||||||
None,
|
ebx: 1000000, /* LAPIC resolution of 1ns (freq: 1GHz) is hardcoded in KVM's
|
||||||
CpuidReg::EAX,
|
* APIC_BUS_CYCLE_NS */
|
||||||
0x4000_0010,
|
..Default::default()
|
||||||
);
|
});
|
||||||
cpuid.retain(|c| c.function != 0x4000_0010);
|
};
|
||||||
cpuid.push(CpuIdEntry {
|
}
|
||||||
function: 0x4000_0010,
|
|
||||||
eax: tsc_khz,
|
for c in &cpuid {
|
||||||
ebx: 1000000, /* LAPIC resolution of 1ns (freq: 1GHz) is hardcoded in KVM's
|
debug!("{}", c);
|
||||||
* APIC_BUS_CYCLE_NS */
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vcpu.set_cpuid2(&cpuid)
|
vcpu.set_cpuid2(&cpuid)
|
||||||
@@ -898,7 +843,15 @@ pub fn configure_vcpu(
|
|||||||
if let Some((kernel_entry_point, guest_memory)) = boot_setup {
|
if let Some((kernel_entry_point, guest_memory)) = boot_setup {
|
||||||
regs::setup_regs(vcpu, kernel_entry_point).map_err(Error::RegsConfiguration)?;
|
regs::setup_regs(vcpu, kernel_entry_point).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)?;
|
|
||||||
|
// CPUs are required (by Intel sdm spec) to boot in x2apic mode if any
|
||||||
|
// of the apic IDs is larger than 255. Experimentally, the Linux kernel
|
||||||
|
// does not recognize the last vCPU if x2apic is not enabled when
|
||||||
|
// there are 256 vCPUs in a flat hierarchy (i.e. max x2apic ID is 255),
|
||||||
|
// so we need to enable x2apic in this case as well.
|
||||||
|
let enable_x2_apic_mode = get_max_x2apic_id(topology) > MAX_SUPPORTED_CPUS_LEGACY;
|
||||||
|
regs::setup_sregs(&guest_memory.memory(), vcpu, enable_x2_apic_mode)
|
||||||
|
.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(())
|
||||||
@@ -948,14 +901,13 @@ pub fn configure_system(
|
|||||||
cmdline_addr: GuestAddress,
|
cmdline_addr: GuestAddress,
|
||||||
cmdline_size: usize,
|
cmdline_size: usize,
|
||||||
initramfs: &Option<InitramfsConfig>,
|
initramfs: &Option<InitramfsConfig>,
|
||||||
_num_cpus: u8,
|
_num_cpus: u32,
|
||||||
setup_header: Option<setup_header>,
|
setup_header: Option<setup_header>,
|
||||||
rsdp_addr: Option<GuestAddress>,
|
rsdp_addr: Option<GuestAddress>,
|
||||||
sgx_epc_region: Option<SgxEpcRegion>,
|
|
||||||
serial_number: Option<&str>,
|
serial_number: Option<&str>,
|
||||||
uuid: Option<&str>,
|
uuid: Option<&str>,
|
||||||
oem_strings: Option<&[&str]>,
|
oem_strings: Option<&[&str]>,
|
||||||
topology: Option<(u8, u8, u8)>,
|
topology: Option<(u16, u16, u16, u16)>,
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
// Write EBDA address to location where ACPICA expects to find it
|
// Write EBDA address to location where ACPICA expects to find it
|
||||||
guest_mem
|
guest_mem
|
||||||
@@ -971,10 +923,10 @@ pub fn configure_system(
|
|||||||
mptable::setup_mptable(offset, guest_mem, _num_cpus, topology).map_err(Error::MpTableSetup)?;
|
mptable::setup_mptable(offset, guest_mem, _num_cpus, topology).map_err(Error::MpTableSetup)?;
|
||||||
|
|
||||||
// Check that the RAM is not smaller than the RSDP start address
|
// Check that the RAM is not smaller than the RSDP start address
|
||||||
if let Some(rsdp_addr) = rsdp_addr {
|
if let Some(rsdp_addr) = rsdp_addr
|
||||||
if rsdp_addr.0 > guest_mem.last_addr().0 {
|
&& rsdp_addr.0 > guest_mem.last_addr().0
|
||||||
return Err(super::Error::RsdpPastRamEnd);
|
{
|
||||||
}
|
return Err(super::Error::RsdpPastRamEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
match setup_header {
|
match setup_header {
|
||||||
@@ -985,15 +937,8 @@ pub fn configure_system(
|
|||||||
initramfs,
|
initramfs,
|
||||||
hdr,
|
hdr,
|
||||||
rsdp_addr,
|
rsdp_addr,
|
||||||
sgx_epc_region,
|
|
||||||
),
|
|
||||||
None => configure_pvh(
|
|
||||||
guest_mem,
|
|
||||||
cmdline_addr,
|
|
||||||
initramfs,
|
|
||||||
rsdp_addr,
|
|
||||||
sgx_epc_region,
|
|
||||||
),
|
),
|
||||||
|
None => configure_pvh(guest_mem, cmdline_addr, initramfs, rsdp_addr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1085,7 +1030,6 @@ fn configure_pvh(
|
|||||||
cmdline_addr: GuestAddress,
|
cmdline_addr: GuestAddress,
|
||||||
initramfs: &Option<InitramfsConfig>,
|
initramfs: &Option<InitramfsConfig>,
|
||||||
rsdp_addr: Option<GuestAddress>,
|
rsdp_addr: Option<GuestAddress>,
|
||||||
sgx_epc_region: Option<SgxEpcRegion>,
|
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578;
|
const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578;
|
||||||
|
|
||||||
@@ -1151,15 +1095,6 @@ fn configure_pvh(
|
|||||||
E820_RESERVED,
|
E820_RESERVED,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(sgx_epc_region) = sgx_epc_region {
|
|
||||||
add_memmap_entry(
|
|
||||||
&mut memmap,
|
|
||||||
sgx_epc_region.start().raw_value(),
|
|
||||||
sgx_epc_region.size(),
|
|
||||||
E820_RESERVED,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
start_info.memmap_entries = memmap.len() as u32;
|
start_info.memmap_entries = memmap.len() as u32;
|
||||||
|
|
||||||
// Copy the vector with the memmap table to the MEMMAP_START address
|
// Copy the vector with the memmap table to the MEMMAP_START address
|
||||||
@@ -1206,7 +1141,6 @@ fn configure_32bit_entry(
|
|||||||
initramfs: &Option<InitramfsConfig>,
|
initramfs: &Option<InitramfsConfig>,
|
||||||
setup_hdr: setup_header,
|
setup_hdr: setup_header,
|
||||||
rsdp_addr: Option<GuestAddress>,
|
rsdp_addr: Option<GuestAddress>,
|
||||||
sgx_epc_region: Option<SgxEpcRegion>,
|
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
const KERNEL_LOADER_OTHER: u8 = 0xff;
|
const KERNEL_LOADER_OTHER: u8 = 0xff;
|
||||||
|
|
||||||
@@ -1262,15 +1196,6 @@ fn configure_32bit_entry(
|
|||||||
E820_RESERVED,
|
E820_RESERVED,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(sgx_epc_region) = sgx_epc_region {
|
|
||||||
add_e820_entry(
|
|
||||||
&mut params,
|
|
||||||
sgx_epc_region.start().raw_value(),
|
|
||||||
sgx_epc_region.size(),
|
|
||||||
E820_RESERVED,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(rsdp_addr) = rsdp_addr {
|
if let Some(rsdp_addr) = rsdp_addr {
|
||||||
params.acpi_rsdp_addr = rsdp_addr.0;
|
params.acpi_rsdp_addr = rsdp_addr.0;
|
||||||
}
|
}
|
||||||
@@ -1363,30 +1288,34 @@ pub fn get_host_cpu_phys_bits(hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u
|
|||||||
|
|
||||||
fn update_cpuid_topology(
|
fn update_cpuid_topology(
|
||||||
cpuid: &mut Vec<CpuIdEntry>,
|
cpuid: &mut Vec<CpuIdEntry>,
|
||||||
threads_per_core: u8,
|
threads_per_core: u16,
|
||||||
cores_per_die: u8,
|
cores_per_die: u16,
|
||||||
dies_per_package: u8,
|
dies_per_package: u16,
|
||||||
|
packages: u16,
|
||||||
cpu_vendor: CpuVendor,
|
cpu_vendor: CpuVendor,
|
||||||
id: u8,
|
id: u32,
|
||||||
) {
|
) {
|
||||||
let x2apic_id = get_x2apic_id(
|
let x2apic_id = get_x2apic_id(
|
||||||
id as u32,
|
id,
|
||||||
Some((threads_per_core, cores_per_die, dies_per_package)),
|
Some((threads_per_core, cores_per_die, dies_per_package, packages)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let thread_width = 8 - (threads_per_core - 1).leading_zeros();
|
// Note: the topology defined here is per "package" (~NUMA node).
|
||||||
let core_width = (8 - (cores_per_die - 1).leading_zeros()) + thread_width;
|
let thread_width = u16::BITS - (threads_per_core - 1).leading_zeros();
|
||||||
let die_width = (8 - (dies_per_package - 1).leading_zeros()) + core_width;
|
let core_width = u16::BITS - (cores_per_die - 1).leading_zeros() + thread_width;
|
||||||
|
let die_width = u16::BITS - (dies_per_package - 1).leading_zeros() + core_width;
|
||||||
|
|
||||||
|
// The very old way: a flat number of logical CPUs per package: CPUID.1H:EBX[23:16] bits.
|
||||||
let mut cpu_ebx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX).unwrap_or(0);
|
let mut cpu_ebx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX).unwrap_or(0);
|
||||||
cpu_ebx |= ((dies_per_package as u32) * (cores_per_die as u32) * (threads_per_core as u32))
|
cpu_ebx |= ((dies_per_package as u32) * (cores_per_die as u32) * (threads_per_core as u32))
|
||||||
& 0xff << 16;
|
& (0xff << 16);
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX, cpu_ebx);
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX, cpu_ebx);
|
||||||
|
|
||||||
let mut cpu_edx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EDX).unwrap_or(0);
|
let mut cpu_edx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EDX).unwrap_or(0);
|
||||||
cpu_edx |= 1 << 28;
|
cpu_edx |= 1 << 28;
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1, None, CpuidReg::EDX, cpu_edx);
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1, None, CpuidReg::EDX, cpu_edx);
|
||||||
|
|
||||||
|
// The legacy way: threads+cores per package.
|
||||||
// CPU Topology leaf 0xb
|
// CPU Topology leaf 0xb
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(0), CpuidReg::EAX, thread_width);
|
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(0), CpuidReg::EAX, thread_width);
|
||||||
CpuidPatch::set_cpuid_reg(
|
CpuidPatch::set_cpuid_reg(
|
||||||
@@ -1407,7 +1336,9 @@ fn update_cpuid_topology(
|
|||||||
u32::from(dies_per_package * cores_per_die * threads_per_core),
|
u32::from(dies_per_package * cores_per_die * threads_per_core),
|
||||||
);
|
);
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(1), CpuidReg::ECX, 2 << 8);
|
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(1), CpuidReg::ECX, 2 << 8);
|
||||||
|
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(1), CpuidReg::EDX, x2apic_id);
|
||||||
|
|
||||||
|
// The modern way: many-level hierarchy (but we here only support four levels).
|
||||||
// CPU Topology leaf 0x1f
|
// CPU Topology leaf 0x1f
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(0), CpuidReg::EAX, thread_width);
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(0), CpuidReg::EAX, thread_width);
|
||||||
CpuidPatch::set_cpuid_reg(
|
CpuidPatch::set_cpuid_reg(
|
||||||
@@ -1498,57 +1429,6 @@ fn update_cpuid_topology(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The goal is to update the CPUID sub-leaves to reflect the number of EPC
|
|
||||||
// sections exposed to the guest.
|
|
||||||
fn update_cpuid_sgx(
|
|
||||||
cpuid: &mut Vec<CpuIdEntry>,
|
|
||||||
epc_sections: &[SgxEpcSection],
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Something's wrong if there's no EPC section.
|
|
||||||
if epc_sections.is_empty() {
|
|
||||||
return Err(Error::NoSgxEpcSection);
|
|
||||||
}
|
|
||||||
// We can't go further if the hypervisor does not support SGX feature.
|
|
||||||
if !CpuidPatch::is_feature_enabled(cpuid, 0x7, 0, CpuidReg::EBX, 2) {
|
|
||||||
return Err(Error::MissingSgxFeature);
|
|
||||||
}
|
|
||||||
// We can't go further if the hypervisor does not support SGX_LC feature.
|
|
||||||
if !CpuidPatch::is_feature_enabled(cpuid, 0x7, 0, CpuidReg::ECX, 30) {
|
|
||||||
return Err(Error::MissingSgxLaunchControlFeature);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get host CPUID for leaf 0x12, subleaf 0x2. This is to retrieve EPC
|
|
||||||
// properties such as confidentiality and integrity.
|
|
||||||
// SAFETY: call cpuid with valid leaves
|
|
||||||
let leaf = unsafe { std::arch::x86_64::__cpuid_count(0x12, 0x2) };
|
|
||||||
|
|
||||||
for (i, epc_section) in epc_sections.iter().enumerate() {
|
|
||||||
let subleaf_idx = i + 2;
|
|
||||||
let start = epc_section.start().raw_value();
|
|
||||||
let size = epc_section.size();
|
|
||||||
let eax = (start & 0xffff_f000) as u32 | 0x1;
|
|
||||||
let ebx = (start >> 32) as u32;
|
|
||||||
let ecx = (size & 0xffff_f000) as u32 | (leaf.ecx & 0xf);
|
|
||||||
let edx = (size >> 32) as u32;
|
|
||||||
// CPU Topology leaf 0x12
|
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EAX, eax);
|
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EBX, ebx);
|
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::ECX, ecx);
|
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EDX, edx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add one NULL entry to terminate the dynamic list
|
|
||||||
let subleaf_idx = epc_sections.len() + 2;
|
|
||||||
// CPU Topology leaf 0x12
|
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EAX, 0);
|
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EBX, 0);
|
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::ECX, 0);
|
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EDX, 0);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use linux_loader::loader::bootparam::boot_e820_entry;
|
use linux_loader::loader::bootparam::boot_e820_entry;
|
||||||
@@ -1579,7 +1459,6 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
);
|
);
|
||||||
config_err.unwrap_err();
|
config_err.unwrap_err();
|
||||||
|
|
||||||
@@ -1604,7 +1483,6 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1634,7 +1512,6 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1650,7 +1527,6 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@@ -1722,22 +1598,27 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_x2apic_id() {
|
fn test_get_x2apic_id() {
|
||||||
let x2apic_id = get_x2apic_id(0, Some((2, 3, 1)));
|
let x2apic_id = get_x2apic_id(0, Some((2, 3, 1, 1)));
|
||||||
assert_eq!(x2apic_id, 0);
|
assert_eq!(x2apic_id, 0);
|
||||||
|
|
||||||
let x2apic_id = get_x2apic_id(1, Some((2, 3, 1)));
|
let x2apic_id = get_x2apic_id(1, Some((2, 3, 1, 1)));
|
||||||
assert_eq!(x2apic_id, 1);
|
assert_eq!(x2apic_id, 1);
|
||||||
|
|
||||||
let x2apic_id = get_x2apic_id(2, Some((2, 3, 1)));
|
let x2apic_id = get_x2apic_id(2, Some((2, 3, 1, 1)));
|
||||||
assert_eq!(x2apic_id, 2);
|
assert_eq!(x2apic_id, 2);
|
||||||
|
|
||||||
let x2apic_id = get_x2apic_id(6, Some((2, 3, 1)));
|
let x2apic_id = get_x2apic_id(6, Some((2, 3, 1, 1)));
|
||||||
assert_eq!(x2apic_id, 8);
|
assert_eq!(x2apic_id, 8);
|
||||||
|
|
||||||
let x2apic_id = get_x2apic_id(7, Some((2, 3, 1)));
|
let x2apic_id = get_x2apic_id(7, Some((2, 3, 1, 1)));
|
||||||
assert_eq!(x2apic_id, 9);
|
assert_eq!(x2apic_id, 9);
|
||||||
|
|
||||||
let x2apic_id = get_x2apic_id(8, Some((2, 3, 1)));
|
let x2apic_id = get_x2apic_id(8, Some((2, 3, 1, 1)));
|
||||||
assert_eq!(x2apic_id, 10);
|
assert_eq!(x2apic_id, 10);
|
||||||
|
|
||||||
|
let x2apic_id = get_x2apic_id(257, Some((1, 312, 1, 1)));
|
||||||
|
assert_eq!(x2apic_id, 257);
|
||||||
|
|
||||||
|
assert_eq!(255, get_max_x2apic_id((1, 256, 1, 1)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
use vm_memory::ByteValued;
|
||||||
|
|
||||||
pub const MP_PROCESSOR: ::std::os::raw::c_uint = 0;
|
pub const MP_PROCESSOR: ::std::os::raw::c_uint = 0;
|
||||||
pub const MP_BUS: ::std::os::raw::c_uint = 1;
|
pub const MP_BUS: ::std::os::raw::c_uint = 1;
|
||||||
@@ -29,6 +31,13 @@ pub struct mpf_intel {
|
|||||||
pub feature5: ::std::os::raw::c_uchar,
|
pub feature5: ::std::os::raw::c_uchar,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(::core::mem::size_of::<mpf_intel>() == 16);
|
||||||
|
// SAFETY: all members of this struct are plain integers
|
||||||
|
// and the sum of their sizes is the size of the struct, so
|
||||||
|
// padding and reserved values are not possible as there
|
||||||
|
// would be nowhere for them to exist.
|
||||||
|
unsafe impl ByteValued for mpf_intel {}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_table {
|
pub struct mpc_table {
|
||||||
@@ -45,6 +54,19 @@ pub struct mpc_table {
|
|||||||
pub reserved: ::std::os::raw::c_uint,
|
pub reserved: ::std::os::raw::c_uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _: () = {
|
||||||
|
assert!(::core::mem::size_of::<mpc_table>() == 4 + 2 + 1 + 1 + 8 + 12 + 4 + 2 + 2 + 4 + 4);
|
||||||
|
assert!(::core::mem::size_of::<::std::os::raw::c_uint>() == 4);
|
||||||
|
assert!(::core::mem::size_of::<::std::os::raw::c_ushort>() == 2);
|
||||||
|
assert!(::core::mem::size_of::<::std::os::raw::c_uchar>() == 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY: all members of this struct are plain integers
|
||||||
|
// and the sum of their sizes is the size of the struct, so
|
||||||
|
// padding and reserved values are not possible as there
|
||||||
|
// would be nowhere for them to exist.
|
||||||
|
unsafe impl ByteValued for mpc_table {}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_cpu {
|
pub struct mpc_cpu {
|
||||||
@@ -57,6 +79,13 @@ pub struct mpc_cpu {
|
|||||||
pub reserved: [::std::os::raw::c_uint; 2usize],
|
pub reserved: [::std::os::raw::c_uint; 2usize],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(::core::mem::size_of::<mpc_cpu>() == 20);
|
||||||
|
// SAFETY: all members of this struct are plain integers
|
||||||
|
// and the sum of their sizes is the size of the struct, so
|
||||||
|
// padding and reserved values are not possible as there
|
||||||
|
// would be nowhere for them to exist.
|
||||||
|
unsafe impl ByteValued for mpc_cpu {}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_bus {
|
pub struct mpc_bus {
|
||||||
@@ -65,6 +94,13 @@ pub struct mpc_bus {
|
|||||||
pub bustype: [::std::os::raw::c_uchar; 6usize],
|
pub bustype: [::std::os::raw::c_uchar; 6usize],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(::core::mem::size_of::<mpc_bus>() == 8);
|
||||||
|
// SAFETY: all members of this struct are plain integers
|
||||||
|
// and the sum of their sizes is the size of the struct, so
|
||||||
|
// padding and reserved values are not possible as there
|
||||||
|
// would be nowhere for them to exist.
|
||||||
|
unsafe impl ByteValued for mpc_bus {}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_ioapic {
|
pub struct mpc_ioapic {
|
||||||
@@ -75,6 +111,13 @@ pub struct mpc_ioapic {
|
|||||||
pub apicaddr: ::std::os::raw::c_uint,
|
pub apicaddr: ::std::os::raw::c_uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(::core::mem::size_of::<mpc_ioapic>() == 8);
|
||||||
|
// SAFETY: all members of this struct are plain integers
|
||||||
|
// and the sum of their sizes is the size of the struct, so
|
||||||
|
// padding and reserved values are not possible as there
|
||||||
|
// would be nowhere for them to exist.
|
||||||
|
unsafe impl ByteValued for mpc_ioapic {}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_intsrc {
|
pub struct mpc_intsrc {
|
||||||
@@ -87,6 +130,13 @@ pub struct mpc_intsrc {
|
|||||||
pub dstirq: ::std::os::raw::c_uchar,
|
pub dstirq: ::std::os::raw::c_uchar,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(::core::mem::size_of::<mpc_intsrc>() == 8);
|
||||||
|
// SAFETY: all members of this struct are plain integers
|
||||||
|
// and the sum of their sizes is the size of the struct, so
|
||||||
|
// padding and reserved values are not possible as there
|
||||||
|
// would be nowhere for them to exist.
|
||||||
|
unsafe impl ByteValued for mpc_intsrc {}
|
||||||
|
|
||||||
pub const MP_IRQ_SOURCE_TYPES_MP_INT: ::std::os::raw::c_uint = 0;
|
pub const MP_IRQ_SOURCE_TYPES_MP_INT: ::std::os::raw::c_uint = 0;
|
||||||
pub const MP_IRQ_SOURCE_TYPES_MP_NMI: ::std::os::raw::c_uint = 1;
|
pub const MP_IRQ_SOURCE_TYPES_MP_NMI: ::std::os::raw::c_uint = 1;
|
||||||
pub const MP_IRQ_SOURCE_TYPES_MP_EXT_INT: ::std::os::raw::c_uint = 3;
|
pub const MP_IRQ_SOURCE_TYPES_MP_EXT_INT: ::std::os::raw::c_uint = 3;
|
||||||
@@ -103,6 +153,13 @@ pub struct mpc_lintsrc {
|
|||||||
pub destapiclint: ::std::os::raw::c_uchar,
|
pub destapiclint: ::std::os::raw::c_uchar,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(::core::mem::size_of::<mpc_lintsrc>() == 8);
|
||||||
|
// SAFETY: all members of this struct are plain integers
|
||||||
|
// and the sum of their sizes is the size of the struct, so
|
||||||
|
// padding and reserved values are not possible as there
|
||||||
|
// would be nowhere for them to exist.
|
||||||
|
unsafe impl ByteValued for mpc_lintsrc {}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_oemtable {
|
pub struct mpc_oemtable {
|
||||||
@@ -112,3 +169,10 @@ pub struct mpc_oemtable {
|
|||||||
pub checksum: ::std::os::raw::c_uchar,
|
pub checksum: ::std::os::raw::c_uchar,
|
||||||
pub mpc: [::std::os::raw::c_uchar; 8usize],
|
pub mpc: [::std::os::raw::c_uchar; 8usize],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(::core::mem::size_of::<mpc_oemtable>() == 16);
|
||||||
|
// SAFETY: all members of this struct are plain integers
|
||||||
|
// and the sum of their sizes is the size of the struct, so
|
||||||
|
// padding and reserved values are not possible as there
|
||||||
|
// would be nowhere for them to exist.
|
||||||
|
unsafe impl ByteValued for mpc_oemtable {}
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ use libc::c_uchar;
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
||||||
|
|
||||||
|
use super::MAX_SUPPORTED_CPUS_LEGACY;
|
||||||
|
use crate::GuestMemoryMmap;
|
||||||
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
||||||
use crate::x86_64::{get_x2apic_id, mpspec};
|
use crate::x86_64::{get_x2apic_id, mpspec};
|
||||||
use crate::GuestMemoryMmap;
|
|
||||||
|
|
||||||
// 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
|
||||||
// trait (in this case `ByteValued`) where:
|
// trait (in this case `ByteValued`) where:
|
||||||
@@ -59,41 +60,33 @@ pub enum Error {
|
|||||||
#[error("The MP table has too little address space to be stored")]
|
#[error("The MP table has too little address space to be stored")]
|
||||||
AddressOverflow,
|
AddressOverflow,
|
||||||
/// Failure while zeroing out the memory for the MP table.
|
/// Failure while zeroing out the memory for the MP table.
|
||||||
#[error("Failure while zeroing out the memory for the MP table: {0}")]
|
#[error("Failure while zeroing out the memory for the MP table")]
|
||||||
Clear(GuestMemoryError),
|
Clear(#[source] GuestMemoryError),
|
||||||
/// Number of CPUs exceeds the maximum supported CPUs
|
|
||||||
#[error("Number of CPUs exceeds the maximum supported CPUs")]
|
|
||||||
TooManyCpus,
|
|
||||||
/// Failure to write the MP floating pointer.
|
/// Failure to write the MP floating pointer.
|
||||||
#[error("Failure to write the MP floating pointer: {0}")]
|
#[error("Failure to write the MP floating pointer")]
|
||||||
WriteMpfIntel(GuestMemoryError),
|
WriteMpfIntel(#[source] GuestMemoryError),
|
||||||
/// Failure to write MP CPU entry.
|
/// Failure to write MP CPU entry.
|
||||||
#[error("Failure to write MP CPU entry: {0}")]
|
#[error("Failure to write MP CPU entry")]
|
||||||
WriteMpcCpu(GuestMemoryError),
|
WriteMpcCpu(#[source] GuestMemoryError),
|
||||||
/// Failure to write MP ioapic entry.
|
/// Failure to write MP ioapic entry.
|
||||||
#[error("Failure to write MP ioapic entry: {0}")]
|
#[error("Failure to write MP ioapic entry")]
|
||||||
WriteMpcIoapic(GuestMemoryError),
|
WriteMpcIoapic(#[source] GuestMemoryError),
|
||||||
/// Failure to write MP bus entry.
|
/// Failure to write MP bus entry.
|
||||||
#[error("Failure to write MP bus entry: {0}")]
|
#[error("Failure to write MP bus entry")]
|
||||||
WriteMpcBus(GuestMemoryError),
|
WriteMpcBus(#[source] GuestMemoryError),
|
||||||
/// Failure to write MP interrupt source entry.
|
/// Failure to write MP interrupt source entry.
|
||||||
#[error("Failure to write MP interrupt source entry: {0}")]
|
#[error("Failure to write MP interrupt source entry")]
|
||||||
WriteMpcIntsrc(GuestMemoryError),
|
WriteMpcIntsrc(#[source] GuestMemoryError),
|
||||||
/// Failure to write MP local interrupt source entry.
|
/// Failure to write MP local interrupt source entry.
|
||||||
#[error("Failure to write MP local interrupt source entry: {0}")]
|
#[error("Failure to write MP local interrupt source entry")]
|
||||||
WriteMpcLintsrc(GuestMemoryError),
|
WriteMpcLintsrc(#[source] GuestMemoryError),
|
||||||
/// Failure to write MP table header.
|
/// Failure to write MP table header.
|
||||||
#[error("Failure to write MP table header: {0}")]
|
#[error("Failure to write MP table header")]
|
||||||
WriteMpcTable(GuestMemoryError),
|
WriteMpcTable(#[source] GuestMemoryError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
// With APIC/xAPIC, there are only 255 APIC IDs available. And IOAPIC occupies
|
|
||||||
// one APIC ID, so only 254 CPUs at maximum may be supported. Actually it's
|
|
||||||
// a large number for FC usecases.
|
|
||||||
pub const MAX_SUPPORTED_CPUS: u32 = 254;
|
|
||||||
|
|
||||||
// Most of these variables are sourced from the Intel MP Spec 1.4.
|
// Most of these variables are sourced from the Intel MP Spec 1.4.
|
||||||
const SMP_MAGIC_IDENT: &[c_uchar; 4] = b"_MP_";
|
const SMP_MAGIC_IDENT: &[c_uchar; 4] = b"_MP_";
|
||||||
const MPC_SIGNATURE: &[c_uchar; 4] = b"PCMP";
|
const MPC_SIGNATURE: &[c_uchar; 4] = b"PCMP";
|
||||||
@@ -106,7 +99,7 @@ const CPU_STEPPING: u32 = 0x600;
|
|||||||
const CPU_FEATURE_APIC: u32 = 0x200;
|
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 + ByteValued>(v: &T) -> u8 {
|
||||||
// SAFETY: 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;
|
||||||
@@ -121,7 +114,7 @@ fn mpf_intel_compute_checksum(v: &mpspec::mpf_intel) -> u8 {
|
|||||||
(!checksum).wrapping_add(1)
|
(!checksum).wrapping_add(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_mp_size(num_cpus: u8) -> usize {
|
fn compute_mp_size(num_cpus: u32) -> usize {
|
||||||
mem::size_of::<MpfIntelWrapper>()
|
mem::size_of::<MpfIntelWrapper>()
|
||||||
+ mem::size_of::<MpcTableWrapper>()
|
+ mem::size_of::<MpcTableWrapper>()
|
||||||
+ mem::size_of::<MpcCpuWrapper>() * (num_cpus as usize)
|
+ mem::size_of::<MpcCpuWrapper>() * (num_cpus as usize)
|
||||||
@@ -135,14 +128,15 @@ fn compute_mp_size(num_cpus: u8) -> usize {
|
|||||||
pub fn setup_mptable(
|
pub fn setup_mptable(
|
||||||
offset: GuestAddress,
|
offset: GuestAddress,
|
||||||
mem: &GuestMemoryMmap,
|
mem: &GuestMemoryMmap,
|
||||||
num_cpus: u8,
|
num_cpus: u32,
|
||||||
topology: Option<(u8, u8, u8)>,
|
topology: Option<(u16, u16, u16, u16)>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if num_cpus > 0 {
|
if num_cpus > 0 {
|
||||||
let cpu_id_max = num_cpus - 1;
|
let cpu_id_max = num_cpus - 1;
|
||||||
let x2apic_id_max = get_x2apic_id(cpu_id_max.into(), topology);
|
let x2apic_id_max = get_x2apic_id(cpu_id_max, topology);
|
||||||
if x2apic_id_max >= MAX_SUPPORTED_CPUS {
|
if x2apic_id_max >= MAX_SUPPORTED_CPUS_LEGACY {
|
||||||
return Err(Error::TooManyCpus);
|
info!("Skipping mptable creation due to too many CPUs");
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +151,7 @@ pub fn setup_mptable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut checksum: u8 = 0;
|
let mut checksum: u8 = 0;
|
||||||
let ioapicid: u8 = MAX_SUPPORTED_CPUS as u8 + 1;
|
let ioapicid: u8 = MAX_SUPPORTED_CPUS_LEGACY as u8 + 1;
|
||||||
|
|
||||||
// The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
|
// The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
|
||||||
// overflow.
|
// overflow.
|
||||||
@@ -195,7 +189,7 @@ pub fn setup_mptable(
|
|||||||
for cpu_id in 0..num_cpus {
|
for cpu_id in 0..num_cpus {
|
||||||
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
|
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
|
||||||
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
|
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
|
||||||
mpc_cpu.0.apicid = get_x2apic_id(cpu_id as u32, topology) as u8;
|
mpc_cpu.0.apicid = get_x2apic_id(cpu_id, topology) as u8;
|
||||||
mpc_cpu.0.apicver = APIC_VERSION;
|
mpc_cpu.0.apicver = APIC_VERSION;
|
||||||
mpc_cpu.0.cpuflag = mpspec::CPU_ENABLED as u8
|
mpc_cpu.0.cpuflag = mpspec::CPU_ENABLED as u8
|
||||||
| if cpu_id == 0 {
|
| if cpu_id == 0 {
|
||||||
@@ -394,11 +388,11 @@ mod tests {
|
|||||||
fn cpu_entry_count() {
|
fn cpu_entry_count() {
|
||||||
let mem = GuestMemoryMmap::from_ranges(&[(
|
let mem = GuestMemoryMmap::from_ranges(&[(
|
||||||
MPTABLE_START,
|
MPTABLE_START,
|
||||||
compute_mp_size(MAX_SUPPORTED_CPUS as u8),
|
compute_mp_size(MAX_SUPPORTED_CPUS_LEGACY),
|
||||||
)])
|
)])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for i in 0..MAX_SUPPORTED_CPUS as u8 {
|
for i in 0..MAX_SUPPORTED_CPUS_LEGACY {
|
||||||
setup_mptable(MPTABLE_START, &mem, i, None).unwrap();
|
setup_mptable(MPTABLE_START, &mem, i, None).unwrap();
|
||||||
|
|
||||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||||
@@ -428,11 +422,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cpu_entry_count_max() {
|
fn cpu_entry_count_max() {
|
||||||
let cpus = MAX_SUPPORTED_CPUS + 1;
|
let cpus = MAX_SUPPORTED_CPUS_LEGACY + 1;
|
||||||
let mem =
|
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus))]).unwrap();
|
||||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
|
|
||||||
|
|
||||||
let result = setup_mptable(MPTABLE_START, &mem, cpus as u8, None);
|
setup_mptable(MPTABLE_START, &mem, cpus, None).unwrap();
|
||||||
result.unwrap_err();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,41 +23,41 @@ use crate::{EntryPoint, GuestMemoryMmap};
|
|||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failed to get SREGs for this CPU.
|
/// Failed to get SREGs for this CPU.
|
||||||
#[error("Failed to get SREGs for this CPU: {0}")]
|
#[error("Failed to get SREGs for this CPU")]
|
||||||
GetStatusRegisters(hypervisor::HypervisorCpuError),
|
GetStatusRegisters(#[source] hypervisor::HypervisorCpuError),
|
||||||
/// Failed to set base registers for this CPU.
|
/// Failed to set base registers for this CPU.
|
||||||
#[error("Failed to set base registers for this CPU: {0}")]
|
#[error("Failed to set base registers for this CPU")]
|
||||||
SetBaseRegisters(hypervisor::HypervisorCpuError),
|
SetBaseRegisters(#[source] hypervisor::HypervisorCpuError),
|
||||||
/// Failed to configure the FPU.
|
/// Failed to configure the FPU.
|
||||||
#[error("Failed to configure the FPU: {0}")]
|
#[error("Failed to configure the FPU")]
|
||||||
SetFpuRegisters(hypervisor::HypervisorCpuError),
|
SetFpuRegisters(#[source] hypervisor::HypervisorCpuError),
|
||||||
/// Setting up MSRs failed.
|
/// Setting up MSRs failed.
|
||||||
#[error("Setting up MSRs failed: {0}")]
|
#[error("Setting up MSRs failed")]
|
||||||
SetModelSpecificRegisters(hypervisor::HypervisorCpuError),
|
SetModelSpecificRegisters(#[source] hypervisor::HypervisorCpuError),
|
||||||
/// Failed to set SREGs for this CPU.
|
/// Failed to set SREGs for this CPU.
|
||||||
#[error("Failed to set SREGs for this CPU: {0}")]
|
#[error("Failed to set SREGs for this CPU")]
|
||||||
SetStatusRegisters(hypervisor::HypervisorCpuError),
|
SetStatusRegisters(#[source] hypervisor::HypervisorCpuError),
|
||||||
/// Checking the GDT address failed.
|
/// Checking the GDT address failed.
|
||||||
#[error("Checking the GDT address failed")]
|
#[error("Checking the GDT address failed")]
|
||||||
CheckGdtAddr,
|
CheckGdtAddr,
|
||||||
/// Writing the GDT to RAM failed.
|
/// Writing the GDT to RAM failed.
|
||||||
#[error("Writing the GDT to RAM failed: {0}")]
|
#[error("Writing the GDT to RAM failed")]
|
||||||
WriteGdt(GuestMemoryError),
|
WriteGdt(#[source] GuestMemoryError),
|
||||||
/// Writing the IDT to RAM failed.
|
/// Writing the IDT to RAM failed.
|
||||||
#[error("Writing the IDT to RAM failed: {0}")]
|
#[error("Writing the IDT to RAM failed")]
|
||||||
WriteIdt(GuestMemoryError),
|
WriteIdt(#[source] GuestMemoryError),
|
||||||
/// Writing PDPTE to RAM failed.
|
/// Writing PDPTE to RAM failed.
|
||||||
#[error("Writing PDPTE to RAM failed: {0}")]
|
#[error("Writing PDPTE to RAM failed")]
|
||||||
WritePdpteAddress(GuestMemoryError),
|
WritePdpteAddress(#[source] GuestMemoryError),
|
||||||
/// Writing PDE to RAM failed.
|
/// Writing PDE to RAM failed.
|
||||||
#[error("Writing PDE to RAM failed: {0}")]
|
#[error("Writing PDE to RAM failed")]
|
||||||
WritePdeAddress(GuestMemoryError),
|
WritePdeAddress(#[source] GuestMemoryError),
|
||||||
/// Writing PML4 to RAM failed.
|
/// Writing PML4 to RAM failed.
|
||||||
#[error("Writing PML4 to RAM failed: {0}")]
|
#[error("Writing PML4 to RAM failed")]
|
||||||
WritePml4Address(GuestMemoryError),
|
WritePml4Address(#[source] GuestMemoryError),
|
||||||
/// Writing PML5 to RAM failed.
|
/// Writing PML5 to RAM failed.
|
||||||
#[error("Writing PML5 to RAM failed: {0}")]
|
#[error("Writing PML5 to RAM failed")]
|
||||||
WritePml5Address(GuestMemoryError),
|
WritePml5Address(#[source] GuestMemoryError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
@@ -119,9 +119,13 @@ pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, entry_point: EntryPoint) ->
|
|||||||
///
|
///
|
||||||
/// * `mem` - The memory that will be passed to the guest.
|
/// * `mem` - The memory that will be passed to the guest.
|
||||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
pub fn setup_sregs(
|
||||||
|
mem: &GuestMemoryMmap,
|
||||||
|
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||||
|
enable_x2_apic_mode: bool,
|
||||||
|
) -> Result<()> {
|
||||||
let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
||||||
configure_segments_and_sregs(mem, &mut sregs)?;
|
configure_segments_and_sregs(mem, &mut sregs, enable_x2_apic_mode)?;
|
||||||
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +152,7 @@ fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
|||||||
pub fn configure_segments_and_sregs(
|
pub fn configure_segments_and_sregs(
|
||||||
mem: &GuestMemoryMmap,
|
mem: &GuestMemoryMmap,
|
||||||
sregs: &mut SpecialRegisters,
|
sregs: &mut SpecialRegisters,
|
||||||
|
enable_x2_apic_mode: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let gdt_table: [u64; BOOT_GDT_MAX] = {
|
let gdt_table: [u64; BOOT_GDT_MAX] = {
|
||||||
// Configure GDT entries as specified by PVH boot protocol
|
// Configure GDT entries as specified by PVH boot protocol
|
||||||
@@ -183,6 +188,11 @@ pub fn configure_segments_and_sregs(
|
|||||||
sregs.cr0 = CR0_PE;
|
sregs.cr0 = CR0_PE;
|
||||||
sregs.cr4 = 0;
|
sregs.cr4 = 0;
|
||||||
|
|
||||||
|
if enable_x2_apic_mode {
|
||||||
|
const X2APIC_ENABLE_BIT: u64 = 1 << 10;
|
||||||
|
sregs.apic_base |= X2APIC_ENABLE_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +214,7 @@ mod tests {
|
|||||||
fn segments_and_sregs() {
|
fn segments_and_sregs() {
|
||||||
let mut sregs: SpecialRegisters = Default::default();
|
let mut sregs: SpecialRegisters = Default::default();
|
||||||
let gm = create_guest_mem();
|
let gm = create_guest_mem();
|
||||||
configure_segments_and_sregs(&gm, &mut sregs).unwrap();
|
configure_segments_and_sregs(&gm, &mut sregs, false).unwrap();
|
||||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
0xcf9b000000ffff,
|
0xcf9b000000ffff,
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ use thiserror::Error;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress};
|
use vm_memory::{Address, ByteValued, Bytes, GuestAddress};
|
||||||
|
|
||||||
use crate::layout::SMBIOS_START;
|
|
||||||
use crate::GuestMemoryMmap;
|
use crate::GuestMemoryMmap;
|
||||||
|
use crate::layout::SMBIOS_START;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@@ -33,8 +33,8 @@ pub enum Error {
|
|||||||
#[error("Failure to write additional data to memory")]
|
#[error("Failure to write additional data to memory")]
|
||||||
WriteData,
|
WriteData,
|
||||||
/// Failure to parse uuid, uuid format may be error
|
/// Failure to parse uuid, uuid format may be error
|
||||||
#[error("Failure to parse uuid: {0}")]
|
#[error("Failure to parse uuid")]
|
||||||
ParseUuid(uuid::Error),
|
ParseUuid(#[source] uuid::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ use crate::GuestMemoryMmap;
|
|||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum TdvfError {
|
pub enum TdvfError {
|
||||||
#[error("Failed read TDVF descriptor: {0}")]
|
#[error("Failed read TDVF descriptor")]
|
||||||
ReadDescriptor(#[source] std::io::Error),
|
ReadDescriptor(#[source] std::io::Error),
|
||||||
#[error("Failed read TDVF descriptor offset: {0}")]
|
#[error("Failed read TDVF descriptor offset")]
|
||||||
ReadDescriptorOffset(#[source] std::io::Error),
|
ReadDescriptorOffset(#[source] std::io::Error),
|
||||||
#[error("Failed read GUID table: {0}")]
|
#[error("Failed read GUID table")]
|
||||||
ReadGuidTable(#[source] std::io::Error),
|
ReadGuidTable(#[source] std::io::Error),
|
||||||
#[error("Invalid descriptor signature")]
|
#[error("Invalid descriptor signature")]
|
||||||
InvalidDescriptorSignature,
|
InvalidDescriptorSignature,
|
||||||
@@ -25,9 +25,9 @@ pub enum TdvfError {
|
|||||||
InvalidDescriptorSize,
|
InvalidDescriptorSize,
|
||||||
#[error("Invalid descriptor version")]
|
#[error("Invalid descriptor version")]
|
||||||
InvalidDescriptorVersion,
|
InvalidDescriptorVersion,
|
||||||
#[error("Failed to write HOB details to guest memory: {0}")]
|
#[error("Failed to write HOB details to guest memory")]
|
||||||
GuestMemoryWriteHob(#[source] GuestMemoryError),
|
GuestMemoryWriteHob(#[source] GuestMemoryError),
|
||||||
#[error("Failed to create Uuid: {0}")]
|
#[error("Failed to create Uuid")]
|
||||||
UuidCreation(#[source] uuid::Error),
|
UuidCreation(#[source] uuid::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ const TABLE_FOOTER_GUID: &str = "96b582de-1fb2-45f7-baea-a366c55a082d";
|
|||||||
const TDVF_METADATA_OFFSET_GUID: &str = "e47a6535-984a-4798-865e-4685a7bf8ec2";
|
const TDVF_METADATA_OFFSET_GUID: &str = "e47a6535-984a-4798-865e-4685a7bf8ec2";
|
||||||
|
|
||||||
// TDVF_DESCRIPTOR
|
// TDVF_DESCRIPTOR
|
||||||
#[repr(packed)]
|
#[repr(C, packed)]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct TdvfDescriptor {
|
pub struct TdvfDescriptor {
|
||||||
signature: [u8; 4],
|
signature: [u8; 4],
|
||||||
@@ -45,7 +45,7 @@ pub struct TdvfDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TDVF_SECTION
|
// TDVF_SECTION
|
||||||
#[repr(packed)]
|
#[repr(C, packed)]
|
||||||
#[derive(Clone, Copy, Default, Debug)]
|
#[derive(Clone, Copy, Default, Debug)]
|
||||||
pub struct TdvfSection {
|
pub struct TdvfSection {
|
||||||
pub data_offset: u32,
|
pub data_offset: u32,
|
||||||
@@ -209,7 +209,7 @@ enum HobType {
|
|||||||
EndOfHobList = 0xffff,
|
EndOfHobList = 0xffff,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C, packed)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobHeader {
|
struct HobHeader {
|
||||||
r#type: HobType,
|
r#type: HobType,
|
||||||
@@ -217,7 +217,7 @@ struct HobHeader {
|
|||||||
reserved: u32,
|
reserved: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C, packed)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobHandoffInfoTable {
|
struct HobHandoffInfoTable {
|
||||||
header: HobHeader,
|
header: HobHeader,
|
||||||
@@ -230,7 +230,7 @@ struct HobHandoffInfoTable {
|
|||||||
efi_end_of_hob_list: u64,
|
efi_end_of_hob_list: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C, packed)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct EfiGuid {
|
struct EfiGuid {
|
||||||
data1: u32,
|
data1: u32,
|
||||||
@@ -239,7 +239,7 @@ struct EfiGuid {
|
|||||||
data4: [u8; 8],
|
data4: [u8; 8],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C, packed)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobResourceDescriptor {
|
struct HobResourceDescriptor {
|
||||||
header: HobHeader,
|
header: HobHeader,
|
||||||
@@ -250,7 +250,7 @@ struct HobResourceDescriptor {
|
|||||||
resource_length: u64,
|
resource_length: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C, packed)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobGuidType {
|
struct HobGuidType {
|
||||||
header: HobHeader,
|
header: HobHeader,
|
||||||
@@ -266,14 +266,14 @@ pub enum PayloadImageType {
|
|||||||
RawVmLinux,
|
RawVmLinux,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C, packed)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
pub struct PayloadInfo {
|
pub struct PayloadInfo {
|
||||||
pub image_type: PayloadImageType,
|
pub image_type: PayloadImageType,
|
||||||
pub entry_point: u64,
|
pub entry_point: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C, packed)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct TdPayload {
|
struct TdPayload {
|
||||||
guid_type: HobGuidType,
|
guid_type: HobGuidType,
|
||||||
@@ -299,7 +299,7 @@ pub struct TdHob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn align_hob(v: u64) -> u64 {
|
fn align_hob(v: u64) -> u64 {
|
||||||
(v + 7) / 8 * 8
|
v.div_ceil(8) * 8
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TdHob {
|
impl TdHob {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Chromium OS Authors", "The Cloud Hypervisor Authors"]
|
authors = ["The Chromium OS Authors", "The Cloud Hypervisor Authors"]
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
name = "block"
|
name = "block"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
@@ -9,17 +9,17 @@ default = []
|
|||||||
io_uring = ["dep:io-uring"]
|
io_uring = ["dep:io-uring"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "1.5.0"
|
byteorder = { workspace = true }
|
||||||
crc-any = "2.4.4"
|
crc-any = "2.5.0"
|
||||||
io-uring = { version = "0.6.4", optional = true }
|
io-uring = { version = "0.7.10", optional = true }
|
||||||
libc = "0.2.158"
|
libc = { workspace = true }
|
||||||
log = "0.4.22"
|
log = { workspace = true }
|
||||||
remain = "0.2.14"
|
remain = "0.2.15"
|
||||||
serde = { version = "1.0.208", features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
smallvec = "1.13.2"
|
smallvec = "1.15.1"
|
||||||
thiserror = "1.0.62"
|
thiserror = { workspace = true }
|
||||||
uuid = { version = "1.8.0", features = ["v4"] }
|
uuid = { workspace = true, features = ["v4"] }
|
||||||
virtio-bindings = { workspace = true, features = ["virtio-v5_0_0"] }
|
virtio-bindings = { workspace = true }
|
||||||
virtio-queue = { workspace = true }
|
virtio-queue = { workspace = true }
|
||||||
vm-memory = { workspace = true, features = [
|
vm-memory = { workspace = true, features = [
|
||||||
"backend-atomic",
|
"backend-atomic",
|
||||||
|
|||||||
@@ -2,42 +2,86 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::DiskTopology;
|
use crate::{BatchRequest, DiskTopology};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum DiskFileError {
|
pub enum DiskFileError {
|
||||||
/// Failed getting disk file size.
|
/// Failed getting disk file size.
|
||||||
#[error("Failed getting disk file size: {0}")]
|
#[error("Failed getting disk file size")]
|
||||||
Size(#[source] std::io::Error),
|
Size(#[source] std::io::Error),
|
||||||
/// Failed creating a new AsyncIo.
|
/// Failed creating a new AsyncIo.
|
||||||
#[error("Failed creating a new AsyncIo: {0}")]
|
#[error("Failed creating a new AsyncIo")]
|
||||||
NewAsyncIo(#[source] std::io::Error),
|
NewAsyncIo(#[source] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
|
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
|
||||||
|
|
||||||
|
/// A wrapper for [`RawFd`] capturing the lifetime of a corresponding [`DiskFile`].
|
||||||
|
///
|
||||||
|
/// This fulfills the same role as [`BorrowedFd`] but is tailored to the limitations
|
||||||
|
/// by some implementations of [`DiskFile`], which wrap the effective [`File`]
|
||||||
|
/// in an `Arc<Mutex<T>>`, making the use of [`BorrowedFd`] impossible.
|
||||||
|
///
|
||||||
|
/// [`BorrowedFd`]: std::os::fd::BorrowedFd
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct BorrowedDiskFd<'fd> {
|
||||||
|
raw_fd: RawFd,
|
||||||
|
_lifetime: PhantomData<&'fd OwnedFd>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BorrowedDiskFd<'_> {
|
||||||
|
pub(super) fn new(raw_fd: RawFd) -> Self {
|
||||||
|
Self {
|
||||||
|
raw_fd,
|
||||||
|
_lifetime: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for BorrowedDiskFd<'_> {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.raw_fd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Abstraction over the effective [`File`] backing up a block device,
|
||||||
|
/// with support for synchronous and asynchronous I/O.
|
||||||
|
///
|
||||||
|
/// This allows abstracting over raw image formats as well as structured
|
||||||
|
/// image formats.
|
||||||
pub trait DiskFile: Send {
|
pub trait DiskFile: Send {
|
||||||
fn size(&mut self) -> DiskFileResult<u64>;
|
fn size(&mut self) -> DiskFileResult<u64>;
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
|
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
|
||||||
fn topology(&mut self) -> DiskTopology {
|
fn topology(&mut self) -> DiskTopology {
|
||||||
DiskTopology::default()
|
DiskTopology::default()
|
||||||
}
|
}
|
||||||
|
/// Returns the file descriptor of the underlying disk image file.
|
||||||
|
///
|
||||||
|
/// The file descriptor is supposed to be used for `fcntl()` calls but no
|
||||||
|
/// other operation.
|
||||||
|
fn fd(&mut self) -> BorrowedDiskFd<'_>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum AsyncIoError {
|
pub enum AsyncIoError {
|
||||||
/// Failed vectored reading from file.
|
/// Failed vectored reading from file.
|
||||||
#[error("Failed vectored reading from file: {0}")]
|
#[error("Failed vectored reading from file")]
|
||||||
ReadVectored(#[source] std::io::Error),
|
ReadVectored(#[source] std::io::Error),
|
||||||
/// Failed vectored writing to file.
|
/// Failed vectored writing to file.
|
||||||
#[error("Failed vectored writing to file: {0}")]
|
#[error("Failed vectored writing to file")]
|
||||||
WriteVectored(#[source] std::io::Error),
|
WriteVectored(#[source] std::io::Error),
|
||||||
/// Failed synchronizing file.
|
/// Failed synchronizing file.
|
||||||
#[error("Failed synchronizing file: {0}")]
|
#[error("Failed synchronizing file")]
|
||||||
Fsync(#[source] std::io::Error),
|
Fsync(#[source] std::io::Error),
|
||||||
|
/// Failed submitting batch requests.
|
||||||
|
#[error("Failed submitting batch requests: {0}")]
|
||||||
|
SubmitBatchRequests(#[source] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
|
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
|
||||||
@@ -58,4 +102,10 @@ pub trait AsyncIo: Send {
|
|||||||
) -> AsyncIoResult<()>;
|
) -> AsyncIoResult<()>;
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
|
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
|
||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
|
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
|
||||||
|
fn batch_requests_enabled(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn submit_batch_requests(&mut self, _batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
173
block/src/fcntl.rs
Normal file
173
block/src/fcntl.rs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
// Copyright © 2025 Cyberus Technology GmbH
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
//! Helpers for advisory file locking.
|
||||||
|
//!
|
||||||
|
//! Under the hood, the implementation uses OFD locks for the entire file,
|
||||||
|
//! as described in [[0]]. The advantage over `F_SETLKW` (currently used by
|
||||||
|
//! Rust std: `File::try_lock()`) is that only the very last `close()` on a
|
||||||
|
//! file descriptor releases the lock. This prevents mistakes and unexpected
|
||||||
|
//! behavior.
|
||||||
|
//!
|
||||||
|
//! [0]: <https://apenwarr.ca/log/20101213>.
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::io;
|
||||||
|
use std::os::fd::{AsRawFd, RawFd};
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Errors that can happen when working with file locks.
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum LockError {
|
||||||
|
/// The file is already locked.
|
||||||
|
///
|
||||||
|
/// A call to [`get_lock_state`] can help to identify the reason.
|
||||||
|
#[error("The file is already locked")]
|
||||||
|
AlreadyLocked,
|
||||||
|
/// IO error.
|
||||||
|
#[error("The lock state could not be checked or set")]
|
||||||
|
Io(#[source] io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Commands for use with [`fcntl`].
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
enum FcntlArg<'a> {
|
||||||
|
/// Set an OFD lock from the given lock description.
|
||||||
|
F_OFD_SETLK(&'a libc::flock),
|
||||||
|
/// Get the first OFD lock for the given lock description.
|
||||||
|
F_OFD_GETLK(&'a mut libc::flock),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for [`libc::fcntl`] that properly sets the function arguments.
|
||||||
|
fn fcntl(fd: RawFd, arg: FcntlArg) -> libc::c_int {
|
||||||
|
// SAFETY: We use a valid FD.
|
||||||
|
unsafe {
|
||||||
|
match arg {
|
||||||
|
FcntlArg::F_OFD_SETLK(flock) => libc::fcntl(fd, libc::F_OFD_SETLK, flock),
|
||||||
|
FcntlArg::F_OFD_GETLK(flock) => libc::fcntl(fd, libc::F_OFD_GETLK, flock),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes the type of lock you want to set.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum LockType {
|
||||||
|
/// Clear a lock.
|
||||||
|
Unlock,
|
||||||
|
/// Set a write lock (exclusive).
|
||||||
|
Write,
|
||||||
|
/// Set a read lock (shared).
|
||||||
|
Read,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LockType {
|
||||||
|
pub const fn to_libc_val(self) -> libc::c_int {
|
||||||
|
match self {
|
||||||
|
Self::Unlock => libc::F_UNLCK as libc::c_int,
|
||||||
|
Self::Write => libc::F_WRLCK as libc::c_int,
|
||||||
|
Self::Read => libc::F_RDLCK as libc::c_int,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes the current state of a lock.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum LockState {
|
||||||
|
/// No lock set.
|
||||||
|
Unlocked,
|
||||||
|
/// Locked for reading (non-exclusive).
|
||||||
|
SharedRead,
|
||||||
|
/// Locked for writing (exclusive mode).
|
||||||
|
ExclusiveWrite,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LockState {
|
||||||
|
fn new(value: libc::c_int) -> Self {
|
||||||
|
const F_UNLCK: libc::c_int = libc::F_UNLCK as libc::c_int;
|
||||||
|
const F_WRLCK: libc::c_int = libc::F_WRLCK as libc::c_int;
|
||||||
|
const F_RDLCK: libc::c_int = libc::F_RDLCK as libc::c_int;
|
||||||
|
match value {
|
||||||
|
F_UNLCK => Self::Unlocked,
|
||||||
|
F_WRLCK => Self::ExclusiveWrite,
|
||||||
|
F_RDLCK => Self::SharedRead,
|
||||||
|
// This is so unlikely that we want to avoid the complexity of
|
||||||
|
// coping with this error case. Can only fail if either Linux
|
||||||
|
// is broken or memory is messed up.
|
||||||
|
other => panic!("Unexpected lock state: {other}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a [`struct@libc::flock`] structure for the whole file.
|
||||||
|
const fn get_flock(lock_type: LockType) -> libc::flock {
|
||||||
|
libc::flock {
|
||||||
|
l_type: lock_type.to_libc_val() as libc::c_short,
|
||||||
|
l_whence: libc::SEEK_SET as libc::c_short,
|
||||||
|
l_start: 0,
|
||||||
|
l_len: 0, /* EOF */
|
||||||
|
l_pid: 0, /* filled by callee */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to acquire a lock using [`fcntl`] with respect to the given
|
||||||
|
/// parameters.
|
||||||
|
///
|
||||||
|
/// Please note that `fcntl()` OFD locks are **advisory locks**, which do not
|
||||||
|
/// prevent to `open()` a file if a lock is already placed.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `file`: The file to acquire a lock for [`LockType`]. The file's state will
|
||||||
|
/// be logically mutated, but not technically.
|
||||||
|
/// - `lock_type`: The [`LockType`]
|
||||||
|
pub fn try_acquire_lock<Fd: AsRawFd>(file: Fd, lock_type: LockType) -> Result<(), LockError> {
|
||||||
|
let flock = get_flock(lock_type);
|
||||||
|
|
||||||
|
let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_SETLK(&flock));
|
||||||
|
match res {
|
||||||
|
0 => Ok(()),
|
||||||
|
-1 => {
|
||||||
|
let io_error = io::Error::last_os_error();
|
||||||
|
let errno = io_error.raw_os_error().unwrap();
|
||||||
|
match errno {
|
||||||
|
// See man page for error code:
|
||||||
|
// <https://man7.org/linux/man-pages/man2/fcntl.2.html>
|
||||||
|
libc::EAGAIN | libc::EACCES => Err(LockError::AlreadyLocked),
|
||||||
|
_ => Err(LockError::Io(io_error)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val => panic!("Unexpected return value from fcntl(): {val}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears a lock.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `file`: The file to clear all locks for [`LockType`].
|
||||||
|
pub fn clear_lock<Fd: AsRawFd>(file: Fd) -> Result<(), LockError> {
|
||||||
|
try_acquire_lock(file, LockType::Unlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current lock state using [`fcntl`] with respect to the given
|
||||||
|
/// parameters.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `file`: The file for which to get the lock state.
|
||||||
|
pub fn get_lock_state<Fd: AsRawFd>(file: Fd) -> Result<LockState, LockError> {
|
||||||
|
let mut flock = get_flock(LockType::Write);
|
||||||
|
let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_GETLK(&mut flock));
|
||||||
|
match res {
|
||||||
|
0 => {
|
||||||
|
let state = flock.l_type as libc::c_int;
|
||||||
|
let state = LockState::new(state);
|
||||||
|
Ok(state)
|
||||||
|
}
|
||||||
|
-1 => {
|
||||||
|
let io_error = io::Error::last_os_error();
|
||||||
|
Err(LockError::Io(io_error))
|
||||||
|
}
|
||||||
|
val => panic!("Unexpected return value from fcntl(): {val}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,8 +6,8 @@ use std::fs::File;
|
|||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
use crate::vhd::VhdFooter;
|
|
||||||
use crate::BlockBackend;
|
use crate::BlockBackend;
|
||||||
|
use crate::vhd::VhdFooter;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FixedVhd {
|
pub struct FixedVhd {
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
|||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||||
};
|
};
|
||||||
use crate::fixed_vhd::FixedVhd;
|
use crate::fixed_vhd::FixedVhd;
|
||||||
use crate::raw_async::RawFileAsync;
|
use crate::raw_async::RawFileAsync;
|
||||||
use crate::BlockBackend;
|
use crate::{BatchRequest, BlockBackend};
|
||||||
|
|
||||||
pub struct FixedVhdDiskAsync(FixedVhd);
|
pub struct FixedVhdDiskAsync(FixedVhd);
|
||||||
|
|
||||||
@@ -33,6 +33,10 @@ impl DiskFile for FixedVhdDiskAsync {
|
|||||||
.map_err(DiskFileError::NewAsyncIo)?,
|
.map_err(DiskFileError::NewAsyncIo)?,
|
||||||
) as Box<dyn AsyncIo>)
|
) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||||
|
BorrowedDiskFd::new(self.0.as_raw_fd())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FixedVhdAsync {
|
pub struct FixedVhdAsync {
|
||||||
@@ -102,4 +106,12 @@ impl AsyncIo for FixedVhdAsync {
|
|||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||||
self.raw_file_async.next_completed_request()
|
self.raw_file_async.next_completed_request()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn batch_requests_enabled(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn submit_batch_requests(&mut self, batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
|
||||||
|
self.raw_file_async.submit_batch_requests(batch_request)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
|||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
use crate::BlockBackend;
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||||
};
|
};
|
||||||
use crate::fixed_vhd::FixedVhd;
|
use crate::fixed_vhd::FixedVhd;
|
||||||
use crate::raw_sync::RawFileSync;
|
use crate::raw_sync::RawFileSync;
|
||||||
use crate::BlockBackend;
|
|
||||||
|
|
||||||
pub struct FixedVhdDiskSync(FixedVhd);
|
pub struct FixedVhdDiskSync(FixedVhd);
|
||||||
|
|
||||||
@@ -33,6 +33,10 @@ impl DiskFile for FixedVhdDiskSync {
|
|||||||
.map_err(DiskFileError::NewAsyncIo)?,
|
.map_err(DiskFileError::NewAsyncIo)?,
|
||||||
) as Box<dyn AsyncIo>)
|
) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||||
|
BorrowedDiskFd::new(self.0.as_raw_fd())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FixedVhdSync {
|
pub struct FixedVhdSync {
|
||||||
|
|||||||
263
block/src/lib.rs
263
block/src/lib.rs
@@ -12,6 +12,7 @@
|
|||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
pub mod async_io;
|
pub mod async_io;
|
||||||
|
pub mod fcntl;
|
||||||
pub mod fixed_vhd;
|
pub mod fixed_vhd;
|
||||||
#[cfg(feature = "io_uring")]
|
#[cfg(feature = "io_uring")]
|
||||||
/// Enabled with the `"io_uring"` feature
|
/// Enabled with the `"io_uring"` feature
|
||||||
@@ -30,7 +31,7 @@ pub mod vhd;
|
|||||||
pub mod vhdx;
|
pub mod vhdx;
|
||||||
pub mod vhdx_sync;
|
pub mod vhdx_sync;
|
||||||
|
|
||||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
use std::alloc::{Layout, alloc_zeroed, dealloc};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@@ -38,13 +39,13 @@ 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::os::unix::io::AsRawFd;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::{Arc, MutexGuard};
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{cmp, result};
|
use std::{cmp, result};
|
||||||
|
|
||||||
#[cfg(feature = "io_uring")]
|
#[cfg(feature = "io_uring")]
|
||||||
use io_uring::{opcode, IoUring, Probe};
|
use io_uring::{IoUring, Probe, opcode};
|
||||||
use libc::{ioctl, S_IFBLK, S_IFMT};
|
use libc::{S_IFBLK, S_IFMT, ioctl};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -56,12 +57,10 @@ 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::{aio, ioctl_io_nr, ioctl_ioc_nr};
|
use vmm_sys_util::{aio, ioctl_io_nr};
|
||||||
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
|
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
|
||||||
use crate::fixed_vhd::FixedVhd;
|
use crate::vhdx::VhdxError;
|
||||||
use crate::qcow::{QcowFile, RawFile};
|
|
||||||
use crate::vhdx::{Vhdx, VhdxError};
|
|
||||||
|
|
||||||
const SECTOR_SHIFT: u8 = 9;
|
const SECTOR_SHIFT: u8 = 9;
|
||||||
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
||||||
@@ -69,9 +68,9 @@ pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
|||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Guest gave us bad memory addresses")]
|
#[error("Guest gave us bad memory addresses")]
|
||||||
GuestMemory(GuestMemoryError),
|
GuestMemory(#[source] GuestMemoryError),
|
||||||
#[error("Guest gave us offsets that would have overflowed a usize")]
|
#[error("Guest gave us offsets that would have overflowed a usize")]
|
||||||
CheckedOffset(GuestAddress, usize),
|
CheckedOffset(GuestAddress, usize /* sector offset */),
|
||||||
#[error("Guest gave us a write only descriptor that protocol says to read from")]
|
#[error("Guest gave us a write only descriptor that protocol says to read from")]
|
||||||
UnexpectedWriteOnlyDescriptor,
|
UnexpectedWriteOnlyDescriptor,
|
||||||
#[error("Guest gave us a read only descriptor that protocol says to write to")]
|
#[error("Guest gave us a read only descriptor that protocol says to write to")]
|
||||||
@@ -80,22 +79,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}")]
|
#[error("Failed to detect image type")]
|
||||||
DetectImageType(std::io::Error),
|
DetectImageType(#[source] std::io::Error),
|
||||||
#[error("Failure in fixed vhd: {0}")]
|
#[error("Failure in fixed vhd")]
|
||||||
FixedVhdError(std::io::Error),
|
FixedVhdError(#[source] 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}")]
|
#[error("Failure in qcow")]
|
||||||
QcowError(qcow::Error),
|
QcowError(#[source] qcow::Error),
|
||||||
#[error("Failure in raw file: {0}")]
|
#[error("Failure in raw file")]
|
||||||
RawFileError(std::io::Error),
|
RawFileError(#[source] 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}")]
|
#[error("Failure in vhdx")]
|
||||||
VhdxError(VhdxError),
|
VhdxError(#[source] VhdxError),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
||||||
@@ -132,34 +131,36 @@ pub fn build_serial(disk_path: &Path) -> Vec<u8> {
|
|||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ExecuteError {
|
pub enum ExecuteError {
|
||||||
#[error("Bad request: {0}")]
|
#[error("Bad request")]
|
||||||
BadRequest(Error),
|
BadRequest(#[source] Error),
|
||||||
#[error("Failed to flush: {0}")]
|
#[error("Failed to flush")]
|
||||||
Flush(io::Error),
|
Flush(#[source] io::Error),
|
||||||
#[error("Failed to read: {0}")]
|
#[error("Failed to read")]
|
||||||
Read(GuestMemoryError),
|
Read(#[source] GuestMemoryError),
|
||||||
#[error("Failed to read_exact: {0}")]
|
#[error("Failed to read_exact")]
|
||||||
ReadExact(io::Error),
|
ReadExact(#[source] io::Error),
|
||||||
#[error("Failed to seek: {0}")]
|
#[error("Can't execute an operation other than `read` on a read-only device")]
|
||||||
Seek(io::Error),
|
ReadOnly,
|
||||||
#[error("Failed to write: {0}")]
|
#[error("Failed to seek")]
|
||||||
Write(GuestMemoryError),
|
Seek(#[source] io::Error),
|
||||||
#[error("Failed to write_all: {0}")]
|
#[error("Failed to write")]
|
||||||
WriteAll(io::Error),
|
Write(#[source] GuestMemoryError),
|
||||||
|
#[error("Failed to write_all")]
|
||||||
|
WriteAll(#[source] io::Error),
|
||||||
#[error("Unsupported request: {0}")]
|
#[error("Unsupported request: {0}")]
|
||||||
Unsupported(u32),
|
Unsupported(u32),
|
||||||
#[error("Failed to submit io uring: {0}")]
|
#[error("Failed to submit io uring")]
|
||||||
SubmitIoUring(io::Error),
|
SubmitIoUring(#[source] io::Error),
|
||||||
#[error("Failed to get guest address: {0}")]
|
#[error("Failed to get guest address")]
|
||||||
GetHostAddress(GuestMemoryError),
|
GetHostAddress(#[source] GuestMemoryError),
|
||||||
#[error("Failed to async read: {0}")]
|
#[error("Failed to async read")]
|
||||||
AsyncRead(AsyncIoError),
|
AsyncRead(#[source] AsyncIoError),
|
||||||
#[error("Failed to async write: {0}")]
|
#[error("Failed to async write")]
|
||||||
AsyncWrite(AsyncIoError),
|
AsyncWrite(#[source] AsyncIoError),
|
||||||
#[error("failed to async flush: {0}")]
|
#[error("failed to async flush")]
|
||||||
AsyncFlush(AsyncIoError),
|
AsyncFlush(#[source] AsyncIoError),
|
||||||
#[error("Failed allocating a temporary buffer: {0}")]
|
#[error("Failed allocating a temporary buffer")]
|
||||||
TemporaryBufferAllocation(io::Error),
|
TemporaryBufferAllocation(#[source] io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecuteError {
|
impl ExecuteError {
|
||||||
@@ -169,6 +170,7 @@ impl ExecuteError {
|
|||||||
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::ReadExact(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::ReadExact(_) => VIRTIO_BLK_S_IOERR,
|
||||||
|
ExecuteError::ReadOnly => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::WriteAll(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::WriteAll(_) => VIRTIO_BLK_S_IOERR,
|
||||||
@@ -220,6 +222,8 @@ fn sector<B: Bitmap + 'static>(
|
|||||||
mem.read_obj(addr).map_err(Error::GuestMemory)
|
mem.read_obj(addr).map_err(Error::GuestMemory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_DESCRIPTOR_VEC_SIZE: usize = 32;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AlignedOperation {
|
pub struct AlignedOperation {
|
||||||
origin_ptr: u64,
|
origin_ptr: u64,
|
||||||
@@ -228,14 +232,28 @@ pub struct AlignedOperation {
|
|||||||
layout: Layout,
|
layout: Layout,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct BatchRequest {
|
||||||
|
pub offset: libc::off_t,
|
||||||
|
pub iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]>,
|
||||||
|
pub user_data: u64,
|
||||||
|
pub request_type: RequestType,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ExecuteAsync {
|
||||||
|
// `true` if the execution will complete asynchronously
|
||||||
|
pub async_complete: bool,
|
||||||
|
// request need to be batched for submission if any
|
||||||
|
pub batch_request: Option<BatchRequest>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Request {
|
pub struct Request {
|
||||||
pub request_type: RequestType,
|
pub request_type: RequestType,
|
||||||
pub sector: u64,
|
pub sector: u64,
|
||||||
pub data_descriptors: SmallVec<[(GuestAddress, u32); 1]>,
|
pub data_descriptors: SmallVec<[(GuestAddress, u32); DEFAULT_DESCRIPTOR_VEC_SIZE]>,
|
||||||
pub status_addr: GuestAddress,
|
pub status_addr: GuestAddress,
|
||||||
pub writeback: bool,
|
pub writeback: bool,
|
||||||
pub aligned_operations: SmallVec<[AlignedOperation; 1]>,
|
pub aligned_operations: SmallVec<[AlignedOperation; DEFAULT_DESCRIPTOR_VEC_SIZE]>,
|
||||||
pub start: Instant,
|
pub start: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,10 +281,10 @@ 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: SmallVec::with_capacity(1),
|
data_descriptors: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE),
|
||||||
status_addr: GuestAddress(0),
|
status_addr: GuestAddress(0),
|
||||||
writeback: true,
|
writeback: true,
|
||||||
aligned_operations: SmallVec::with_capacity(1),
|
aligned_operations: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE),
|
||||||
start: Instant::now(),
|
start: Instant::now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -393,21 +411,27 @@ impl Request {
|
|||||||
disk_image: &mut dyn AsyncIo,
|
disk_image: &mut dyn AsyncIo,
|
||||||
serial: &[u8],
|
serial: &[u8],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> result::Result<bool, ExecuteError> {
|
) -> result::Result<ExecuteAsync, 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: SmallVec<[libc::iovec; 1]> =
|
let mut iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
||||||
SmallVec::with_capacity(self.data_descriptors.len());
|
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 {
|
let _: u32 = data_len; // compiler-checked documentation
|
||||||
|
const _: () = assert!(
|
||||||
|
core::mem::size_of::<u32>() <= core::mem::size_of::<usize>(),
|
||||||
|
"unsupported platform"
|
||||||
|
);
|
||||||
|
if data_len == 0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut top: u64 = u64::from(*data_len) / SECTOR_SIZE;
|
let mut top: u64 = u64::from(data_len) / SECTOR_SIZE;
|
||||||
if u64::from(*data_len) % SECTOR_SIZE != 0 {
|
if u64::from(data_len) % SECTOR_SIZE != 0 {
|
||||||
top += 1;
|
top += 1;
|
||||||
}
|
}
|
||||||
|
let data_len = data_len as usize;
|
||||||
top = top
|
top = top
|
||||||
.checked_add(sector)
|
.checked_add(sector)
|
||||||
.ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?;
|
.ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?;
|
||||||
@@ -416,7 +440,7 @@ impl Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let origin_ptr = mem
|
let origin_ptr = mem
|
||||||
.get_slice(*data_addr, *data_len as usize)
|
.get_slice(data_addr, data_len)
|
||||||
.map_err(ExecuteError::GetHostAddress)?
|
.map_err(ExecuteError::GetHostAddress)?
|
||||||
.ptr_guard();
|
.ptr_guard();
|
||||||
|
|
||||||
@@ -424,9 +448,8 @@ impl Request {
|
|||||||
// 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_ptr() as u64) % SECTOR_SIZE != 0 {
|
let iov_base = if !(origin_ptr.as_ptr() as u64).is_multiple_of(SECTOR_SIZE) {
|
||||||
let layout =
|
let layout = Layout::from_size_align(data_len, SECTOR_SIZE as usize).unwrap();
|
||||||
Layout::from_size_align(*data_len as usize, SECTOR_SIZE as usize).unwrap();
|
|
||||||
// SAFETY: 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() {
|
||||||
@@ -440,7 +463,7 @@ impl Request {
|
|||||||
if request_type == RequestType::Out {
|
if request_type == RequestType::Out {
|
||||||
// SAFETY: destination buffer has been allocated with
|
// SAFETY: destination buffer has been allocated with
|
||||||
// the proper size.
|
// the proper size.
|
||||||
unsafe { std::ptr::copy(origin_ptr.as_ptr(), aligned_ptr, *data_len as usize) };
|
unsafe { std::ptr::copy(origin_ptr.as_ptr(), aligned_ptr, data_len) };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store both origin and aligned pointers for complete_async()
|
// Store both origin and aligned pointers for complete_async()
|
||||||
@@ -448,7 +471,7 @@ impl Request {
|
|||||||
self.aligned_operations.push(AlignedOperation {
|
self.aligned_operations.push(AlignedOperation {
|
||||||
origin_ptr: origin_ptr.as_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,
|
||||||
layout,
|
layout,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -459,11 +482,15 @@ impl Request {
|
|||||||
|
|
||||||
let iovec = libc::iovec {
|
let iovec = libc::iovec {
|
||||||
iov_base,
|
iov_base,
|
||||||
iov_len: *data_len as libc::size_t,
|
iov_len: data_len as libc::size_t,
|
||||||
};
|
};
|
||||||
iovecs.push(iovec);
|
iovecs.push(iovec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut ret = ExecuteAsync {
|
||||||
|
async_complete: true,
|
||||||
|
batch_request: None,
|
||||||
|
};
|
||||||
// Queue operations expected to be submitted.
|
// Queue operations expected to be submitted.
|
||||||
match request_type {
|
match request_type {
|
||||||
RequestType::In => {
|
RequestType::In => {
|
||||||
@@ -473,14 +500,32 @@ impl Request {
|
|||||||
.bitmap()
|
.bitmap()
|
||||||
.mark_dirty(0, *data_len as usize);
|
.mark_dirty(0, *data_len as usize);
|
||||||
}
|
}
|
||||||
disk_image
|
if disk_image.batch_requests_enabled() {
|
||||||
.read_vectored(offset, &iovecs, user_data)
|
ret.batch_request = Some(BatchRequest {
|
||||||
.map_err(ExecuteError::AsyncRead)?;
|
offset,
|
||||||
|
iovecs,
|
||||||
|
user_data,
|
||||||
|
request_type,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
disk_image
|
||||||
|
.read_vectored(offset, &iovecs, user_data)
|
||||||
|
.map_err(ExecuteError::AsyncRead)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RequestType::Out => {
|
RequestType::Out => {
|
||||||
disk_image
|
if disk_image.batch_requests_enabled() {
|
||||||
.write_vectored(offset, &iovecs, user_data)
|
ret.batch_request = Some(BatchRequest {
|
||||||
.map_err(ExecuteError::AsyncWrite)?;
|
offset,
|
||||||
|
iovecs,
|
||||||
|
user_data,
|
||||||
|
request_type,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
disk_image
|
||||||
|
.write_vectored(offset, &iovecs, user_data)
|
||||||
|
.map_err(ExecuteError::AsyncWrite)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RequestType::Flush => {
|
RequestType::Flush => {
|
||||||
disk_image
|
disk_image
|
||||||
@@ -498,12 +543,13 @@ impl Request {
|
|||||||
}
|
}
|
||||||
mem.write_slice(serial, data_addr)
|
mem.write_slice(serial, data_addr)
|
||||||
.map_err(ExecuteError::Write)?;
|
.map_err(ExecuteError::Write)?;
|
||||||
return Ok(false);
|
ret.async_complete = false;
|
||||||
|
return Ok(ret);
|
||||||
}
|
}
|
||||||
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(true)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn complete_async(&mut self) -> result::Result<(), Error> {
|
pub fn complete_async(&mut self) -> result::Result<(), Error> {
|
||||||
@@ -640,10 +686,7 @@ pub fn block_io_uring_is_supported() -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait AsyncAdaptor<F>
|
pub trait AsyncAdaptor {
|
||||||
where
|
|
||||||
F: Read + Write + Seek,
|
|
||||||
{
|
|
||||||
fn read_vectored_sync(
|
fn read_vectored_sync(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
@@ -651,9 +694,13 @@ where
|
|||||||
user_data: u64,
|
user_data: u64,
|
||||||
eventfd: &EventFd,
|
eventfd: &EventFd,
|
||||||
completion_list: &mut VecDeque<(u64, i32)>,
|
completion_list: &mut VecDeque<(u64, i32)>,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()>
|
||||||
|
where
|
||||||
|
Self: Read + Seek,
|
||||||
|
{
|
||||||
// Convert libc::iovec into IoSliceMut
|
// Convert libc::iovec into IoSliceMut
|
||||||
let mut slices: SmallVec<[IoSliceMut; 1]> = SmallVec::with_capacity(iovecs.len());
|
let mut slices: SmallVec<[IoSliceMut; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
||||||
|
SmallVec::with_capacity(iovecs.len());
|
||||||
for iovec in iovecs.iter() {
|
for iovec in iovecs.iter() {
|
||||||
// SAFETY: on Linux IoSliceMut wraps around libc::iovec
|
// SAFETY: on Linux IoSliceMut wraps around libc::iovec
|
||||||
slices.push(IoSliceMut::new(unsafe {
|
slices.push(IoSliceMut::new(unsafe {
|
||||||
@@ -662,15 +709,15 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
let mut file = self.file();
|
|
||||||
|
|
||||||
// Move the cursor to the right offset
|
// Move the cursor to the right offset
|
||||||
file.seek(SeekFrom::Start(offset as u64))
|
self.seek(SeekFrom::Start(offset as u64))
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
.map_err(AsyncIoError::ReadVectored)?;
|
||||||
|
|
||||||
// Read vectored
|
let mut r = 0;
|
||||||
file.read_vectored(slices.as_mut_slice())
|
for b in slices.iter_mut() {
|
||||||
.map_err(AsyncIoError::ReadVectored)?
|
r += self.read(b).map_err(AsyncIoError::ReadVectored)?;
|
||||||
|
}
|
||||||
|
r
|
||||||
};
|
};
|
||||||
|
|
||||||
completion_list.push_back((user_data, result as i32));
|
completion_list.push_back((user_data, result as i32));
|
||||||
@@ -686,9 +733,13 @@ where
|
|||||||
user_data: u64,
|
user_data: u64,
|
||||||
eventfd: &EventFd,
|
eventfd: &EventFd,
|
||||||
completion_list: &mut VecDeque<(u64, i32)>,
|
completion_list: &mut VecDeque<(u64, i32)>,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()>
|
||||||
|
where
|
||||||
|
Self: Write + Seek,
|
||||||
|
{
|
||||||
// Convert libc::iovec into IoSlice
|
// Convert libc::iovec into IoSlice
|
||||||
let mut slices: SmallVec<[IoSlice; 1]> = SmallVec::with_capacity(iovecs.len());
|
let mut slices: SmallVec<[IoSlice; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
||||||
|
SmallVec::with_capacity(iovecs.len());
|
||||||
for iovec in iovecs.iter() {
|
for iovec in iovecs.iter() {
|
||||||
// SAFETY: on Linux IoSlice wraps around libc::iovec
|
// SAFETY: on Linux IoSlice wraps around libc::iovec
|
||||||
slices.push(IoSlice::new(unsafe {
|
slices.push(IoSlice::new(unsafe {
|
||||||
@@ -697,15 +748,15 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
let mut file = self.file();
|
|
||||||
|
|
||||||
// Move the cursor to the right offset
|
// Move the cursor to the right offset
|
||||||
file.seek(SeekFrom::Start(offset as u64))
|
self.seek(SeekFrom::Start(offset as u64))
|
||||||
.map_err(AsyncIoError::WriteVectored)?;
|
.map_err(AsyncIoError::WriteVectored)?;
|
||||||
|
|
||||||
// Write vectored
|
let mut r = 0;
|
||||||
file.write_vectored(slices.as_slice())
|
for b in slices.iter() {
|
||||||
.map_err(AsyncIoError::WriteVectored)?
|
r += self.write(b).map_err(AsyncIoError::WriteVectored)?;
|
||||||
|
}
|
||||||
|
r
|
||||||
};
|
};
|
||||||
|
|
||||||
completion_list.push_back((user_data, result as i32));
|
completion_list.push_back((user_data, result as i32));
|
||||||
@@ -719,12 +770,13 @@ where
|
|||||||
user_data: Option<u64>,
|
user_data: Option<u64>,
|
||||||
eventfd: &EventFd,
|
eventfd: &EventFd,
|
||||||
completion_list: &mut VecDeque<(u64, i32)>,
|
completion_list: &mut VecDeque<(u64, i32)>,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()>
|
||||||
|
where
|
||||||
|
Self: Write,
|
||||||
|
{
|
||||||
let result: i32 = {
|
let result: i32 = {
|
||||||
let mut file = self.file();
|
|
||||||
|
|
||||||
// Flush
|
// Flush
|
||||||
file.flush().map_err(AsyncIoError::Fsync)?;
|
self.flush().map_err(AsyncIoError::Fsync)?;
|
||||||
|
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
@@ -736,8 +788,6 @@ where
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn file(&mut self) -> MutexGuard<F>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ImageType {
|
pub enum ImageType {
|
||||||
@@ -789,25 +839,6 @@ pub trait BlockBackend: Read + Write + Seek + Send + Debug {
|
|||||||
fn size(&self) -> Result<u64, Error>;
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct DiskTopology {
|
pub struct DiskTopology {
|
||||||
pub logical_block_size: u64,
|
pub logical_block_size: u64,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use std::cmp::{max, min};
|
|||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
use std::os::fd::{AsRawFd, RawFd};
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
@@ -23,11 +24,11 @@ use vmm_sys_util::file_traits::{FileSetLen, FileSync};
|
|||||||
use vmm_sys_util::seek_hole::SeekHole;
|
use vmm_sys_util::seek_hole::SeekHole;
|
||||||
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
||||||
|
|
||||||
|
use crate::BlockBackend;
|
||||||
use crate::qcow::qcow_raw_file::QcowRawFile;
|
use crate::qcow::qcow_raw_file::QcowRawFile;
|
||||||
pub use crate::qcow::raw_file::RawFile;
|
pub use crate::qcow::raw_file::RawFile;
|
||||||
use crate::qcow::refcount::RefCount;
|
use crate::qcow::refcount::RefCount;
|
||||||
use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache};
|
use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache};
|
||||||
use crate::BlockBackend;
|
|
||||||
|
|
||||||
/// Nesting depth limit for disk formats that can open other disk files.
|
/// Nesting depth limit for disk formats that can open other disk files.
|
||||||
const MAX_NESTING_DEPTH: u32 = 10;
|
const MAX_NESTING_DEPTH: u32 = 10;
|
||||||
@@ -35,24 +36,24 @@ const MAX_NESTING_DEPTH: u32 = 10;
|
|||||||
#[sorted]
|
#[sorted]
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Backing file io error: {0}")]
|
#[error("Backing file io error")]
|
||||||
BackingFileIo(io::Error),
|
BackingFileIo(#[source] io::Error),
|
||||||
#[error("Backing file open error: {0}")]
|
#[error("Backing file open error")]
|
||||||
BackingFileOpen(Box<Error>),
|
BackingFileOpen(#[source] Box<Error>),
|
||||||
#[error("Backing file name is too long: {0} bytes over")]
|
#[error("Backing file name is too long: {0} bytes over")]
|
||||||
BackingFileTooLong(usize),
|
BackingFileTooLong(usize),
|
||||||
#[error("Compressed blocks not supported")]
|
#[error("Compressed blocks not supported")]
|
||||||
CompressedBlocksNotSupported,
|
CompressedBlocksNotSupported,
|
||||||
#[error("Failed to evict cache: {0}")]
|
#[error("Failed to evict cache")]
|
||||||
EvictingCache(io::Error),
|
EvictingCache(#[source] io::Error),
|
||||||
#[error("File larger than max of {}: {0}", MAX_QCOW_FILE_SIZE)]
|
#[error("File larger than max of {MAX_QCOW_FILE_SIZE}: {0}")]
|
||||||
FileTooBig(u64),
|
FileTooBig(u64),
|
||||||
#[error("Failed to get file size: {0}")]
|
#[error("Failed to get file size")]
|
||||||
GettingFileSize(io::Error),
|
GettingFileSize(#[source] io::Error),
|
||||||
#[error("Failed to get refcount: {0}")]
|
#[error("Failed to get refcount")]
|
||||||
GettingRefcount(refcount::Error),
|
GettingRefcount(#[source] refcount::Error),
|
||||||
#[error("Failed to parse filename: {0}")]
|
#[error("Failed to parse filename")]
|
||||||
InvalidBackingFileName(str::Utf8Error),
|
InvalidBackingFileName(#[source] str::Utf8Error),
|
||||||
#[error("Invalid cluster index")]
|
#[error("Invalid cluster index")]
|
||||||
InvalidClusterIndex,
|
InvalidClusterIndex,
|
||||||
#[error("Invalid cluster size")]
|
#[error("Invalid cluster size")]
|
||||||
@@ -80,29 +81,29 @@ pub enum Error {
|
|||||||
#[error("Not enough space for refcounts")]
|
#[error("Not enough space for refcounts")]
|
||||||
NotEnoughSpaceForRefcounts,
|
NotEnoughSpaceForRefcounts,
|
||||||
#[error("Failed to open file {0}")]
|
#[error("Failed to open file {0}")]
|
||||||
OpeningFile(io::Error),
|
OpeningFile(#[source] io::Error),
|
||||||
#[error("Failed to read data: {0}")]
|
#[error("Failed to read data")]
|
||||||
ReadingData(io::Error),
|
ReadingData(#[source] io::Error),
|
||||||
#[error("Failed to read header: {0}")]
|
#[error("Failed to read header")]
|
||||||
ReadingHeader(io::Error),
|
ReadingHeader(#[source] io::Error),
|
||||||
#[error("Failed to read pointers: {0}")]
|
#[error("Failed to read pointers")]
|
||||||
ReadingPointers(io::Error),
|
ReadingPointers(#[source] io::Error),
|
||||||
#[error("Failed to read ref count block: {0}")]
|
#[error("Failed to read ref count block")]
|
||||||
ReadingRefCountBlock(refcount::Error),
|
ReadingRefCountBlock(#[source] refcount::Error),
|
||||||
#[error("Failed to read ref counts: {0}")]
|
#[error("Failed to read ref counts")]
|
||||||
ReadingRefCounts(io::Error),
|
ReadingRefCounts(#[source] io::Error),
|
||||||
#[error("Failed to rebuild ref counts: {0}")]
|
#[error("Failed to rebuild ref counts")]
|
||||||
RebuildingRefCounts(io::Error),
|
RebuildingRefCounts(#[source] io::Error),
|
||||||
#[error("Refcount table offset past file end")]
|
#[error("Refcount table offset past file end")]
|
||||||
RefcountTableOffEnd,
|
RefcountTableOffEnd,
|
||||||
#[error("Too many clusters specified for refcount")]
|
#[error("Too many clusters specified for refcount")]
|
||||||
RefcountTableTooLarge,
|
RefcountTableTooLarge,
|
||||||
#[error("Failed to seek file: {0}")]
|
#[error("Failed to seek file")]
|
||||||
SeekingFile(io::Error),
|
SeekingFile(#[source] io::Error),
|
||||||
#[error("Failed to set file size: {0}")]
|
#[error("Failed to set file size")]
|
||||||
SettingFileSize(io::Error),
|
SettingFileSize(#[source] io::Error),
|
||||||
#[error("Failed to set refcount refcount: {0}")]
|
#[error("Failed to set refcount refcount")]
|
||||||
SettingRefcountRefcount(io::Error),
|
SettingRefcountRefcount(#[source] io::Error),
|
||||||
#[error("Size too small for number of clusters")]
|
#[error("Size too small for number of clusters")]
|
||||||
SizeTooSmallForNumberOfClusters,
|
SizeTooSmallForNumberOfClusters,
|
||||||
#[error("L1 entry table too large: {0}")]
|
#[error("L1 entry table too large: {0}")]
|
||||||
@@ -113,10 +114,10 @@ pub enum Error {
|
|||||||
UnsupportedRefcountOrder,
|
UnsupportedRefcountOrder,
|
||||||
#[error("Unsupported version: {0}")]
|
#[error("Unsupported version: {0}")]
|
||||||
UnsupportedVersion(u32),
|
UnsupportedVersion(u32),
|
||||||
#[error("Failed to write data: {0}")]
|
#[error("Failed to write data")]
|
||||||
WritingData(io::Error),
|
WritingData(#[source] io::Error),
|
||||||
#[error("Failed to write header: {0}")]
|
#[error("Failed to write header")]
|
||||||
WritingHeader(io::Error),
|
WritingHeader(#[source] io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
@@ -286,11 +287,12 @@ impl QcowHeader {
|
|||||||
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;
|
let max_length: usize = (cluster_size - header_size) as usize;
|
||||||
if let Some(path) = backing_file {
|
if let Some(path) = backing_file
|
||||||
if path.len() > max_length {
|
&& path.len() > max_length
|
||||||
return Err(Error::BackingFileTooLong(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;
|
||||||
@@ -378,7 +380,7 @@ impl QcowHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(backing_file_path) = self.backing_file_path.as_ref() {
|
if let Some(backing_file_path) = self.backing_file_path.as_ref() {
|
||||||
write!(file, "{}", backing_file_path).map_err(Error::WritingHeader)?;
|
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
|
||||||
@@ -424,7 +426,7 @@ fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u
|
|||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct QcowFile {
|
pub struct QcowFile {
|
||||||
raw_file: QcowRawFile,
|
raw_file: QcowRawFile,
|
||||||
header: QcowHeader,
|
header: QcowHeader,
|
||||||
@@ -502,7 +504,7 @@ impl QcowFile {
|
|||||||
if refcount_bits != 16 {
|
if refcount_bits != 16 {
|
||||||
return Err(Error::UnsupportedRefcountOrder);
|
return Err(Error::UnsupportedRefcountOrder);
|
||||||
}
|
}
|
||||||
let refcount_bytes = (refcount_bits + 7) / 8;
|
let refcount_bytes = refcount_bits.div_ceil(8);
|
||||||
|
|
||||||
// Need at least one refcount cluster
|
// Need at least one refcount cluster
|
||||||
if header.refcount_table_clusters == 0 {
|
if header.refcount_table_clusters == 0 {
|
||||||
@@ -588,14 +590,12 @@ impl QcowFile {
|
|||||||
|
|
||||||
// Check for compressed blocks
|
// Check for compressed blocks
|
||||||
for l2_addr_disk in l1_table.get_values() {
|
for l2_addr_disk in l1_table.get_values() {
|
||||||
if *l2_addr_disk != 0 {
|
if *l2_addr_disk != 0
|
||||||
if let Err(e) = Self::read_l2_cluster(&mut raw_file, *l2_addr_disk) {
|
&& let Err(e) = Self::read_l2_cluster(&mut raw_file, *l2_addr_disk)
|
||||||
if let Some(os_error) = e.raw_os_error() {
|
&& let Some(os_error) = e.raw_os_error()
|
||||||
if os_error == ENOTSUP {
|
&& os_error == ENOTSUP
|
||||||
return Err(Error::CompressedBlocksNotSupported);
|
{
|
||||||
}
|
return Err(Error::CompressedBlocksNotSupported);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1516,6 +1516,12 @@ impl QcowFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for QcowFile {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.raw_file.as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Drop for QcowFile {
|
impl Drop for QcowFile {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let _ = self.sync_caches();
|
let _ = self.sync_caches();
|
||||||
@@ -1577,11 +1583,11 @@ impl Seek for QcowFile {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(o) = new_offset {
|
if let Some(o) = new_offset
|
||||||
if o <= self.virtual_size() {
|
&& o <= self.virtual_size()
|
||||||
self.current_offset = o;
|
{
|
||||||
return Ok(o);
|
self.current_offset = o;
|
||||||
}
|
return Ok(o);
|
||||||
}
|
}
|
||||||
Err(std::io::Error::from_raw_os_error(EINVAL))
|
Err(std::io::Error::from_raw_os_error(EINVAL))
|
||||||
}
|
}
|
||||||
@@ -1625,8 +1631,7 @@ impl FileSync for QcowFile {
|
|||||||
|
|
||||||
impl FileSetLen for QcowFile {
|
impl FileSetLen for QcowFile {
|
||||||
fn set_len(&self, _len: u64) -> std::io::Result<()> {
|
fn set_len(&self, _len: u64) -> std::io::Result<()> {
|
||||||
Err(std::io::Error::new(
|
Err(std::io::Error::other(
|
||||||
std::io::ErrorKind::Other,
|
|
||||||
"set_len() not supported for QcowFile",
|
"set_len() not supported for QcowFile",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -1699,12 +1704,12 @@ fn offset_is_cluster_boundary(offset: u64, cluster_bits: u32) -> Result<()> {
|
|||||||
|
|
||||||
// Ceiling of the division of `dividend`/`divisor`.
|
// Ceiling of the division of `dividend`/`divisor`.
|
||||||
fn div_round_up_u64(dividend: u64, divisor: u64) -> u64 {
|
fn div_round_up_u64(dividend: u64, divisor: u64) -> u64 {
|
||||||
dividend / divisor + u64::from(dividend % divisor != 0)
|
dividend / divisor + u64::from(!dividend.is_multiple_of(divisor))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ceiling of the division of `dividend`/`divisor`.
|
// Ceiling of the division of `dividend`/`divisor`.
|
||||||
fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 {
|
fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 {
|
||||||
dividend / divisor + u32::from(dividend % divisor != 0)
|
dividend / divisor + u32::from(!dividend.is_multiple_of(divisor))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_copy<R, W>(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()>
|
fn convert_copy<R, W>(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
use std::io::{self, BufWriter, Seek, SeekFrom, Write};
|
use std::io::{self, BufWriter, Seek, SeekFrom, Write};
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
use std::os::fd::{AsRawFd, RawFd};
|
||||||
|
|
||||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use vmm_sys_util::write_zeroes::WriteZeroes;
|
use vmm_sys_util::write_zeroes::WriteZeroes;
|
||||||
@@ -35,7 +36,7 @@ impl QcowRawFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Reads `count` 64 bit offsets and returns them as a vector.
|
/// Reads `count` 64 bit offsets and returns them as a vector.
|
||||||
/// `mask` optionally ands out some of the bits on the file.
|
/// `mask` optionally `&`s out some of the bits on the file.
|
||||||
pub fn read_pointer_table(
|
pub fn read_pointer_table(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: u64,
|
offset: u64,
|
||||||
@@ -54,7 +55,7 @@ impl QcowRawFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Reads a cluster's worth of 64 bit offsets and returns them as a vector.
|
/// Reads a cluster's worth of 64 bit offsets and returns them as a vector.
|
||||||
/// `mask` optionally ands out some of the bits on the file.
|
/// `mask` optionally `&`s out some of the bits on the file.
|
||||||
pub fn read_pointer_cluster(&mut self, offset: u64, mask: Option<u64>) -> io::Result<Vec<u64>> {
|
pub fn read_pointer_cluster(&mut self, offset: u64, mask: Option<u64>) -> io::Result<Vec<u64>> {
|
||||||
let count = self.cluster_size / size_of::<u64>() as u64;
|
let count = self.cluster_size / size_of::<u64>() as u64;
|
||||||
self.read_pointer_table(offset, count, mask)
|
self.read_pointer_table(offset, count, mask)
|
||||||
@@ -159,3 +160,9 @@ impl Clone for QcowRawFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for QcowRawFile {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.file.as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
use std::alloc::{Layout, alloc_zeroed, dealloc};
|
||||||
use std::fs::{File, Metadata};
|
use std::fs::{File, Metadata};
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
@@ -89,9 +89,9 @@ impl RawFile {
|
|||||||
|
|
||||||
let align64: u64 = self.alignment.try_into().unwrap();
|
let align64: u64 = self.alignment.try_into().unwrap();
|
||||||
|
|
||||||
(self.position % align64 == 0)
|
self.position.is_multiple_of(align64)
|
||||||
&& ((buf.as_ptr() as usize) % self.alignment == 0)
|
&& (buf.as_ptr() as usize).is_multiple_of(self.alignment)
|
||||||
&& (buf.len() % self.alignment == 0)
|
&& buf.len().is_multiple_of(self.alignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_len(&self, size: u64) -> std::io::Result<()> {
|
pub fn set_len(&self, size: u64) -> std::io::Result<()> {
|
||||||
@@ -369,3 +369,9 @@ impl Clone for RawFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for RawFile {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.file.as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache};
|
|||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// `EvictingCache` - Error writing a refblock from the cache to disk.
|
/// `EvictingCache` - Error writing a refblock from the cache to disk.
|
||||||
#[error("Failed to write a refblock from the cache to disk: {0}")]
|
#[error("Failed to write a refblock from the cache to disk")]
|
||||||
EvictingRefCounts(io::Error),
|
EvictingRefCounts(#[source] io::Error),
|
||||||
/// `InvalidIndex` - Address requested isn't within the range of the disk.
|
/// `InvalidIndex` - Address requested isn't within the range of the disk.
|
||||||
#[error("Address requested is not within the range of the disk")]
|
#[error("Address requested is not within the range of the disk")]
|
||||||
InvalidIndex,
|
InvalidIndex,
|
||||||
@@ -27,8 +27,8 @@ pub enum Error {
|
|||||||
#[error("New cluster needs to be allocated for refcounts")]
|
#[error("New cluster needs to be allocated for refcounts")]
|
||||||
NeedNewCluster,
|
NeedNewCluster,
|
||||||
/// `ReadingRefCounts` - Error reading the file into the refcount cache.
|
/// `ReadingRefCounts` - Error reading the file into the refcount cache.
|
||||||
#[error("Failed to read the file into the refcount cache: {0}")]
|
#[error("Failed to read the file into the refcount cache")]
|
||||||
ReadingRefCounts(io::Error),
|
ReadingRefCounts(#[source] io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::collections::hash_map::IterMut;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::collections::hash_map::IterMut;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
use std::slice::SliceIndex;
|
use std::slice::SliceIndex;
|
||||||
@@ -111,7 +111,7 @@ impl<T: Cacheable> CacheMap<T> {
|
|||||||
self.map.get_mut(&index)
|
self.map.get_mut(&index)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter_mut(&mut self) -> IterMut<usize, T> {
|
pub fn iter_mut(&mut self) -> IterMut<'_, usize, T> {
|
||||||
self.map.iter_mut()
|
self.map.iter_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,10 +123,10 @@ impl<T: Cacheable> CacheMap<T> {
|
|||||||
if self.map.len() == self.capacity {
|
if self.map.len() == self.capacity {
|
||||||
// TODO(dgreid) - smarter eviction strategy.
|
// TODO(dgreid) - smarter eviction strategy.
|
||||||
let to_evict = *self.map.iter().next().unwrap().0;
|
let to_evict = *self.map.iter().next().unwrap().0;
|
||||||
if let Some(evicted) = self.map.remove(&to_evict) {
|
if let Some(evicted) = self.map.remove(&to_evict)
|
||||||
if evicted.dirty() {
|
&& evicted.dirty()
|
||||||
write_callback(to_evict, evicted)?;
|
{
|
||||||
}
|
write_callback(to_evict, evicted)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.map.insert(index, block);
|
self.map.insert(index, block);
|
||||||
|
|||||||
@@ -5,46 +5,52 @@
|
|||||||
use std::collections::VecDeque;
|
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::os::fd::AsRawFd;
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
|
||||||
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
|
|
||||||
use crate::AsyncAdaptor;
|
use crate::AsyncAdaptor;
|
||||||
|
use crate::async_io::{
|
||||||
|
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||||
|
};
|
||||||
|
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
|
||||||
|
|
||||||
pub struct QcowDiskSync {
|
pub struct QcowDiskSync {
|
||||||
qcow_file: Arc<Mutex<QcowFile>>,
|
qcow_file: QcowFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QcowDiskSync {
|
impl QcowDiskSync {
|
||||||
pub fn new(file: File, direct_io: bool) -> QcowResult<Self> {
|
pub fn new(file: File, direct_io: bool) -> QcowResult<Self> {
|
||||||
Ok(QcowDiskSync {
|
Ok(QcowDiskSync {
|
||||||
qcow_file: Arc::new(Mutex::new(QcowFile::from(RawFile::new(file, direct_io))?)),
|
qcow_file: QcowFile::from(RawFile::new(file, direct_io))?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for QcowDiskSync {
|
impl DiskFile for QcowDiskSync {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn size(&mut self) -> DiskFileResult<u64> {
|
||||||
let mut file = self.qcow_file.lock().unwrap();
|
self.qcow_file
|
||||||
|
.seek(SeekFrom::End(0))
|
||||||
file.seek(SeekFrom::End(0)).map_err(DiskFileError::Size)
|
.map_err(DiskFileError::Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
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(QcowSync::new(self.qcow_file.clone())) as Box<dyn AsyncIo>)
|
Ok(Box::new(QcowSync::new(self.qcow_file.clone())) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||||
|
BorrowedDiskFd::new(self.qcow_file.as_raw_fd())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct QcowSync {
|
pub struct QcowSync {
|
||||||
qcow_file: Arc<Mutex<QcowFile>>,
|
qcow_file: QcowFile,
|
||||||
eventfd: EventFd,
|
eventfd: EventFd,
|
||||||
completion_list: VecDeque<(u64, i32)>,
|
completion_list: VecDeque<(u64, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QcowSync {
|
impl QcowSync {
|
||||||
pub fn new(qcow_file: Arc<Mutex<QcowFile>>) -> Self {
|
pub fn new(qcow_file: QcowFile) -> Self {
|
||||||
QcowSync {
|
QcowSync {
|
||||||
qcow_file,
|
qcow_file,
|
||||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
||||||
@@ -54,11 +60,7 @@ impl QcowSync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncAdaptor<QcowFile> for Arc<Mutex<QcowFile>> {
|
impl AsyncAdaptor for QcowFile {}
|
||||||
fn file(&mut self) -> MutexGuard<QcowFile> {
|
|
||||||
self.lock().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncIo for QcowSync {
|
impl AsyncIo for QcowSync {
|
||||||
fn notifier(&self) -> &EventFd {
|
fn notifier(&self) -> &EventFd {
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Error, Seek, SeekFrom};
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
use io_uring::{opcode, squeue, types, IoUring};
|
use io_uring::{IoUring, opcode, types};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||||
};
|
};
|
||||||
use crate::DiskTopology;
|
use crate::{BatchRequest, DiskTopology, RequestType};
|
||||||
|
|
||||||
pub struct RawFileDisk {
|
pub struct RawFileDisk {
|
||||||
file: File,
|
file: File,
|
||||||
@@ -46,6 +46,10 @@ impl DiskFile for RawFileDisk {
|
|||||||
DiskTopology::default()
|
DiskTopology::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||||
|
BorrowedDiskFd::new(self.file.as_raw_fd())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RawFileAsync {
|
pub struct RawFileAsync {
|
||||||
@@ -86,14 +90,14 @@ impl AsyncIo for RawFileAsync {
|
|||||||
|
|
||||||
// SAFETY: 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 {
|
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.try_into().unwrap())
|
.offset(offset.try_into().unwrap())
|
||||||
.build()
|
.build()
|
||||||
.flags(squeue::Flags::ASYNC)
|
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
)
|
)
|
||||||
|
.map_err(|_| AsyncIoError::ReadVectored(Error::other("Submission queue is full")))?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the submission queue and submit new operations to the
|
// Update the submission queue and submit new operations to the
|
||||||
@@ -114,14 +118,14 @@ impl AsyncIo for RawFileAsync {
|
|||||||
|
|
||||||
// SAFETY: 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 {
|
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.try_into().unwrap())
|
.offset(offset.try_into().unwrap())
|
||||||
.build()
|
.build()
|
||||||
.flags(squeue::Flags::ASYNC)
|
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
)
|
)
|
||||||
|
.map_err(|_| AsyncIoError::WriteVectored(Error::other("Submission queue is full")))?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the submission queue and submit new operations to the
|
// Update the submission queue and submit new operations to the
|
||||||
@@ -137,13 +141,13 @@ impl AsyncIo for RawFileAsync {
|
|||||||
let (submitter, mut sq, _) = self.io_uring.split();
|
let (submitter, mut sq, _) = self.io_uring.split();
|
||||||
|
|
||||||
// SAFETY: we know the file descriptor is valid.
|
// SAFETY: we know the file descriptor is valid.
|
||||||
let _ = unsafe {
|
unsafe {
|
||||||
sq.push(
|
sq.push(
|
||||||
&opcode::Fsync::new(types::Fd(self.fd))
|
&opcode::Fsync::new(types::Fd(self.fd))
|
||||||
.build()
|
.build()
|
||||||
.flags(squeue::Flags::ASYNC)
|
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
)
|
)
|
||||||
|
.map_err(|_| AsyncIoError::Fsync(Error::other("Submission queue is full")))?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the submission queue and submit new operations to the
|
// Update the submission queue and submit new operations to the
|
||||||
@@ -164,4 +168,77 @@ impl AsyncIo for RawFileAsync {
|
|||||||
.next()
|
.next()
|
||||||
.map(|entry| (entry.user_data(), entry.result()))
|
.map(|entry| (entry.user_data(), entry.result()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn batch_requests_enabled(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn submit_batch_requests(&mut self, batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
|
||||||
|
if !self.batch_requests_enabled() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (submitter, mut sq, _) = self.io_uring.split();
|
||||||
|
let mut submitted = false;
|
||||||
|
|
||||||
|
for req in batch_request {
|
||||||
|
match req.request_type {
|
||||||
|
RequestType::In => {
|
||||||
|
// SAFETY: we know the file descriptor is valid and we
|
||||||
|
// relied on vm-memory to provide the buffer address.
|
||||||
|
unsafe {
|
||||||
|
sq.push(
|
||||||
|
&opcode::Readv::new(
|
||||||
|
types::Fd(self.fd),
|
||||||
|
req.iovecs.as_ptr(),
|
||||||
|
req.iovecs.len() as u32,
|
||||||
|
)
|
||||||
|
.offset(req.offset as u64)
|
||||||
|
.build()
|
||||||
|
.user_data(req.user_data),
|
||||||
|
)
|
||||||
|
.map_err(|_| {
|
||||||
|
AsyncIoError::ReadVectored(Error::other("Submission queue is full"))
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
submitted = true;
|
||||||
|
}
|
||||||
|
RequestType::Out => {
|
||||||
|
// SAFETY: we know the file descriptor is valid and we
|
||||||
|
// relied on vm-memory to provide the buffer address.
|
||||||
|
unsafe {
|
||||||
|
sq.push(
|
||||||
|
&opcode::Writev::new(
|
||||||
|
types::Fd(self.fd),
|
||||||
|
req.iovecs.as_ptr(),
|
||||||
|
req.iovecs.len() as u32,
|
||||||
|
)
|
||||||
|
.offset(req.offset as u64)
|
||||||
|
.build()
|
||||||
|
.user_data(req.user_data),
|
||||||
|
)
|
||||||
|
.map_err(|_| {
|
||||||
|
AsyncIoError::WriteVectored(Error::other("Submission queue is full"))
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
submitted = true;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
unreachable!("Unexpected batch request type: {:?}", req.request_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only submit if we actually queued something
|
||||||
|
if submitted {
|
||||||
|
// Update the submission queue and submit new operations to the
|
||||||
|
// io_uring instance.
|
||||||
|
sq.sync();
|
||||||
|
submitter
|
||||||
|
.submit()
|
||||||
|
.map_err(AsyncIoError::SubmitBatchRequests)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
|||||||
use vmm_sys_util::aio;
|
use vmm_sys_util::aio;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
|
||||||
};
|
|
||||||
use crate::DiskTopology;
|
use crate::DiskTopology;
|
||||||
|
use crate::async_io::{
|
||||||
|
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct RawFileDiskAio {
|
pub struct RawFileDiskAio {
|
||||||
file: File,
|
file: File,
|
||||||
@@ -49,6 +49,10 @@ impl DiskFile for RawFileDiskAio {
|
|||||||
DiskTopology::default()
|
DiskTopology::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||||
|
BorrowedDiskFd::new(self.file.as_raw_fd())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RawFileAsyncAio {
|
pub struct RawFileAsyncAio {
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
|||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
|
||||||
};
|
|
||||||
use crate::DiskTopology;
|
use crate::DiskTopology;
|
||||||
|
use crate::async_io::{
|
||||||
|
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct RawFileDiskSync {
|
pub struct RawFileDiskSync {
|
||||||
file: File,
|
file: File,
|
||||||
@@ -43,6 +43,10 @@ impl DiskFile for RawFileDiskSync {
|
|||||||
DiskTopology::default()
|
DiskTopology::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||||
|
BorrowedDiskFd::new(self.file.as_raw_fd())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RawFileSync {
|
pub struct RawFileSync {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
|
|
||||||
use crate::{read_aligned_block_size, DiskTopology};
|
use crate::{DiskTopology, read_aligned_block_size};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct VhdFooter {
|
pub struct VhdFooter {
|
||||||
@@ -123,7 +123,7 @@ mod tests {
|
|||||||
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
use vmm_sys_util::tempfile::TempFile;
|
||||||
|
|
||||||
use super::{is_fixed_vhd, VhdFooter};
|
use super::{VhdFooter, is_fixed_vhd};
|
||||||
|
|
||||||
fn valid_fixed_vhd_footer() -> Vec<u8> {
|
fn valid_fixed_vhd_footer() -> Vec<u8> {
|
||||||
vec![
|
vec![
|
||||||
|
|||||||
@@ -5,17 +5,18 @@
|
|||||||
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 std::os::fd::{AsRawFd, RawFd};
|
||||||
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use remain::sorted;
|
use remain::sorted;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::BlockBackend;
|
||||||
use crate::vhdx::vhdx_bat::{BatEntry, VhdxBatError};
|
use crate::vhdx::vhdx_bat::{BatEntry, VhdxBatError};
|
||||||
use crate::vhdx::vhdx_header::{RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError};
|
use crate::vhdx::vhdx_header::{RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError};
|
||||||
use crate::vhdx::vhdx_io::VhdxIoError;
|
use crate::vhdx::vhdx_io::VhdxIoError;
|
||||||
use crate::vhdx::vhdx_metadata::{DiskSpec, VhdxMetadataError};
|
use crate::vhdx::vhdx_metadata::{DiskSpec, VhdxMetadataError};
|
||||||
use crate::BlockBackend;
|
|
||||||
|
|
||||||
mod vhdx_bat;
|
mod vhdx_bat;
|
||||||
mod vhdx_header;
|
mod vhdx_header;
|
||||||
@@ -25,19 +26,19 @@ mod vhdx_metadata;
|
|||||||
#[sorted]
|
#[sorted]
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum VhdxError {
|
pub enum VhdxError {
|
||||||
#[error("Not a VHDx file {0}")]
|
#[error("Not a VHDx file")]
|
||||||
NotVhdx(#[source] VhdxHeaderError),
|
NotVhdx(#[source] VhdxHeaderError),
|
||||||
#[error("Failed to parse VHDx header {0}")]
|
#[error("Failed to parse VHDx header")]
|
||||||
ParseVhdxHeader(#[source] VhdxHeaderError),
|
ParseVhdxHeader(#[source] VhdxHeaderError),
|
||||||
#[error("Failed to parse VHDx metadata {0}")]
|
#[error("Failed to parse VHDx metadata")]
|
||||||
ParseVhdxMetadata(#[source] VhdxMetadataError),
|
ParseVhdxMetadata(#[source] VhdxMetadataError),
|
||||||
#[error("Failed to parse VHDx region entries {0}")]
|
#[error("Failed to parse VHDx region entries")]
|
||||||
ParseVhdxRegionEntry(#[source] VhdxHeaderError),
|
ParseVhdxRegionEntry(#[source] VhdxHeaderError),
|
||||||
#[error("Failed reading metadata {0}")]
|
#[error("Failed reading metadata")]
|
||||||
ReadBatEntry(#[source] VhdxBatError),
|
ReadBatEntry(#[source] VhdxBatError),
|
||||||
#[error("Failed reading sector from disk {0}")]
|
#[error("Failed reading sector from disk")]
|
||||||
ReadFailed(#[source] VhdxIoError),
|
ReadFailed(#[source] VhdxIoError),
|
||||||
#[error("Failed writing to sector on disk {0}")]
|
#[error("Failed writing to sector on disk")]
|
||||||
WriteFailed(#[source] VhdxIoError),
|
WriteFailed(#[source] VhdxIoError),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +103,7 @@ impl Read for Vhdx {
|
|||||||
let sector_count = (buf.len() as u64).div_ceil(self.disk_spec.logical_sector_size as u64);
|
let sector_count = (buf.len() as u64).div_ceil(self.disk_spec.logical_sector_size as u64);
|
||||||
let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64;
|
let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64;
|
||||||
|
|
||||||
vhdx_io::read(
|
let result = vhdx_io::read(
|
||||||
&mut self.file,
|
&mut self.file,
|
||||||
buf,
|
buf,
|
||||||
&self.disk_spec,
|
&self.disk_spec,
|
||||||
@@ -111,13 +112,14 @@ impl Read for Vhdx {
|
|||||||
sector_count,
|
sector_count,
|
||||||
)
|
)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
std::io::Error::new(
|
std::io::Error::other(format!(
|
||||||
std::io::ErrorKind::Other,
|
"Failed reading {sector_count} sectors from VHDx at index {sector_index}: {e}"
|
||||||
format!(
|
))
|
||||||
"Failed reading {sector_count} sectors from VHDx at index {sector_index}: {e}"
|
})?;
|
||||||
),
|
|
||||||
)
|
self.current_offset = self.current_offset.checked_add(result as u64).unwrap();
|
||||||
})
|
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,15 +136,12 @@ impl Write for Vhdx {
|
|||||||
|
|
||||||
if self.first_write {
|
if self.first_write {
|
||||||
self.first_write = false;
|
self.first_write = false;
|
||||||
self.vhdx_header.update(&mut self.file).map_err(|e| {
|
self.vhdx_header
|
||||||
std::io::Error::new(
|
.update(&mut self.file)
|
||||||
std::io::ErrorKind::Other,
|
.map_err(|e| std::io::Error::other(format!("Failed to update VHDx header: {e}")))?;
|
||||||
format!("Failed to update VHDx header: {e}"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vhdx_io::write(
|
let result = vhdx_io::write(
|
||||||
&mut self.file,
|
&mut self.file,
|
||||||
buf,
|
buf,
|
||||||
&mut self.disk_spec,
|
&mut self.disk_spec,
|
||||||
@@ -152,13 +151,14 @@ impl Write for Vhdx {
|
|||||||
sector_count,
|
sector_count,
|
||||||
)
|
)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
std::io::Error::new(
|
std::io::Error::other(format!(
|
||||||
std::io::ErrorKind::Other,
|
"Failed writing {sector_count} sectors on VHDx at index {sector_index}: {e}"
|
||||||
format!(
|
))
|
||||||
"Failed writing {sector_count} sectors on VHDx at index {sector_index}: {e}"
|
})?;
|
||||||
),
|
|
||||||
)
|
self.current_offset = self.current_offset.checked_add(result as u64).unwrap();
|
||||||
})
|
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,11 +187,11 @@ impl Seek for Vhdx {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(o) = new_offset {
|
if let Some(o) = new_offset
|
||||||
if o <= self.virtual_disk_size() {
|
&& o <= self.virtual_disk_size()
|
||||||
self.current_offset = o;
|
{
|
||||||
return Ok(o);
|
self.current_offset = o;
|
||||||
}
|
return Ok(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(std::io::Error::new(
|
Err(std::io::Error::new(
|
||||||
@@ -223,6 +223,12 @@ impl Clone for Vhdx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for Vhdx {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.file.as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn uuid_from_guid(buf: &[u8]) -> Uuid {
|
pub(crate) fn uuid_from_guid(buf: &[u8]) -> Uuid {
|
||||||
// The first 3 fields of UUID are stored in Big Endian format, and
|
// 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
|
// the last 8 bytes are stored as byte array. Therefore, we read the
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ pub enum VhdxBatError {
|
|||||||
InvalidBatEntry,
|
InvalidBatEntry,
|
||||||
#[error("Invalid BAT entry count")]
|
#[error("Invalid BAT entry count")]
|
||||||
InvalidEntryCount,
|
InvalidEntryCount,
|
||||||
#[error("Failed to read BAT entry {0}")]
|
#[error("Failed to read BAT entry")]
|
||||||
ReadBat(#[source] io::Error),
|
ReadBat(#[source] io::Error),
|
||||||
#[error("Failed to write BAT entry {0}")]
|
#[error("Failed to write BAT entry")]
|
||||||
WriteBat(#[source] io::Error),
|
WriteBat(#[source] io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ impl FileTypeIdentifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(packed)]
|
#[repr(C, packed)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Header {
|
pub struct Header {
|
||||||
pub signature: u32,
|
pub signature: u32,
|
||||||
@@ -142,7 +142,7 @@ impl Header {
|
|||||||
return Err(VhdxHeaderError::InvalidHeaderSign);
|
return Err(VhdxHeaderError::InvalidHeaderSign);
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>())?;
|
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
||||||
if header.checksum != new_checksum {
|
if header.checksum != new_checksum {
|
||||||
return Err(VhdxHeaderError::InvalidChecksum(String::from("Header")));
|
return Err(VhdxHeaderError::InvalidChecksum(String::from("Header")));
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,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 write_to_buffer(&self, buffer: &mut [u8; HEADER_SIZE as usize]) {
|
||||||
// SAFETY: self is a valid header.
|
// 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)
|
||||||
@@ -160,7 +160,7 @@ impl Header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates and returns new updated header from the provided current header
|
/// Creates and returns new updated header from the provided current header
|
||||||
pub fn update_header(
|
fn update_header(
|
||||||
f: &mut File,
|
f: &mut File,
|
||||||
current_header: &Header,
|
current_header: &Header,
|
||||||
change_data_guid: bool,
|
change_data_guid: bool,
|
||||||
@@ -191,11 +191,9 @@ impl Header {
|
|||||||
log_offset: current_header.log_offset,
|
log_offset: current_header.log_offset,
|
||||||
};
|
};
|
||||||
|
|
||||||
new_header.get_header_as_buffer(&mut buffer);
|
new_header.write_to_buffer(&mut buffer);
|
||||||
let mut crc = crc_any::CRC::crc32c();
|
new_header.checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
||||||
crc.digest(&buffer);
|
new_header.write_to_buffer(&mut buffer);
|
||||||
new_header.checksum = crc.get_crc() as u32;
|
|
||||||
new_header.get_header_as_buffer(&mut buffer);
|
|
||||||
|
|
||||||
f.seek(SeekFrom::Start(start))
|
f.seek(SeekFrom::Start(start))
|
||||||
.map_err(VhdxHeaderError::SeekHeader)?;
|
.map_err(VhdxHeaderError::SeekHeader)?;
|
||||||
@@ -205,7 +203,7 @@ impl Header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(packed)]
|
#[repr(C, packed)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct RegionTableHeader {
|
struct RegionTableHeader {
|
||||||
pub signature: u32,
|
pub signature: u32,
|
||||||
@@ -231,7 +229,7 @@ impl RegionTableHeader {
|
|||||||
return Err(VhdxHeaderError::InvalidRegionSign);
|
return Err(VhdxHeaderError::InvalidRegionSign);
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>())?;
|
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
||||||
if region_table_header.checksum != new_checksum {
|
if region_table_header.checksum != new_checksum {
|
||||||
return Err(VhdxHeaderError::InvalidChecksum(String::from("Region")));
|
return Err(VhdxHeaderError::InvalidChecksum(String::from("Region")));
|
||||||
}
|
}
|
||||||
@@ -330,7 +328,7 @@ impl RegionInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(packed)]
|
#[repr(C, packed)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct RegionTableEntry {
|
pub struct RegionTableEntry {
|
||||||
pub guid: Uuid,
|
pub guid: Uuid,
|
||||||
@@ -371,22 +369,10 @@ pub struct VhdxHeader {
|
|||||||
impl VhdxHeader {
|
impl VhdxHeader {
|
||||||
/// Creates a VhdxHeader from a reference to a file
|
/// Creates a VhdxHeader from a reference to a file
|
||||||
pub fn new(f: &mut File) -> Result<VhdxHeader> {
|
pub fn new(f: &mut File) -> Result<VhdxHeader> {
|
||||||
let _file_type_identifier: FileTypeIdentifier = FileTypeIdentifier::new(f)?;
|
|
||||||
let header_1 = Header::new(f, HEADER_1_START);
|
|
||||||
let header_2 = Header::new(f, HEADER_2_START);
|
|
||||||
|
|
||||||
let mut file_write_guid: u128 = 0;
|
|
||||||
let metadata = f.metadata().map_err(VhdxHeaderError::ReadMetadata)?;
|
|
||||||
if !metadata.permissions().readonly() {
|
|
||||||
file_write_guid = Uuid::new_v4().as_u128();
|
|
||||||
}
|
|
||||||
|
|
||||||
let (header_1, header_2) =
|
|
||||||
VhdxHeader::update_headers(f, header_1, header_2, file_write_guid)?;
|
|
||||||
Ok(VhdxHeader {
|
Ok(VhdxHeader {
|
||||||
_file_type_identifier,
|
_file_type_identifier: FileTypeIdentifier::new(f)?,
|
||||||
header_1,
|
header_1: Header::new(f, HEADER_1_START)?,
|
||||||
header_2,
|
header_2: Header::new(f, HEADER_2_START)?,
|
||||||
region_table_1: RegionTableHeader::new(f, REGION_TABLE_1_START)?,
|
region_table_1: RegionTableHeader::new(f, REGION_TABLE_1_START)?,
|
||||||
_region_table_2: RegionTableHeader::new(f, REGION_TABLE_2_START)?,
|
_region_table_2: RegionTableHeader::new(f, REGION_TABLE_2_START)?,
|
||||||
})
|
})
|
||||||
@@ -468,13 +454,11 @@ impl VhdxHeader {
|
|||||||
/// 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.
|
||||||
pub fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> Result<u32> {
|
fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> u32 {
|
||||||
// Read the checksum into a mutable slice
|
// Read the original checksum from the buffer
|
||||||
let csum_buf = &mut buffer[csum_offset..csum_offset + 4];
|
let orig_csum = LittleEndian::read_u32(&buffer[csum_offset..csum_offset + 4]);
|
||||||
// Convert the checksum chunk into a u32 integer
|
|
||||||
let orig_csum = LittleEndian::read_u32(csum_buf);
|
|
||||||
// Zero the checksum in the buffer
|
// Zero the checksum in the buffer
|
||||||
LittleEndian::write_u32(csum_buf, 0);
|
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], 0);
|
||||||
// Calculate the checksum on the resulting buffer
|
// Calculate the checksum on the resulting buffer
|
||||||
let mut crc = crc_any::CRC::crc32c();
|
let mut crc = crc_any::CRC::crc32c();
|
||||||
crc.digest(&buffer);
|
crc.digest(&buffer);
|
||||||
@@ -483,5 +467,5 @@ pub fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> Result<u32>
|
|||||||
// Put back the original checksum in the buffer
|
// Put back the original checksum in the buffer
|
||||||
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], orig_csum);
|
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], orig_csum);
|
||||||
|
|
||||||
Ok(new_csum)
|
new_csum
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,7 @@ pub enum VhdxIoError {
|
|||||||
pub type Result<T> = std::result::Result<T, VhdxIoError>;
|
pub type Result<T> = std::result::Result<T, VhdxIoError>;
|
||||||
|
|
||||||
macro_rules! align {
|
macro_rules! align {
|
||||||
($n:expr, $align:expr) => {{
|
($n:expr, $align:expr) => {{ $n.div_ceil($align) * $align }};
|
||||||
$n.div_ceil($align) * $align
|
|
||||||
}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -95,46 +93,45 @@ pub fn read(
|
|||||||
mut sector_index: u64,
|
mut sector_index: u64,
|
||||||
mut sector_count: u64,
|
mut sector_count: u64,
|
||||||
) -> Result<usize> {
|
) -> Result<usize> {
|
||||||
|
if disk_spec.has_parent {
|
||||||
|
return Err(VhdxIoError::UnsupportedMode);
|
||||||
|
}
|
||||||
|
|
||||||
let mut read_count: usize = 0;
|
let mut read_count: usize = 0;
|
||||||
|
|
||||||
while sector_count > 0 {
|
while sector_count > 0 {
|
||||||
if disk_spec.has_parent {
|
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
} else {
|
|
||||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
|
||||||
|
|
||||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
let bat_entry = match bat.get(sector.bat_index as usize) {
|
||||||
Some(entry) => entry.0,
|
Some(entry) => entry.0,
|
||||||
None => {
|
None => {
|
||||||
return Err(VhdxIoError::InvalidBatIndex);
|
return Err(VhdxIoError::InvalidBatIndex);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
|
||||||
f.seek(SeekFrom::Start(sector.file_offset))
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
f.read_exact(
|
|
||||||
&mut buf[read_count
|
|
||||||
..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
|
||||||
)
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(VhdxIoError::InvalidBatEntryState);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sector_count -= sector.free_sectors;
|
|
||||||
sector_index += sector.free_sectors;
|
|
||||||
read_count = sector.free_bytes as usize;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
|
||||||
|
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
|
||||||
|
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
|
||||||
|
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
|
||||||
|
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {}
|
||||||
|
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
||||||
|
f.seek(SeekFrom::Start(sector.file_offset))
|
||||||
|
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||||
|
f.read_exact(
|
||||||
|
&mut buf
|
||||||
|
[read_count..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
||||||
|
)
|
||||||
|
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||||
|
}
|
||||||
|
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
||||||
|
return Err(VhdxIoError::UnsupportedMode);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(VhdxIoError::InvalidBatEntryState);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sector_count -= sector.free_sectors;
|
||||||
|
sector_index += sector.free_sectors;
|
||||||
|
read_count += sector.free_bytes as usize;
|
||||||
}
|
}
|
||||||
Ok(read_count)
|
Ok(read_count)
|
||||||
}
|
}
|
||||||
@@ -150,77 +147,73 @@ pub fn write(
|
|||||||
mut sector_index: u64,
|
mut sector_index: u64,
|
||||||
mut sector_count: u64,
|
mut sector_count: u64,
|
||||||
) -> Result<usize> {
|
) -> Result<usize> {
|
||||||
|
if disk_spec.has_parent {
|
||||||
|
return Err(VhdxIoError::UnsupportedMode);
|
||||||
|
}
|
||||||
|
|
||||||
let mut write_count: usize = 0;
|
let mut write_count: usize = 0;
|
||||||
|
|
||||||
while sector_count > 0 {
|
while sector_count > 0 {
|
||||||
if disk_spec.has_parent {
|
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
} else {
|
|
||||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
|
||||||
|
|
||||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
let bat_entry = match bat.get(sector.bat_index as usize) {
|
||||||
Some(entry) => entry.0,
|
Some(entry) => entry.0,
|
||||||
None => {
|
None => {
|
||||||
return Err(VhdxIoError::InvalidBatIndex);
|
return Err(VhdxIoError::InvalidBatIndex);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {
|
|
||||||
let file_offset =
|
|
||||||
align!(disk_spec.image_size, vhdx_metadata::BLOCK_SIZE_MIN as u64);
|
|
||||||
let new_size = file_offset
|
|
||||||
.checked_add(disk_spec.block_size as u64)
|
|
||||||
.ok_or(VhdxIoError::InvalidDiskSize)?;
|
|
||||||
|
|
||||||
f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?;
|
|
||||||
disk_spec.image_size = new_size;
|
|
||||||
|
|
||||||
let new_bat_entry = file_offset
|
|
||||||
| (vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT & vhdx_bat::BAT_STATE_BIT_MASK);
|
|
||||||
bat[sector.bat_index as usize] = BatEntry(new_bat_entry);
|
|
||||||
BatEntry::write_bat_entries(f, bat_offset, bat)
|
|
||||||
.map_err(VhdxIoError::WriteBat)?;
|
|
||||||
|
|
||||||
if file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(SeekFrom::Start(file_offset))
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
f.write_all(
|
|
||||||
&buf[write_count
|
|
||||||
..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
|
||||||
)
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
|
||||||
if sector.file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(SeekFrom::Start(sector.file_offset))
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
f.write_all(
|
|
||||||
&buf[write_count
|
|
||||||
..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
|
||||||
)
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(VhdxIoError::InvalidBatEntryState);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sector_count -= sector.free_sectors;
|
|
||||||
sector_index += sector.free_sectors;
|
|
||||||
write_count = sector.free_bytes as usize;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
|
||||||
|
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
|
||||||
|
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
|
||||||
|
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
|
||||||
|
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {
|
||||||
|
let file_offset =
|
||||||
|
align!(disk_spec.image_size, vhdx_metadata::BLOCK_SIZE_MIN as u64);
|
||||||
|
let new_size = file_offset
|
||||||
|
.checked_add(disk_spec.block_size as u64)
|
||||||
|
.ok_or(VhdxIoError::InvalidDiskSize)?;
|
||||||
|
|
||||||
|
f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?;
|
||||||
|
disk_spec.image_size = new_size;
|
||||||
|
|
||||||
|
let new_bat_entry = file_offset
|
||||||
|
| (vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT & vhdx_bat::BAT_STATE_BIT_MASK);
|
||||||
|
bat[sector.bat_index as usize] = BatEntry(new_bat_entry);
|
||||||
|
BatEntry::write_bat_entries(f, bat_offset, bat).map_err(VhdxIoError::WriteBat)?;
|
||||||
|
|
||||||
|
if file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.seek(SeekFrom::Start(file_offset))
|
||||||
|
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||||
|
f.write_all(
|
||||||
|
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
||||||
|
)
|
||||||
|
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||||
|
}
|
||||||
|
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
||||||
|
if sector.file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.seek(SeekFrom::Start(sector.file_offset))
|
||||||
|
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||||
|
f.write_all(
|
||||||
|
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
||||||
|
)
|
||||||
|
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||||
|
}
|
||||||
|
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
||||||
|
return Err(VhdxIoError::UnsupportedMode);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(VhdxIoError::InvalidBatEntryState);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sector_count -= sector.free_sectors;
|
||||||
|
sector_index += sector.free_sectors;
|
||||||
|
write_count += sector.free_bytes as usize;
|
||||||
}
|
}
|
||||||
Ok(write_count)
|
Ok(write_count)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ const METADATA_LENGTH_MAX: u32 = 1 << 20; // 1 MiB
|
|||||||
pub enum VhdxMetadataError {
|
pub enum VhdxMetadataError {
|
||||||
#[error("Invalid block size count")]
|
#[error("Invalid block size count")]
|
||||||
InvalidBlockSize,
|
InvalidBlockSize,
|
||||||
|
#[error("Invalid disk size {0}")]
|
||||||
|
InvalidDiskSize(u64),
|
||||||
#[error("Invalid metadata entry count")]
|
#[error("Invalid metadata entry count")]
|
||||||
InvalidEntryCount,
|
InvalidEntryCount,
|
||||||
#[error("Invalid logical sector size")]
|
#[error("Invalid logical sector size")]
|
||||||
@@ -214,6 +216,14 @@ impl DiskSpec {
|
|||||||
if metadata_presence != METADATA_ALL_PRESENT {
|
if metadata_presence != METADATA_ALL_PRESENT {
|
||||||
return Err(VhdxMetadataError::MissingMetadata);
|
return Err(VhdxMetadataError::MissingMetadata);
|
||||||
}
|
}
|
||||||
|
// Make sure virtual disk size is not zero
|
||||||
|
if (metadata_presence & METADATA_VIRTUAL_DISK_SIZE_PRESENT != 0)
|
||||||
|
&& disk_spec.virtual_disk_size == 0
|
||||||
|
{
|
||||||
|
return Err(VhdxMetadataError::InvalidDiskSize(
|
||||||
|
disk_spec.virtual_disk_size,
|
||||||
|
));
|
||||||
|
}
|
||||||
// Check if the virtual disk size is a multiple of the logical sector
|
// Check if the virtual disk size is a multiple of the logical sector
|
||||||
// size.
|
// size.
|
||||||
if ((metadata_presence & METADATA_LOGICAL_SECTOR_SIZE_PRESENT) != 0)
|
if ((metadata_presence & METADATA_LOGICAL_SECTOR_SIZE_PRESENT) != 0)
|
||||||
@@ -257,7 +267,7 @@ impl DiskSpec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(packed)]
|
#[repr(C, packed)]
|
||||||
#[derive(Default, Debug, Clone, Copy)]
|
#[derive(Default, Debug, Clone, Copy)]
|
||||||
struct MetadataTableHeader {
|
struct MetadataTableHeader {
|
||||||
signature: u64,
|
signature: u64,
|
||||||
@@ -288,7 +298,7 @@ impl MetadataTableHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(packed)]
|
#[repr(C, packed)]
|
||||||
#[derive(Default, Debug, Clone, Copy)]
|
#[derive(Default, Debug, Clone, Copy)]
|
||||||
pub struct MetadataTableEntry {
|
pub struct MetadataTableEntry {
|
||||||
item_id: Uuid,
|
item_id: Uuid,
|
||||||
|
|||||||
@@ -4,29 +4,31 @@
|
|||||||
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::os::fd::AsRawFd;
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
|
||||||
use crate::vhdx::{Result as VhdxResult, Vhdx};
|
|
||||||
use crate::AsyncAdaptor;
|
use crate::AsyncAdaptor;
|
||||||
|
use crate::async_io::{
|
||||||
|
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||||
|
};
|
||||||
|
use crate::vhdx::{Result as VhdxResult, Vhdx};
|
||||||
|
|
||||||
pub struct VhdxDiskSync {
|
pub struct VhdxDiskSync {
|
||||||
vhdx_file: Arc<Mutex<Vhdx>>,
|
vhdx_file: Vhdx,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VhdxDiskSync {
|
impl VhdxDiskSync {
|
||||||
pub fn new(f: File) -> VhdxResult<Self> {
|
pub fn new(f: File) -> VhdxResult<Self> {
|
||||||
Ok(VhdxDiskSync {
|
Ok(VhdxDiskSync {
|
||||||
vhdx_file: Arc::new(Mutex::new(Vhdx::new(f)?)),
|
vhdx_file: Vhdx::new(f)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for VhdxDiskSync {
|
impl DiskFile for VhdxDiskSync {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn size(&mut self) -> DiskFileResult<u64> {
|
||||||
Ok(self.vhdx_file.lock().unwrap().virtual_disk_size())
|
Ok(self.vhdx_file.virtual_disk_size())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
@@ -35,16 +37,20 @@ impl DiskFile for VhdxDiskSync {
|
|||||||
as Box<dyn AsyncIo>,
|
as Box<dyn AsyncIo>,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||||
|
BorrowedDiskFd::new(self.vhdx_file.as_raw_fd())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VhdxSync {
|
pub struct VhdxSync {
|
||||||
vhdx_file: Arc<Mutex<Vhdx>>,
|
vhdx_file: Vhdx,
|
||||||
eventfd: EventFd,
|
eventfd: EventFd,
|
||||||
completion_list: VecDeque<(u64, i32)>,
|
completion_list: VecDeque<(u64, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VhdxSync {
|
impl VhdxSync {
|
||||||
pub fn new(vhdx_file: Arc<Mutex<Vhdx>>) -> std::io::Result<Self> {
|
pub fn new(vhdx_file: Vhdx) -> std::io::Result<Self> {
|
||||||
Ok(VhdxSync {
|
Ok(VhdxSync {
|
||||||
vhdx_file,
|
vhdx_file,
|
||||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)?,
|
eventfd: EventFd::new(libc::EFD_NONBLOCK)?,
|
||||||
@@ -53,11 +59,7 @@ impl VhdxSync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncAdaptor<Vhdx> for Arc<Mutex<Vhdx>> {
|
impl AsyncAdaptor for Vhdx {}
|
||||||
fn file(&mut self) -> MutexGuard<Vhdx> {
|
|
||||||
self.lock().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncIo for VhdxSync {
|
impl AsyncIo for VhdxSync {
|
||||||
fn notifier(&self) -> &EventFd {
|
fn notifier(&self) -> &EventFd {
|
||||||
|
|||||||
20
build.rs
20
build.rs
@@ -3,17 +3,25 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
|
use std::env;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut version = "v".to_owned() + env!("CARGO_PKG_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() {
|
&& git_out.status.success()
|
||||||
if let Ok(git_out_str) = String::from_utf8(git_out.stdout) {
|
&& let Ok(git_out_str) = String::from_utf8(git_out.stdout)
|
||||||
version = git_out_str;
|
{
|
||||||
}
|
version = git_out_str;
|
||||||
}
|
// Pop the trailing newline.
|
||||||
|
version.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append CH_EXTRA_VERSION to version if it is set.
|
||||||
|
if let Ok(extra_version) = env::var("CH_EXTRA_VERSION") {
|
||||||
|
println!("cargo:rerun-if-env-changed=CH_EXTRA_VERSION");
|
||||||
|
version.push_str(&format!("-{extra_version}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// This println!() has a special behavior, as it will set the environment
|
// This println!() has a special behavior, as it will set the environment
|
||||||
|
|||||||
@@ -1,23 +1,29 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Chromium OS Authors"]
|
authors = ["The Chromium OS Authors"]
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
name = "devices"
|
name = "devices"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
acpi_tables = { workspace = true }
|
acpi_tables = { workspace = true }
|
||||||
anyhow = "1.0.87"
|
anyhow = { workspace = true }
|
||||||
arch = { path = "../arch" }
|
arch = { path = "../arch" }
|
||||||
bitflags = "2.6.0"
|
bitfield-struct = { version = "0.10.1", optional = true }
|
||||||
byteorder = "1.5.0"
|
bitflags = { workspace = true }
|
||||||
|
byteorder = { workspace = true }
|
||||||
event_monitor = { path = "../event_monitor" }
|
event_monitor = { path = "../event_monitor" }
|
||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
libc = "0.2.158"
|
libc = { workspace = true }
|
||||||
log = "0.4.22"
|
linux-loader = { workspace = true, features = [
|
||||||
|
"bzimage",
|
||||||
|
"elf",
|
||||||
|
"pe",
|
||||||
|
], optional = true }
|
||||||
|
log = { workspace = true }
|
||||||
num_enum = "0.7.2"
|
num_enum = "0.7.2"
|
||||||
pci = { path = "../pci" }
|
pci = { path = "../pci" }
|
||||||
serde = { version = "1.0.208", features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
thiserror = "1.0.62"
|
thiserror = { workspace = true }
|
||||||
tpm = { path = "../tpm" }
|
tpm = { path = "../tpm" }
|
||||||
vm-allocator = { path = "../vm-allocator" }
|
vm-allocator = { path = "../vm-allocator" }
|
||||||
vm-device = { path = "../vm-device" }
|
vm-device = { path = "../vm-device" }
|
||||||
@@ -28,10 +34,17 @@ vm-memory = { workspace = true, features = [
|
|||||||
] }
|
] }
|
||||||
vm-migration = { path = "../vm-migration" }
|
vm-migration = { path = "../vm-migration" }
|
||||||
vmm-sys-util = { workspace = true }
|
vmm-sys-util = { workspace = true }
|
||||||
|
zerocopy = { version = "0.8.26", features = [
|
||||||
|
"alloc",
|
||||||
|
"derive",
|
||||||
|
], optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
||||||
arch = { path = "../arch" }
|
arch = { path = "../arch" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
fw_cfg = ["arch/fw_cfg", "bitfield-struct", "linux-loader", "zerocopy"]
|
||||||
|
ivshmem = []
|
||||||
|
kvm = ["arch/kvm"]
|
||||||
pvmemcontrol = []
|
pvmemcontrol = []
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ use std::sync::{Arc, Barrier};
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use acpi_tables::{aml, Aml, AmlSink};
|
use acpi_tables::{Aml, AmlSink, aml};
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_memory::GuestAddress;
|
use vm_memory::GuestAddress;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
|||||||
144
devices/src/aia.rs
Normal file
144
devices/src/aia.rs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||||
|
// Copyright 2020, ARM Limited.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
|
use super::interrupt_controller::{Error, InterruptController};
|
||||||
|
extern crate arch;
|
||||||
|
use std::result;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use arch::layout;
|
||||||
|
use hypervisor::arch::riscv64::aia::{Vaia, VaiaConfig};
|
||||||
|
use hypervisor::{AiaState, CpuState};
|
||||||
|
use vm_device::interrupt::{
|
||||||
|
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||||
|
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
||||||
|
};
|
||||||
|
use vm_memory::address::Address;
|
||||||
|
use vm_migration::{Migratable, Pausable, Snapshottable, Transportable};
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
|
// Reserve 32 IRQs for legacy devices.
|
||||||
|
pub const IRQ_LEGACY_BASE: usize = layout::IRQ_BASE as usize;
|
||||||
|
pub const IRQ_LEGACY_COUNT: usize = 32;
|
||||||
|
// TODO: AIA snapshotting is not yet completed.
|
||||||
|
pub const _AIA_SNAPSHOT_ID: &str = "";
|
||||||
|
|
||||||
|
// Aia (Advance Interrupt Architecture) struct provides all the functionality of a
|
||||||
|
// AIA device. It wraps a hypervisor-emulated AIA device (Vaia) provided by the
|
||||||
|
// `hypervisor` crate.
|
||||||
|
// Aia struct also implements InterruptController to provide interrupt delivery
|
||||||
|
// service.
|
||||||
|
pub struct Aia {
|
||||||
|
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
||||||
|
// The hypervisor agnostic virtual AIA
|
||||||
|
vaia: Arc<Mutex<dyn Vaia>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Aia {
|
||||||
|
pub fn new(
|
||||||
|
vcpu_count: u32,
|
||||||
|
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||||
|
vm: Arc<dyn hypervisor::Vm>,
|
||||||
|
) -> Result<Aia> {
|
||||||
|
let interrupt_source_group = interrupt_manager
|
||||||
|
.create_group(MsiIrqGroupConfig {
|
||||||
|
base: IRQ_LEGACY_BASE as InterruptIndex,
|
||||||
|
count: IRQ_LEGACY_COUNT as InterruptIndex,
|
||||||
|
})
|
||||||
|
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||||
|
|
||||||
|
let vaia = vm
|
||||||
|
.create_vaia(Aia::create_default_config(vcpu_count as u64))
|
||||||
|
.map_err(Error::CreateAia)?;
|
||||||
|
|
||||||
|
let aia = Aia {
|
||||||
|
interrupt_source_group,
|
||||||
|
vaia,
|
||||||
|
};
|
||||||
|
aia.enable()?;
|
||||||
|
|
||||||
|
Ok(aia)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restore_vaia(
|
||||||
|
&mut self,
|
||||||
|
state: Option<AiaState>,
|
||||||
|
_saved_vcpu_states: &[CpuState],
|
||||||
|
) -> Result<()> {
|
||||||
|
self.vaia
|
||||||
|
.clone()
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.set_state(&state.unwrap())
|
||||||
|
.map_err(Error::RestoreAia)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enable(&self) -> Result<()> {
|
||||||
|
// Set irqfd for legacy interrupts
|
||||||
|
self.interrupt_source_group
|
||||||
|
.enable()
|
||||||
|
.map_err(Error::EnableInterrupt)?;
|
||||||
|
|
||||||
|
// Set irq_routing for legacy interrupts.
|
||||||
|
// irqchip: Hardcode to 0 as we support only 1 APLIC
|
||||||
|
// pin: Use irq number as pin
|
||||||
|
for i in IRQ_LEGACY_BASE..(IRQ_LEGACY_BASE + IRQ_LEGACY_COUNT) {
|
||||||
|
let config = LegacyIrqSourceConfig {
|
||||||
|
irqchip: 0,
|
||||||
|
pin: (i - IRQ_LEGACY_BASE) as u32,
|
||||||
|
};
|
||||||
|
self.interrupt_source_group
|
||||||
|
.update(
|
||||||
|
i as InterruptIndex,
|
||||||
|
InterruptSourceConfig::LegacyIrq(config),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.map_err(Error::EnableInterrupt)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.interrupt_source_group
|
||||||
|
.set_gsi()
|
||||||
|
.map_err(Error::EnableInterrupt)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default config implied by arch::layout
|
||||||
|
pub fn create_default_config(vcpu_count: u64) -> VaiaConfig {
|
||||||
|
VaiaConfig {
|
||||||
|
vcpu_count: vcpu_count as u32,
|
||||||
|
aplic_addr: layout::APLIC_START.raw_value(),
|
||||||
|
imsic_addr: layout::IMSIC_START.raw_value(),
|
||||||
|
nr_irqs: layout::IRQ_NUM,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_vaia(&mut self) -> Result<Arc<Mutex<dyn Vaia>>> {
|
||||||
|
Ok(self.vaia.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterruptController for Aia {
|
||||||
|
// This should be called anytime an interrupt needs to be injected into the
|
||||||
|
// running guest.
|
||||||
|
fn service_irq(&mut self, irq: usize) -> Result<()> {
|
||||||
|
self.interrupt_source_group
|
||||||
|
.trigger(irq as InterruptIndex)
|
||||||
|
.map_err(Error::TriggerInterrupt)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn notifier(&self, irq: usize) -> Option<EventFd> {
|
||||||
|
self.interrupt_source_group.notifier(irq as InterruptIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Snapshottable for Aia {}
|
||||||
|
impl Pausable for Aia {}
|
||||||
|
impl Transportable for Aia {}
|
||||||
|
impl Migratable for Aia {}
|
||||||
@@ -9,8 +9,8 @@ use std::sync::{Arc, Mutex};
|
|||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use arch::layout;
|
use arch::layout;
|
||||||
use hypervisor::arch::aarch64::gic::{Vgic, VgicConfig};
|
use hypervisor::CpuState;
|
||||||
use hypervisor::{CpuState, GicState};
|
use hypervisor::arch::aarch64::gic::{GicState, Vgic, VgicConfig};
|
||||||
use vm_device::interrupt::{
|
use vm_device::interrupt::{
|
||||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||||
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
||||||
@@ -39,7 +39,7 @@ pub struct Gic {
|
|||||||
|
|
||||||
impl Gic {
|
impl Gic {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
vcpu_count: u8,
|
vcpu_count: u32,
|
||||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||||
vm: Arc<dyn hypervisor::Vm>,
|
vm: Arc<dyn hypervisor::Vm>,
|
||||||
) -> Result<Gic> {
|
) -> Result<Gic> {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||||
// Copyright 2020, ARM Limited.
|
// Copyright 2020, ARM Limited.
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
@@ -16,31 +17,39 @@ pub enum Error {
|
|||||||
#[error("Invalid delivery mode")]
|
#[error("Invalid delivery mode")]
|
||||||
InvalidDeliveryMode,
|
InvalidDeliveryMode,
|
||||||
/// Failed creating the interrupt source group.
|
/// Failed creating the interrupt source group.
|
||||||
#[error("Failed creating the interrupt source group: {0}")]
|
#[error("Failed creating the interrupt source group")]
|
||||||
CreateInterruptSourceGroup(io::Error),
|
CreateInterruptSourceGroup(#[source] io::Error),
|
||||||
/// Failed triggering the interrupt.
|
/// Failed triggering the interrupt.
|
||||||
#[error("Failed triggering the interrupt: {0}")]
|
#[error("Failed triggering the interrupt")]
|
||||||
TriggerInterrupt(io::Error),
|
TriggerInterrupt(#[source] io::Error),
|
||||||
/// Failed masking the interrupt.
|
/// Failed masking the interrupt.
|
||||||
#[error("Failed masking the interrupt: {0}")]
|
#[error("Failed masking the interrupt")]
|
||||||
MaskInterrupt(io::Error),
|
MaskInterrupt(#[source] io::Error),
|
||||||
/// Failed unmasking the interrupt.
|
/// Failed unmasking the interrupt.
|
||||||
#[error("Failed unmasking the interrupt: {0}")]
|
#[error("Failed unmasking the interrupt")]
|
||||||
UnmaskInterrupt(io::Error),
|
UnmaskInterrupt(#[source] io::Error),
|
||||||
/// Failed updating the interrupt.
|
/// Failed updating the interrupt.
|
||||||
#[error("Failed updating the interrupt: {0}")]
|
#[error("Failed updating the interrupt")]
|
||||||
UpdateInterrupt(io::Error),
|
UpdateInterrupt(#[source] io::Error),
|
||||||
/// Failed enabling the interrupt.
|
/// Failed enabling the interrupt.
|
||||||
#[error("Failed enabling the interrupt: {0}")]
|
#[error("Failed enabling the interrupt")]
|
||||||
EnableInterrupt(io::Error),
|
EnableInterrupt(#[source] io::Error),
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
/// Failed creating GIC device.
|
/// Failed creating GIC device.
|
||||||
#[error("Failed creating GIC device: {0}")]
|
#[error("Failed creating GIC device")]
|
||||||
CreateGic(hypervisor::HypervisorVmError),
|
CreateGic(#[source] hypervisor::HypervisorVmError),
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
/// Failed restoring GIC device.
|
/// Failed restoring GIC device.
|
||||||
#[error("Failed restoring GIC device: {0}")]
|
#[error("Failed restoring GIC device")]
|
||||||
RestoreGic(hypervisor::arch::aarch64::gic::Error),
|
RestoreGic(#[source] hypervisor::arch::aarch64::gic::Error),
|
||||||
|
#[cfg(target_arch = "riscv64")]
|
||||||
|
/// Failed creating AIA device.
|
||||||
|
#[error("Failed creating AIA device")]
|
||||||
|
CreateAia(#[source] hypervisor::HypervisorVmError),
|
||||||
|
#[cfg(target_arch = "riscv64")]
|
||||||
|
/// Failed restoring AIA device.
|
||||||
|
#[error("Failed restoring AIA device")]
|
||||||
|
RestoreAia(#[source] hypervisor::arch::riscv64::aia::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
@@ -67,7 +76,7 @@ pub struct MsiMessage {
|
|||||||
// Introduce trait InterruptController to uniform the interrupt
|
// Introduce trait InterruptController to uniform the interrupt
|
||||||
// service provided for devices.
|
// service provided for devices.
|
||||||
// Device manager uses this trait without caring whether it is a
|
// Device manager uses this trait without caring whether it is a
|
||||||
// IOAPIC (X86) or GIC (Arm).
|
// IOAPIC (X86), GIC (Arm) or AIA (RISC-V).
|
||||||
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 = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ use std::sync::{Arc, Barrier};
|
|||||||
|
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use vm_device::BusDevice;
|
||||||
use vm_device::interrupt::{
|
use vm_device::interrupt::{
|
||||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||||
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||||
};
|
};
|
||||||
use vm_device::BusDevice;
|
|
||||||
use vm_memory::GuestAddress;
|
use vm_memory::GuestAddress;
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
@@ -346,9 +346,9 @@ impl Ioapic {
|
|||||||
|
|
||||||
// Generate MSI message address
|
// Generate MSI message address
|
||||||
let low_addr: u32 = self.apic_address.0 as u32
|
let low_addr: u32 = self.apic_address.0 as u32
|
||||||
| u32::from(destination_id) << 12
|
| (u32::from(destination_id) << 12)
|
||||||
| u32::from(redirection_hint) << 3
|
| (u32::from(redirection_hint) << 3)
|
||||||
| u32::from(destination_mode) << 2;
|
| (u32::from(destination_mode) << 2);
|
||||||
|
|
||||||
// Validate Trigger Mode value
|
// Validate Trigger Mode value
|
||||||
let trigger_mode = trigger_mode(entry);
|
let trigger_mode = trigger_mode(entry);
|
||||||
@@ -372,9 +372,9 @@ impl Ioapic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate MSI message data
|
// Generate MSI message data
|
||||||
let data: u32 = u32::from(trigger_mode) << 15
|
let data: u32 = (u32::from(trigger_mode) << 15)
|
||||||
| u32::from(remote_irr(entry)) << 14
|
| (u32::from(remote_irr(entry)) << 14)
|
||||||
| u32::from(delivery_mode) << 8
|
| (u32::from(delivery_mode) << 8)
|
||||||
| u32::from(vector(entry));
|
| u32::from(vector(entry));
|
||||||
|
|
||||||
let config = MsiIrqSourceConfig {
|
let config = MsiIrqSourceConfig {
|
||||||
@@ -418,7 +418,7 @@ impl InterruptController for Ioapic {
|
|||||||
self.interrupt_source_group
|
self.interrupt_source_group
|
||||||
.trigger(irq as InterruptIndex)
|
.trigger(irq as InterruptIndex)
|
||||||
.map_err(Error::TriggerInterrupt)?;
|
.map_err(Error::TriggerInterrupt)?;
|
||||||
debug!("Interrupt {irq} successfully delivered");
|
trace!("Interrupt {irq} successfully delivered");
|
||||||
|
|
||||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
// If trigger mode is level sensitive, set the Remote IRR bit.
|
||||||
// It will be cleared when the EOI is received.
|
// It will be cleared when the EOI is received.
|
||||||
|
|||||||
420
devices/src/ivshmem.rs
Normal file
420
devices/src/ivshmem.rs
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
// Copyright © 2024 Tencent Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use std::any::Any;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::result;
|
||||||
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
use std::sync::{Arc, Barrier, Mutex};
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
use pci::{
|
||||||
|
BarReprogrammingParams, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable,
|
||||||
|
PciBarRegionType, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType,
|
||||||
|
PciSubclass,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use thiserror::Error;
|
||||||
|
use vm_allocator::{AddressAllocator, SystemAllocator};
|
||||||
|
use vm_device::{BusDevice, Resource, UserspaceMapping};
|
||||||
|
use vm_memory::bitmap::AtomicBitmap;
|
||||||
|
use vm_memory::{Address, GuestAddress};
|
||||||
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
|
|
||||||
|
const IVSHMEM_BAR0_IDX: usize = 0;
|
||||||
|
const IVSHMEM_BAR1_IDX: usize = 1;
|
||||||
|
const IVSHMEM_BAR2_IDX: usize = 2;
|
||||||
|
|
||||||
|
const IVSHMEM_VENDOR_ID: u16 = 0x1af4;
|
||||||
|
const IVSHMEM_DEVICE_ID: u16 = 0x1110;
|
||||||
|
|
||||||
|
const IVSHMEM_REG_BAR_SIZE: u64 = 0x100;
|
||||||
|
|
||||||
|
type GuestRegionMmap = vm_memory::GuestRegionMmap<AtomicBitmap>;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum IvshmemError {
|
||||||
|
#[error("Failed to retrieve PciConfigurationState: {0}")]
|
||||||
|
RetrievePciConfigurationState(#[source] anyhow::Error),
|
||||||
|
#[error("Failed to retrieve IvshmemDeviceState: {0}")]
|
||||||
|
RetrieveIvshmemDeviceStateState(#[source] anyhow::Error),
|
||||||
|
#[error("Failed to remove user memory region")]
|
||||||
|
RemoveUserMemoryRegion,
|
||||||
|
#[error("Failed to create user memory region.")]
|
||||||
|
CreateUserMemoryRegion,
|
||||||
|
#[error("Failed to create userspace mapping.")]
|
||||||
|
CreateUserspaceMapping,
|
||||||
|
#[error("Failed to remove old userspace mapping.")]
|
||||||
|
RemoveUserspaceMapping,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum IvshmemSubclass {
|
||||||
|
Other = 0x00,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PciSubclass for IvshmemSubclass {
|
||||||
|
fn get_register_value(&self) -> u8 {
|
||||||
|
*self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IvshmemOps: Send + Sync {
|
||||||
|
fn map_ram_region(
|
||||||
|
&mut self,
|
||||||
|
start_addr: u64,
|
||||||
|
size: usize,
|
||||||
|
backing_file: Option<PathBuf>,
|
||||||
|
) -> Result<(Arc<GuestRegionMmap>, UserspaceMapping), IvshmemError>;
|
||||||
|
|
||||||
|
fn unmap_ram_region(&mut self, mapping: UserspaceMapping) -> Result<(), IvshmemError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inner-Vm Shared Memory Device (Ivshmem device)
|
||||||
|
///
|
||||||
|
/// This device can share memory between host and guest(ivshmem-plain)
|
||||||
|
/// and share memory between guests(ivshmem-doorbell).
|
||||||
|
/// But only ivshmem-plain support now, ivshmem-doorbell doesn't support yet.
|
||||||
|
pub struct IvshmemDevice {
|
||||||
|
id: String,
|
||||||
|
|
||||||
|
// ivshmem device registers
|
||||||
|
// (only used for ivshmem-doorbell, ivshmem-doorbell don't support yet)
|
||||||
|
_interrupt_mask: u32,
|
||||||
|
_interrupt_status: Arc<AtomicU32>,
|
||||||
|
_iv_position: u32,
|
||||||
|
_doorbell: u32,
|
||||||
|
|
||||||
|
// PCI configuration registers.
|
||||||
|
configuration: PciConfiguration,
|
||||||
|
bar_regions: Vec<PciBarConfiguration>,
|
||||||
|
|
||||||
|
region_size: u64,
|
||||||
|
ivshmem_ops: Arc<Mutex<dyn IvshmemOps>>,
|
||||||
|
backend_file: Option<PathBuf>,
|
||||||
|
region: Option<Arc<GuestRegionMmap>>,
|
||||||
|
userspace_mapping: Option<UserspaceMapping>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||||
|
pub struct IvshmemDeviceState {
|
||||||
|
interrupt_mask: u32,
|
||||||
|
interrupt_status: u32,
|
||||||
|
iv_position: u32,
|
||||||
|
doorbell: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IvshmemDevice {
|
||||||
|
pub fn new(
|
||||||
|
id: String,
|
||||||
|
region_size: u64,
|
||||||
|
backend_file: Option<PathBuf>,
|
||||||
|
ivshmem_ops: Arc<Mutex<dyn IvshmemOps>>,
|
||||||
|
snapshot: Option<Snapshot>,
|
||||||
|
) -> Result<Self, IvshmemError> {
|
||||||
|
let pci_configuration_state =
|
||||||
|
vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| {
|
||||||
|
IvshmemError::RetrievePciConfigurationState(anyhow!(
|
||||||
|
"Failed to get PciConfigurationState from Snapshot: {e}",
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let state: Option<IvshmemDeviceState> = snapshot
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.to_state())
|
||||||
|
.transpose()
|
||||||
|
.map_err(|e| {
|
||||||
|
IvshmemError::RetrieveIvshmemDeviceStateState(anyhow!(
|
||||||
|
"Failed to get IvshmemDeviceState from Snapshot: {e}",
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let configuration = PciConfiguration::new(
|
||||||
|
IVSHMEM_VENDOR_ID,
|
||||||
|
IVSHMEM_DEVICE_ID,
|
||||||
|
0x1,
|
||||||
|
PciClassCode::MemoryController,
|
||||||
|
&IvshmemSubclass::Other,
|
||||||
|
None,
|
||||||
|
PciHeaderType::Device,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
pci_configuration_state,
|
||||||
|
);
|
||||||
|
|
||||||
|
let device = if let Some(s) = state {
|
||||||
|
IvshmemDevice {
|
||||||
|
id,
|
||||||
|
configuration,
|
||||||
|
bar_regions: vec![],
|
||||||
|
_interrupt_mask: s.interrupt_mask,
|
||||||
|
_interrupt_status: Arc::new(AtomicU32::new(s.interrupt_status)),
|
||||||
|
_iv_position: s.iv_position,
|
||||||
|
_doorbell: s.doorbell,
|
||||||
|
region_size,
|
||||||
|
ivshmem_ops,
|
||||||
|
region: None,
|
||||||
|
userspace_mapping: None,
|
||||||
|
backend_file,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
IvshmemDevice {
|
||||||
|
id,
|
||||||
|
configuration,
|
||||||
|
bar_regions: vec![],
|
||||||
|
_interrupt_mask: 0,
|
||||||
|
_interrupt_status: Arc::new(AtomicU32::new(0)),
|
||||||
|
_iv_position: 0,
|
||||||
|
_doorbell: 0,
|
||||||
|
region_size,
|
||||||
|
ivshmem_ops,
|
||||||
|
region: None,
|
||||||
|
userspace_mapping: None,
|
||||||
|
backend_file,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(device)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_region(
|
||||||
|
&mut self,
|
||||||
|
region: Arc<GuestRegionMmap>,
|
||||||
|
userspace_mapping: UserspaceMapping,
|
||||||
|
) {
|
||||||
|
self.region = Some(region);
|
||||||
|
self.userspace_mapping = Some(userspace_mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config_bar_addr(&self) -> u64 {
|
||||||
|
self.configuration.get_bar_addr(IVSHMEM_BAR0_IDX)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn data_bar_addr(&self) -> u64 {
|
||||||
|
self.configuration.get_bar_addr(IVSHMEM_BAR2_IDX)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> IvshmemDeviceState {
|
||||||
|
IvshmemDeviceState {
|
||||||
|
interrupt_mask: self._interrupt_mask,
|
||||||
|
interrupt_status: self._interrupt_status.load(Ordering::SeqCst),
|
||||||
|
iv_position: self._iv_position,
|
||||||
|
doorbell: self._doorbell,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BusDevice for IvshmemDevice {
|
||||||
|
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>> {
|
||||||
|
self.write_bar(base, offset, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PciDevice for IvshmemDevice {
|
||||||
|
fn allocate_bars(
|
||||||
|
&mut self,
|
||||||
|
_allocator: &Arc<Mutex<SystemAllocator>>,
|
||||||
|
mmio32_allocator: &mut AddressAllocator,
|
||||||
|
mmio64_allocator: &mut AddressAllocator,
|
||||||
|
resources: Option<Vec<Resource>>,
|
||||||
|
) -> std::result::Result<Vec<PciBarConfiguration>, PciDeviceError> {
|
||||||
|
let mut bars = Vec::new();
|
||||||
|
let mut bar0_addr = None;
|
||||||
|
let mut bar2_addr = None;
|
||||||
|
|
||||||
|
let restoring = resources.is_some();
|
||||||
|
if let Some(resources) = resources {
|
||||||
|
for resource in resources {
|
||||||
|
match resource {
|
||||||
|
Resource::PciBar { index, base, .. } => {
|
||||||
|
match index {
|
||||||
|
IVSHMEM_BAR0_IDX => {
|
||||||
|
bar0_addr = Some(GuestAddress(base));
|
||||||
|
}
|
||||||
|
IVSHMEM_BAR1_IDX => {}
|
||||||
|
IVSHMEM_BAR2_IDX => {
|
||||||
|
bar2_addr = Some(GuestAddress(base));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("Unexpected pci bar index {index}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("Unexpected resource {resource:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bar0_addr.is_none() || bar2_addr.is_none() {
|
||||||
|
return Err(PciDeviceError::MissingResource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BAR0 holds device registers (256 Byte MMIO)
|
||||||
|
let bar0_addr = mmio32_allocator
|
||||||
|
.allocate(bar0_addr, IVSHMEM_REG_BAR_SIZE, None)
|
||||||
|
.ok_or(PciDeviceError::IoAllocationFailed(IVSHMEM_REG_BAR_SIZE))?;
|
||||||
|
debug!("ivshmem bar0 address 0x{:x}", bar0_addr.0);
|
||||||
|
|
||||||
|
let bar0 = PciBarConfiguration::default()
|
||||||
|
.set_index(IVSHMEM_BAR0_IDX)
|
||||||
|
.set_address(bar0_addr.raw_value())
|
||||||
|
.set_size(IVSHMEM_REG_BAR_SIZE)
|
||||||
|
.set_region_type(PciBarRegionType::Memory32BitRegion)
|
||||||
|
.set_prefetchable(PciBarPrefetchable::NotPrefetchable);
|
||||||
|
|
||||||
|
// BAR1 holds MSI-X table and PBA (only ivshmem-doorbell).
|
||||||
|
|
||||||
|
// BAR2 maps the shared memory object
|
||||||
|
let bar2_size = self.region_size;
|
||||||
|
let bar2_addr = mmio64_allocator
|
||||||
|
.allocate(bar2_addr, bar2_size, None)
|
||||||
|
.ok_or(PciDeviceError::IoAllocationFailed(bar2_size))?;
|
||||||
|
debug!("ivshmem bar2 address 0x{:x}", bar2_addr.0);
|
||||||
|
|
||||||
|
let bar2 = PciBarConfiguration::default()
|
||||||
|
.set_index(IVSHMEM_BAR2_IDX)
|
||||||
|
.set_address(bar2_addr.raw_value())
|
||||||
|
.set_size(bar2_size)
|
||||||
|
.set_region_type(PciBarRegionType::Memory64BitRegion)
|
||||||
|
.set_prefetchable(PciBarPrefetchable::Prefetchable);
|
||||||
|
|
||||||
|
if !restoring {
|
||||||
|
self.configuration
|
||||||
|
.add_pci_bar(&bar0)
|
||||||
|
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar0_addr.raw_value(), e))?;
|
||||||
|
self.configuration
|
||||||
|
.add_pci_bar(&bar2)
|
||||||
|
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar2_addr.raw_value(), e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
bars.push(bar0);
|
||||||
|
bars.push(bar2);
|
||||||
|
self.bar_regions = bars.clone();
|
||||||
|
|
||||||
|
Ok(bars)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free_bars(
|
||||||
|
&mut self,
|
||||||
|
_allocator: &mut SystemAllocator,
|
||||||
|
_mmio32_allocator: &mut AddressAllocator,
|
||||||
|
_mmio64_allocator: &mut AddressAllocator,
|
||||||
|
) -> std::result::Result<(), PciDeviceError> {
|
||||||
|
unimplemented!("Device hotplug and remove are not supported for ivshmem");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_config_register(
|
||||||
|
&mut self,
|
||||||
|
reg_idx: usize,
|
||||||
|
offset: u64,
|
||||||
|
data: &[u8],
|
||||||
|
) -> (Vec<BarReprogrammingParams>, 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 read_bar(&mut self, base: u64, offset: u64, data: &mut [u8]) {
|
||||||
|
debug!("read base {base:x} offset {offset}");
|
||||||
|
|
||||||
|
let mut bar_idx = 0;
|
||||||
|
for (idx, bar) in self.bar_regions.iter().enumerate() {
|
||||||
|
if bar.addr() == base {
|
||||||
|
bar_idx = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match bar_idx {
|
||||||
|
// bar 0
|
||||||
|
0 => {
|
||||||
|
// ivshmem don't use interrupt, we return zero now.
|
||||||
|
LittleEndian::write_u32(data, 0);
|
||||||
|
}
|
||||||
|
// bar 2
|
||||||
|
1 => warn!("Unexpected read ivshmem memory idx: {offset}"),
|
||||||
|
_ => {
|
||||||
|
warn!("Invalid bar_idx: {bar_idx}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_bar(&mut self, base: u64, offset: u64, _data: &[u8]) -> Option<Arc<Barrier>> {
|
||||||
|
debug!("write base {base:x} offset {offset}");
|
||||||
|
warn!("Unexpected write ivshmem memory idx: {offset}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), std::io::Error> {
|
||||||
|
if new_base == self.data_bar_addr() {
|
||||||
|
if let Some(old_mapping) = self.userspace_mapping.take() {
|
||||||
|
self.ivshmem_ops
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.unmap_ram_region(old_mapping)
|
||||||
|
.map_err(std::io::Error::other)?;
|
||||||
|
}
|
||||||
|
let (region, new_mapping) = self
|
||||||
|
.ivshmem_ops
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.map_ram_region(
|
||||||
|
new_base,
|
||||||
|
self.region_size as usize,
|
||||||
|
self.backend_file.clone(),
|
||||||
|
)
|
||||||
|
.map_err(std::io::Error::other)?;
|
||||||
|
self.set_region(region, new_mapping);
|
||||||
|
}
|
||||||
|
for bar in self.bar_regions.iter_mut() {
|
||||||
|
if bar.addr() == old_base {
|
||||||
|
*bar = bar.set_address(new_base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> Option<String> {
|
||||||
|
Some(self.id.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pausable for IvshmemDevice {}
|
||||||
|
|
||||||
|
impl Snapshottable for IvshmemDevice {
|
||||||
|
fn id(&self) -> String {
|
||||||
|
self.id.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The snapshot/restore (also live migration) support only work for ivshmem-plain mode.
|
||||||
|
// Additional work is needed for supporting ivshmem-doorbell.
|
||||||
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
|
let mut snapshot = Snapshot::new_from_state(&self.state())?;
|
||||||
|
|
||||||
|
// Snapshot PciConfiguration
|
||||||
|
snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
|
||||||
|
|
||||||
|
Ok(snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transportable for IvshmemDevice {}
|
||||||
|
|
||||||
|
impl Migratable for IvshmemDevice {}
|
||||||
@@ -12,7 +12,7 @@ use std::{mem, thread};
|
|||||||
// https://github.com/rust-lang/libc/issues/1848
|
// https://github.com/rust-lang/libc/issues/1848
|
||||||
#[cfg_attr(target_env = "musl", allow(deprecated))]
|
#[cfg_attr(target_env = "musl", allow(deprecated))]
|
||||||
use libc::time_t;
|
use libc::time_t;
|
||||||
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
|
use libc::{CLOCK_REALTIME, clock_gettime, gmtime_r, timespec, tm};
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ impl BusDevice for Cmos {
|
|||||||
0x08 => to_bcd(month as u8),
|
0x08 => to_bcd(month as u8),
|
||||||
0x09 => to_bcd((year % 100) as u8),
|
0x09 => to_bcd((year % 100) as u8),
|
||||||
// Bit 5 for 32kHz clock. Bit 7 for Update in Progress
|
// Bit 5 for 32kHz clock. Bit 7 for Update in Progress
|
||||||
0x0a => 1 << 5 | (update_in_progress as u8) << 7,
|
0x0a => (1 << 5) | ((update_in_progress as u8) << 7),
|
||||||
// Bit 0-6 are reserved and must be 0.
|
// Bit 0-6 are reserved and must be 0.
|
||||||
// Bit 7 must be 1 (CMOS has power)
|
// Bit 7 must be 1 (CMOS has power)
|
||||||
0x0d => 1 << 7,
|
0x0d => 1 << 7,
|
||||||
|
|||||||
954
devices/src/legacy/fw_cfg.rs
Normal file
954
devices/src/legacy/fw_cfg.rs
Normal file
@@ -0,0 +1,954 @@
|
|||||||
|
// Copyright 2025 Google LLC.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
/// Cloud Hypervisor implementation of Qemu's fw_cfg spec
|
||||||
|
/// https://www.qemu.org/docs/master/specs/fw_cfg.html
|
||||||
|
/// Linux kernel fw_cfg driver header
|
||||||
|
/// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h
|
||||||
|
/// Uploading files to the guest via fw_cfg is supported for all kernels 4.6+ w/ CONFIG_FW_CFG_SYSFS enabled
|
||||||
|
/// https://cateee.net/lkddb/web-lkddb/FW_CFG_SYSFS.html
|
||||||
|
/// No kernel requirement if above functionality is not required,
|
||||||
|
/// only firmware must implement mechanism to interact with this fw_cfg device
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{ErrorKind, Read, Result, Seek, SeekFrom},
|
||||||
|
mem::offset_of,
|
||||||
|
os::unix::fs::FileExt,
|
||||||
|
sync::{Arc, Barrier},
|
||||||
|
};
|
||||||
|
|
||||||
|
use acpi_tables::rsdp::Rsdp;
|
||||||
|
use arch::RegionType;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
use arch::aarch64::layout::{
|
||||||
|
MEM_32BIT_DEVICES_START, MEM_32BIT_RESERVED_START, RAM_64BIT_START, RAM_START as HIGH_RAM_START,
|
||||||
|
};
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
use arch::layout::{
|
||||||
|
EBDA_START, HIGH_RAM_START, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START,
|
||||||
|
MEM_32BIT_RESERVED_START, PCI_MMCONFIG_SIZE, PCI_MMCONFIG_START, RAM_64BIT_START,
|
||||||
|
};
|
||||||
|
use bitfield_struct::bitfield;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
use linux_loader::bootparam::boot_params;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
use linux_loader::loader::pe::arm64_image_header as boot_params;
|
||||||
|
use vm_device::BusDevice;
|
||||||
|
use vm_memory::bitmap::AtomicBitmap;
|
||||||
|
use vm_memory::{
|
||||||
|
ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap,
|
||||||
|
};
|
||||||
|
use vmm_sys_util::sock_ctrl_msg::IntoIovec;
|
||||||
|
use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes};
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
// https://github.com/project-oak/oak/tree/main/stage0_bin#memory-layout
|
||||||
|
const STAGE0_START_ADDRESS: GuestAddress = GuestAddress(0xfffe_0000);
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
const STAGE0_SIZE: usize = 0x2_0000;
|
||||||
|
const E820_RAM: u32 = 1;
|
||||||
|
const E820_RESERVED: u32 = 2;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
const PORT_FW_CFG_SELECTOR: u64 = 0x510;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
const PORT_FW_CFG_DATA: u64 = 0x511;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
const PORT_FW_CFG_DMA_HI: u64 = 0x514;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
const PORT_FW_CFG_DMA_LO: u64 = 0x518;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
pub const PORT_FW_CFG_BASE: u64 = 0x510;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
pub const PORT_FW_CFG_WIDTH: u64 = 0xc;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
const PORT_FW_CFG_SELECTOR: u64 = 0x9030008;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
const PORT_FW_CFG_DATA: u64 = 0x9030000;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
const PORT_FW_CFG_DMA_HI: u64 = 0x9030010;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
const PORT_FW_CFG_DMA_LO: u64 = 0x9030014;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
pub const PORT_FW_CFG_BASE: u64 = 0x9030000;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
pub const PORT_FW_CFG_WIDTH: u64 = 0x10;
|
||||||
|
|
||||||
|
const FW_CFG_SIGNATURE: u16 = 0x00;
|
||||||
|
const FW_CFG_ID: u16 = 0x01;
|
||||||
|
const FW_CFG_KERNEL_SIZE: u16 = 0x08;
|
||||||
|
const FW_CFG_INITRD_SIZE: u16 = 0x0b;
|
||||||
|
const FW_CFG_KERNEL_DATA: u16 = 0x11;
|
||||||
|
const FW_CFG_INITRD_DATA: u16 = 0x12;
|
||||||
|
const FW_CFG_CMDLINE_SIZE: u16 = 0x14;
|
||||||
|
const FW_CFG_CMDLINE_DATA: u16 = 0x15;
|
||||||
|
const FW_CFG_SETUP_SIZE: u16 = 0x17;
|
||||||
|
const FW_CFG_SETUP_DATA: u16 = 0x18;
|
||||||
|
const FW_CFG_FILE_DIR: u16 = 0x19;
|
||||||
|
const FW_CFG_KNOWN_ITEMS: usize = 0x20;
|
||||||
|
|
||||||
|
pub const FW_CFG_FILE_FIRST: u16 = 0x20;
|
||||||
|
pub const FW_CFG_DMA_SIGNATURE: [u8; 8] = *b"QEMU CFG";
|
||||||
|
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h
|
||||||
|
pub const FW_CFG_ACPI_ID: &str = "QEMU0002";
|
||||||
|
// Reserved (must be enabled)
|
||||||
|
const FW_CFG_F_RESERVED: u8 = 1 << 0;
|
||||||
|
// DMA Toggle Bit (enabled by default)
|
||||||
|
const FW_CFG_F_DMA: u8 = 1 << 1;
|
||||||
|
pub const FW_CFG_FEATURE: [u8; 4] = [FW_CFG_F_RESERVED | FW_CFG_F_DMA, 0, 0, 0];
|
||||||
|
|
||||||
|
const COMMAND_ALLOCATE: u32 = 0x1;
|
||||||
|
const COMMAND_ADD_POINTER: u32 = 0x2;
|
||||||
|
const COMMAND_ADD_CHECKSUM: u32 = 0x3;
|
||||||
|
|
||||||
|
const ALLOC_ZONE_HIGH: u8 = 0x1;
|
||||||
|
const ALLOC_ZONE_FSEG: u8 = 0x2;
|
||||||
|
|
||||||
|
const FW_CFG_FILENAME_TABLE_LOADER: &str = "etc/table-loader";
|
||||||
|
const FW_CFG_FILENAME_RSDP: &str = "acpi/rsdp";
|
||||||
|
const FW_CFG_FILENAME_ACPI_TABLES: &str = "acpi/tables";
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FwCfgContent {
|
||||||
|
Bytes(Vec<u8>),
|
||||||
|
Slice(&'static [u8]),
|
||||||
|
File(u64, File),
|
||||||
|
U32(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FwCfgContentAccess<'a> {
|
||||||
|
content: &'a FwCfgContent,
|
||||||
|
offset: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for FwCfgContentAccess<'_> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
||||||
|
match self.content {
|
||||||
|
FwCfgContent::File(offset, f) => {
|
||||||
|
Seek::seek(&mut (&*f), SeekFrom::Start(offset + self.offset as u64))?;
|
||||||
|
Read::read(&mut (&*f), buf)
|
||||||
|
}
|
||||||
|
FwCfgContent::Bytes(b) => match b.get(self.offset as usize..) {
|
||||||
|
Some(mut s) => s.read(buf),
|
||||||
|
None => Err(ErrorKind::UnexpectedEof)?,
|
||||||
|
},
|
||||||
|
FwCfgContent::Slice(b) => match b.get(self.offset as usize..) {
|
||||||
|
Some(mut s) => s.read(buf),
|
||||||
|
None => Err(ErrorKind::UnexpectedEof)?,
|
||||||
|
},
|
||||||
|
FwCfgContent::U32(n) => match n.to_le_bytes().get(self.offset as usize..) {
|
||||||
|
Some(mut s) => s.read(buf),
|
||||||
|
None => Err(ErrorKind::UnexpectedEof)?,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FwCfgContent {
|
||||||
|
fn default() -> Self {
|
||||||
|
FwCfgContent::Slice(&[])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FwCfgContent {
|
||||||
|
fn size(&self) -> Result<u32> {
|
||||||
|
let ret = match self {
|
||||||
|
FwCfgContent::Bytes(v) => v.len(),
|
||||||
|
FwCfgContent::File(offset, f) => (f.metadata()?.len() - offset) as usize,
|
||||||
|
FwCfgContent::Slice(s) => s.len(),
|
||||||
|
FwCfgContent::U32(n) => size_of_val(n),
|
||||||
|
};
|
||||||
|
u32::try_from(ret).map_err(|_| std::io::ErrorKind::InvalidInput.into())
|
||||||
|
}
|
||||||
|
fn access(&self, offset: u32) -> FwCfgContentAccess<'_> {
|
||||||
|
FwCfgContentAccess {
|
||||||
|
content: self,
|
||||||
|
offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct FwCfgItem {
|
||||||
|
pub name: String,
|
||||||
|
pub content: FwCfgContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// https://www.qemu.org/docs/master/specs/fw_cfg.html
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FwCfg {
|
||||||
|
selector: u16,
|
||||||
|
data_offset: u32,
|
||||||
|
dma_address: u64,
|
||||||
|
items: Vec<FwCfgItem>, // 0x20 and above
|
||||||
|
known_items: [FwCfgContent; FW_CFG_KNOWN_ITEMS], // 0x0 to 0x19
|
||||||
|
memory: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, IntoBytes, FromBytes)]
|
||||||
|
struct FwCfgDmaAccess {
|
||||||
|
control_be: u32,
|
||||||
|
length_be: u32,
|
||||||
|
address_be: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h#L67
|
||||||
|
#[bitfield(u32)]
|
||||||
|
struct AccessControl {
|
||||||
|
// FW_CFG_DMA_CTL_ERROR = 0x01
|
||||||
|
error: bool,
|
||||||
|
// FW_CFG_DMA_CTL_READ = 0x02
|
||||||
|
read: bool,
|
||||||
|
#[bits(1)]
|
||||||
|
_unused2: u8,
|
||||||
|
// FW_CFG_DMA_CTL_SKIP = 0x04
|
||||||
|
skip: bool,
|
||||||
|
#[bits(3)]
|
||||||
|
_unused3: u8,
|
||||||
|
// FW_CFG_DMA_CTL_ERROR = 0x08
|
||||||
|
select: bool,
|
||||||
|
#[bits(7)]
|
||||||
|
_unused4: u8,
|
||||||
|
// FW_CFG_DMA_CTL_WRITE = 0x10
|
||||||
|
write: bool,
|
||||||
|
#[bits(16)]
|
||||||
|
_unused: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, IntoBytes, FromBytes)]
|
||||||
|
struct FwCfgFilesHeader {
|
||||||
|
count_be: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const FILE_NAME_SIZE: usize = 56;
|
||||||
|
|
||||||
|
pub fn create_file_name(name: &str) -> [u8; FILE_NAME_SIZE] {
|
||||||
|
let mut c_name = [0u8; FILE_NAME_SIZE];
|
||||||
|
let c_len = std::cmp::min(FILE_NAME_SIZE - 1, name.len());
|
||||||
|
c_name[0..c_len].copy_from_slice(&name.as_bytes()[0..c_len]);
|
||||||
|
c_name
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(Debug, IntoBytes, FromBytes, Clone, Copy)]
|
||||||
|
struct BootE820Entry {
|
||||||
|
addr: u64,
|
||||||
|
size: u64,
|
||||||
|
type_: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, IntoBytes, FromBytes)]
|
||||||
|
struct FwCfgFile {
|
||||||
|
size_be: u32,
|
||||||
|
select_be: u16,
|
||||||
|
_reserved: u16,
|
||||||
|
name: [u8; FILE_NAME_SIZE],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C, align(4))]
|
||||||
|
#[derive(Debug, IntoBytes, Immutable)]
|
||||||
|
struct Allocate {
|
||||||
|
command: u32,
|
||||||
|
file: [u8; FILE_NAME_SIZE],
|
||||||
|
align: u32,
|
||||||
|
zone: u8,
|
||||||
|
_pad: [u8; 63],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C, align(4))]
|
||||||
|
#[derive(Debug, IntoBytes, Immutable)]
|
||||||
|
struct AddPointer {
|
||||||
|
command: u32,
|
||||||
|
dst: [u8; FILE_NAME_SIZE],
|
||||||
|
src: [u8; FILE_NAME_SIZE],
|
||||||
|
offset: u32,
|
||||||
|
size: u8,
|
||||||
|
_pad: [u8; 7],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C, align(4))]
|
||||||
|
#[derive(Debug, IntoBytes, Immutable)]
|
||||||
|
struct AddChecksum {
|
||||||
|
command: u32,
|
||||||
|
file: [u8; FILE_NAME_SIZE],
|
||||||
|
offset: u32,
|
||||||
|
start: u32,
|
||||||
|
len: u32,
|
||||||
|
_pad: [u8; 56],
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_intra_pointer(name: &str, offset: usize, size: u8) -> AddPointer {
|
||||||
|
AddPointer {
|
||||||
|
command: COMMAND_ADD_POINTER,
|
||||||
|
dst: create_file_name(name),
|
||||||
|
src: create_file_name(name),
|
||||||
|
offset: offset as u32,
|
||||||
|
size,
|
||||||
|
_pad: [0; 7],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_acpi_table_checksum(offset: usize, len: usize) -> AddChecksum {
|
||||||
|
AddChecksum {
|
||||||
|
command: COMMAND_ADD_CHECKSUM,
|
||||||
|
file: create_file_name(FW_CFG_FILENAME_ACPI_TABLES),
|
||||||
|
offset: (offset + offset_of!(AcpiTableHeader, checksum)) as u32,
|
||||||
|
start: offset as u32,
|
||||||
|
len: len as u32,
|
||||||
|
_pad: [0; 56],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C, align(4))]
|
||||||
|
#[derive(Debug, Clone, Default, FromBytes, IntoBytes)]
|
||||||
|
struct AcpiTableHeader {
|
||||||
|
signature: [u8; 4],
|
||||||
|
length: u32,
|
||||||
|
revision: u8,
|
||||||
|
checksum: u8,
|
||||||
|
oem_id: [u8; 6],
|
||||||
|
oem_table_id: [u8; 8],
|
||||||
|
oem_revision: u32,
|
||||||
|
asl_compiler_id: [u8; 4],
|
||||||
|
asl_compiler_revision: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AcpiTable {
|
||||||
|
rsdp: Rsdp,
|
||||||
|
tables: Vec<u8>,
|
||||||
|
table_pointers: Vec<usize>,
|
||||||
|
table_checksums: Vec<(usize, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AcpiTable {
|
||||||
|
fn pointers(&self) -> &[usize] {
|
||||||
|
&self.table_pointers
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checksums(&self) -> &[(usize, usize)] {
|
||||||
|
&self.table_checksums
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take(self) -> (Rsdp, Vec<u8>) {
|
||||||
|
(self.rsdp, self.tables)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates fw_cfg items used by firmware to load and verify Acpi tables
|
||||||
|
// https://github.com/qemu/qemu/blob/master/hw/acpi/bios-linker-loader.c
|
||||||
|
fn create_acpi_loader(acpi_table: AcpiTable) -> [FwCfgItem; 3] {
|
||||||
|
let mut table_loader_bytes: Vec<u8> = Vec::new();
|
||||||
|
let allocate_rsdp = Allocate {
|
||||||
|
command: COMMAND_ALLOCATE,
|
||||||
|
file: create_file_name(FW_CFG_FILENAME_RSDP),
|
||||||
|
align: 4,
|
||||||
|
zone: ALLOC_ZONE_FSEG,
|
||||||
|
_pad: [0; 63],
|
||||||
|
};
|
||||||
|
table_loader_bytes.extend(allocate_rsdp.as_bytes());
|
||||||
|
|
||||||
|
let allocate_tables = Allocate {
|
||||||
|
command: COMMAND_ALLOCATE,
|
||||||
|
file: create_file_name(FW_CFG_FILENAME_ACPI_TABLES),
|
||||||
|
align: 4,
|
||||||
|
zone: ALLOC_ZONE_HIGH,
|
||||||
|
_pad: [0; 63],
|
||||||
|
};
|
||||||
|
table_loader_bytes.extend(allocate_tables.as_bytes());
|
||||||
|
|
||||||
|
for pointer_offset in acpi_table.pointers().iter() {
|
||||||
|
let pointer = create_intra_pointer(FW_CFG_FILENAME_ACPI_TABLES, *pointer_offset, 8);
|
||||||
|
table_loader_bytes.extend(pointer.as_bytes());
|
||||||
|
}
|
||||||
|
for (offset, len) in acpi_table.checksums().iter() {
|
||||||
|
let checksum = create_acpi_table_checksum(*offset, *len);
|
||||||
|
table_loader_bytes.extend(checksum.as_bytes());
|
||||||
|
}
|
||||||
|
let pointer_rsdp_to_xsdt = AddPointer {
|
||||||
|
command: COMMAND_ADD_POINTER,
|
||||||
|
dst: create_file_name(FW_CFG_FILENAME_RSDP),
|
||||||
|
src: create_file_name(FW_CFG_FILENAME_ACPI_TABLES),
|
||||||
|
offset: offset_of!(Rsdp, xsdt_addr) as u32,
|
||||||
|
size: 8,
|
||||||
|
_pad: [0; 7],
|
||||||
|
};
|
||||||
|
table_loader_bytes.extend(pointer_rsdp_to_xsdt.as_bytes());
|
||||||
|
let checksum_rsdp = AddChecksum {
|
||||||
|
command: COMMAND_ADD_CHECKSUM,
|
||||||
|
file: create_file_name(FW_CFG_FILENAME_RSDP),
|
||||||
|
offset: offset_of!(Rsdp, checksum) as u32,
|
||||||
|
start: 0,
|
||||||
|
len: offset_of!(Rsdp, length) as u32,
|
||||||
|
_pad: [0; 56],
|
||||||
|
};
|
||||||
|
let checksum_rsdp_ext = AddChecksum {
|
||||||
|
command: COMMAND_ADD_CHECKSUM,
|
||||||
|
file: create_file_name(FW_CFG_FILENAME_RSDP),
|
||||||
|
offset: offset_of!(Rsdp, extended_checksum) as u32,
|
||||||
|
start: 0,
|
||||||
|
len: size_of::<Rsdp>() as u32,
|
||||||
|
_pad: [0; 56],
|
||||||
|
};
|
||||||
|
table_loader_bytes.extend(checksum_rsdp.as_bytes());
|
||||||
|
table_loader_bytes.extend(checksum_rsdp_ext.as_bytes());
|
||||||
|
|
||||||
|
let table_loader = FwCfgItem {
|
||||||
|
name: FW_CFG_FILENAME_TABLE_LOADER.to_owned(),
|
||||||
|
content: FwCfgContent::Bytes(table_loader_bytes),
|
||||||
|
};
|
||||||
|
let (rsdp, tables) = acpi_table.take();
|
||||||
|
let acpi_rsdp = FwCfgItem {
|
||||||
|
name: FW_CFG_FILENAME_RSDP.to_owned(),
|
||||||
|
content: FwCfgContent::Bytes(rsdp.as_bytes().to_owned()),
|
||||||
|
};
|
||||||
|
let apci_tables = FwCfgItem {
|
||||||
|
name: FW_CFG_FILENAME_ACPI_TABLES.to_owned(),
|
||||||
|
content: FwCfgContent::Bytes(tables),
|
||||||
|
};
|
||||||
|
[table_loader, acpi_rsdp, apci_tables]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FwCfg {
|
||||||
|
pub fn new(memory: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>) -> FwCfg {
|
||||||
|
const DEFAULT_ITEM: FwCfgContent = FwCfgContent::Slice(&[]);
|
||||||
|
let mut known_items = [DEFAULT_ITEM; FW_CFG_KNOWN_ITEMS];
|
||||||
|
known_items[FW_CFG_SIGNATURE as usize] = FwCfgContent::Slice(&FW_CFG_DMA_SIGNATURE);
|
||||||
|
known_items[FW_CFG_ID as usize] = FwCfgContent::Slice(&FW_CFG_FEATURE);
|
||||||
|
let file_buf = Vec::from(FwCfgFilesHeader { count_be: 0 }.as_mut_bytes());
|
||||||
|
known_items[FW_CFG_FILE_DIR as usize] = FwCfgContent::Bytes(file_buf);
|
||||||
|
|
||||||
|
FwCfg {
|
||||||
|
selector: 0,
|
||||||
|
data_offset: 0,
|
||||||
|
dma_address: 0,
|
||||||
|
items: vec![],
|
||||||
|
known_items,
|
||||||
|
memory,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn populate_fw_cfg(
|
||||||
|
&mut self,
|
||||||
|
mem_size: Option<usize>,
|
||||||
|
kernel: Option<File>,
|
||||||
|
initramfs: Option<File>,
|
||||||
|
cmdline: Option<std::ffi::CString>,
|
||||||
|
fw_cfg_item_list: Option<Vec<FwCfgItem>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Some(mem_size) = mem_size {
|
||||||
|
self.add_e820(mem_size)?
|
||||||
|
}
|
||||||
|
if let Some(kernel) = kernel {
|
||||||
|
self.add_kernel_data(&kernel)?;
|
||||||
|
}
|
||||||
|
if let Some(cmdline) = cmdline {
|
||||||
|
self.add_kernel_cmdline(cmdline);
|
||||||
|
}
|
||||||
|
if let Some(initramfs) = initramfs {
|
||||||
|
self.add_initramfs_data(&initramfs)?
|
||||||
|
}
|
||||||
|
if let Some(fw_cfg_item_list) = fw_cfg_item_list {
|
||||||
|
for item in fw_cfg_item_list {
|
||||||
|
self.add_item(item)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_e820(&mut self, mem_size: usize) -> Result<()> {
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
let mut mem_regions = vec![
|
||||||
|
(GuestAddress(0), EBDA_START.0 as usize, RegionType::Ram),
|
||||||
|
(
|
||||||
|
MEM_32BIT_DEVICES_START,
|
||||||
|
MEM_32BIT_DEVICES_SIZE as usize,
|
||||||
|
RegionType::Reserved,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
PCI_MMCONFIG_START,
|
||||||
|
PCI_MMCONFIG_SIZE as usize,
|
||||||
|
RegionType::Reserved,
|
||||||
|
),
|
||||||
|
(STAGE0_START_ADDRESS, STAGE0_SIZE, RegionType::Reserved),
|
||||||
|
];
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
let mut mem_regions = arch::aarch64::arch_memory_regions();
|
||||||
|
if mem_size < MEM_32BIT_DEVICES_START.0 as usize {
|
||||||
|
mem_regions.push((
|
||||||
|
HIGH_RAM_START,
|
||||||
|
mem_size - HIGH_RAM_START.0 as usize,
|
||||||
|
RegionType::Ram,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
mem_regions.push((
|
||||||
|
HIGH_RAM_START,
|
||||||
|
MEM_32BIT_RESERVED_START.0 as usize - HIGH_RAM_START.0 as usize,
|
||||||
|
RegionType::Ram,
|
||||||
|
));
|
||||||
|
mem_regions.push((
|
||||||
|
RAM_64BIT_START,
|
||||||
|
mem_size - (MEM_32BIT_DEVICES_START.0 as usize),
|
||||||
|
RegionType::Ram,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut bytes = vec![];
|
||||||
|
for (addr, size, region) in mem_regions.iter() {
|
||||||
|
let type_ = match region {
|
||||||
|
RegionType::Ram => E820_RAM,
|
||||||
|
RegionType::Reserved => E820_RESERVED,
|
||||||
|
RegionType::SubRegion => continue,
|
||||||
|
};
|
||||||
|
let mut entry = BootE820Entry {
|
||||||
|
addr: addr.0,
|
||||||
|
size: *size as u64,
|
||||||
|
type_,
|
||||||
|
};
|
||||||
|
bytes.extend_from_slice(entry.as_mut_bytes());
|
||||||
|
}
|
||||||
|
let item = FwCfgItem {
|
||||||
|
name: "etc/e820".to_owned(),
|
||||||
|
content: FwCfgContent::Bytes(bytes),
|
||||||
|
};
|
||||||
|
self.add_item(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_dir_mut(&mut self) -> &mut Vec<u8> {
|
||||||
|
let FwCfgContent::Bytes(file_buf) = &mut self.known_items[FW_CFG_FILE_DIR as usize] else {
|
||||||
|
unreachable!("fw_cfg: selector {FW_CFG_FILE_DIR:#x} should be FwCfgContent::Byte!")
|
||||||
|
};
|
||||||
|
file_buf
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_count(&mut self) {
|
||||||
|
let mut header = FwCfgFilesHeader {
|
||||||
|
count_be: (self.items.len() as u32).to_be(),
|
||||||
|
};
|
||||||
|
self.file_dir_mut()[0..4].copy_from_slice(header.as_mut_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_item(&mut self, item: FwCfgItem) -> Result<()> {
|
||||||
|
let index = self.items.len();
|
||||||
|
let c_name = create_file_name(&item.name);
|
||||||
|
let size = item.content.size()?;
|
||||||
|
let mut cfg_file = FwCfgFile {
|
||||||
|
size_be: size.to_be(),
|
||||||
|
select_be: (FW_CFG_FILE_FIRST + index as u16).to_be(),
|
||||||
|
_reserved: 0,
|
||||||
|
name: c_name,
|
||||||
|
};
|
||||||
|
self.file_dir_mut()
|
||||||
|
.extend_from_slice(cfg_file.as_mut_bytes());
|
||||||
|
self.items.push(item);
|
||||||
|
self.update_count();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dma_read_content(
|
||||||
|
&self,
|
||||||
|
content: &FwCfgContent,
|
||||||
|
offset: u32,
|
||||||
|
len: u32,
|
||||||
|
address: u64,
|
||||||
|
) -> Result<u32> {
|
||||||
|
let content_size = content.size()?.saturating_sub(offset);
|
||||||
|
let op_size = std::cmp::min(content_size, len);
|
||||||
|
let mut access = content.access(offset);
|
||||||
|
let mut buf = vec![0u8; op_size as usize];
|
||||||
|
access.read_exact(buf.as_mut_bytes())?;
|
||||||
|
let r = self
|
||||||
|
.memory
|
||||||
|
.memory()
|
||||||
|
.write(buf.as_bytes(), GuestAddress(address));
|
||||||
|
match r {
|
||||||
|
Err(e) => {
|
||||||
|
error!("fw_cfg: dma read error: {e:x?}");
|
||||||
|
Err(ErrorKind::InvalidInput.into())
|
||||||
|
}
|
||||||
|
Ok(size) => Ok(size as u32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dma_read(&mut self, selector: u16, len: u32, address: u64) -> Result<()> {
|
||||||
|
let op_size = if let Some(content) = self.known_items.get(selector as usize) {
|
||||||
|
self.dma_read_content(content, self.data_offset, len, address)
|
||||||
|
} else if let Some(item) = self.items.get((selector - FW_CFG_FILE_FIRST) as usize) {
|
||||||
|
self.dma_read_content(&item.content, self.data_offset, len, address)
|
||||||
|
} else {
|
||||||
|
error!("fw_cfg: selector {selector:#x} does not exist.");
|
||||||
|
Err(ErrorKind::NotFound.into())
|
||||||
|
}?;
|
||||||
|
self.data_offset += op_size;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_dma(&mut self) {
|
||||||
|
let dma_address = self.dma_address;
|
||||||
|
let mut access = FwCfgDmaAccess::new_zeroed();
|
||||||
|
let dma_access = match self
|
||||||
|
.memory
|
||||||
|
.memory()
|
||||||
|
.read(access.as_mut_bytes(), GuestAddress(dma_address))
|
||||||
|
{
|
||||||
|
Ok(_) => access,
|
||||||
|
Err(e) => {
|
||||||
|
error!("fw_cfg: invalid address of dma access {dma_address:#x}: {e:?}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let control = AccessControl(u32::from_be(dma_access.control_be));
|
||||||
|
if control.select() {
|
||||||
|
self.selector = control.select() as u16;
|
||||||
|
}
|
||||||
|
let len = u32::from_be(dma_access.length_be);
|
||||||
|
let addr = u64::from_be(dma_access.address_be);
|
||||||
|
let ret = if control.read() {
|
||||||
|
self.dma_read(self.selector, len, addr)
|
||||||
|
} else if control.write() {
|
||||||
|
Err(ErrorKind::InvalidInput.into())
|
||||||
|
} else if control.skip() {
|
||||||
|
self.data_offset += len;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ErrorKind::InvalidData.into())
|
||||||
|
};
|
||||||
|
let mut access_resp = AccessControl(0);
|
||||||
|
if let Err(e) = ret {
|
||||||
|
error!("fw_cfg: dma operation {dma_access:x?}: {e:x?}");
|
||||||
|
access_resp.set_error(true);
|
||||||
|
}
|
||||||
|
if let Err(e) = self.memory.memory().write(
|
||||||
|
&access_resp.0.to_be_bytes(),
|
||||||
|
GuestAddress(dma_address + core::mem::offset_of!(FwCfgDmaAccess, control_be) as u64),
|
||||||
|
) {
|
||||||
|
error!("fw_cfg: finishing dma: {e:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_kernel_data(&mut self, file: &File) -> Result<()> {
|
||||||
|
let mut buffer = vec![0u8; size_of::<boot_params>()];
|
||||||
|
file.read_exact_at(&mut buffer, 0)?;
|
||||||
|
let bp = boot_params::from_mut_slice(&mut buffer).unwrap();
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
{
|
||||||
|
// must set to 4 for backwards compatibility
|
||||||
|
// https://docs.kernel.org/arch/x86/boot.html#the-real-mode-kernel-header
|
||||||
|
if bp.hdr.setup_sects == 0 {
|
||||||
|
bp.hdr.setup_sects = 4;
|
||||||
|
}
|
||||||
|
// wildcard boot loader type
|
||||||
|
bp.hdr.type_of_loader = 0xff;
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
let kernel_start = bp.text_offset;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
let kernel_start = (bp.hdr.setup_sects as usize + 1) * 512;
|
||||||
|
self.known_items[FW_CFG_SETUP_SIZE as usize] = FwCfgContent::U32(buffer.len() as u32);
|
||||||
|
self.known_items[FW_CFG_SETUP_DATA as usize] = FwCfgContent::Bytes(buffer);
|
||||||
|
self.known_items[FW_CFG_KERNEL_SIZE as usize] =
|
||||||
|
FwCfgContent::U32(file.metadata()?.len() as u32 - kernel_start as u32);
|
||||||
|
self.known_items[FW_CFG_KERNEL_DATA as usize] =
|
||||||
|
FwCfgContent::File(kernel_start as u64, file.try_clone()?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_kernel_cmdline(&mut self, s: std::ffi::CString) {
|
||||||
|
let bytes = s.into_bytes_with_nul();
|
||||||
|
self.known_items[FW_CFG_CMDLINE_SIZE as usize] = FwCfgContent::U32(bytes.len() as u32);
|
||||||
|
self.known_items[FW_CFG_CMDLINE_DATA as usize] = FwCfgContent::Bytes(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_acpi(
|
||||||
|
&mut self,
|
||||||
|
rsdp: Rsdp,
|
||||||
|
tables: Vec<u8>,
|
||||||
|
table_checksums: Vec<(usize, usize)>,
|
||||||
|
table_pointers: Vec<usize>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let acpi_table = AcpiTable {
|
||||||
|
rsdp,
|
||||||
|
tables,
|
||||||
|
table_checksums,
|
||||||
|
table_pointers,
|
||||||
|
};
|
||||||
|
let [table_loader, acpi_rsdp, apci_tables] = create_acpi_loader(acpi_table);
|
||||||
|
self.add_item(table_loader)?;
|
||||||
|
self.add_item(acpi_rsdp)?;
|
||||||
|
self.add_item(apci_tables)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_initramfs_data(&mut self, file: &File) -> Result<()> {
|
||||||
|
let initramfs_size = file.metadata()?.len();
|
||||||
|
self.known_items[FW_CFG_INITRD_SIZE as usize] = FwCfgContent::U32(initramfs_size as _);
|
||||||
|
self.known_items[FW_CFG_INITRD_DATA as usize] = FwCfgContent::File(0, file.try_clone()?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_content(content: &FwCfgContent, offset: u32, data: &mut [u8], size: u32) -> Option<u8> {
|
||||||
|
let start = offset as usize;
|
||||||
|
let end = start + size as usize;
|
||||||
|
match content {
|
||||||
|
FwCfgContent::Bytes(b) => {
|
||||||
|
if b.len() >= size as usize {
|
||||||
|
data.copy_from_slice(&b[start..end]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FwCfgContent::Slice(s) => {
|
||||||
|
if s.len() >= size as usize {
|
||||||
|
data.copy_from_slice(&s[start..end]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FwCfgContent::File(o, f) => {
|
||||||
|
f.read_exact_at(data, o + offset as u64).ok()?;
|
||||||
|
}
|
||||||
|
FwCfgContent::U32(n) => {
|
||||||
|
let bytes = n.to_le_bytes();
|
||||||
|
data.copy_from_slice(&bytes[start..end]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some(size as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_data(&mut self, data: &mut [u8], size: u32) -> u8 {
|
||||||
|
let ret = if let Some(content) = self.known_items.get(self.selector as usize) {
|
||||||
|
Self::read_content(content, self.data_offset, data, size)
|
||||||
|
} else if let Some(item) = self.items.get((self.selector - FW_CFG_FILE_FIRST) as usize) {
|
||||||
|
Self::read_content(&item.content, self.data_offset, data, size)
|
||||||
|
} else {
|
||||||
|
error!("fw_cfg: selector {:#x} does not exist.", self.selector);
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if let Some(val) = ret {
|
||||||
|
self.data_offset += size;
|
||||||
|
val
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BusDevice for FwCfg {
|
||||||
|
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||||
|
let port = offset + PORT_FW_CFG_BASE;
|
||||||
|
let size = data.len();
|
||||||
|
match (port, size) {
|
||||||
|
(PORT_FW_CFG_SELECTOR, _) => {
|
||||||
|
error!("fw_cfg: selector register is write-only.");
|
||||||
|
}
|
||||||
|
(PORT_FW_CFG_DATA, _) => _ = self.read_data(data, size as u32),
|
||||||
|
(PORT_FW_CFG_DMA_HI, 4) => {
|
||||||
|
let addr = self.dma_address;
|
||||||
|
let addr_hi = (addr >> 32) as u32;
|
||||||
|
data.copy_from_slice(&addr_hi.to_be_bytes());
|
||||||
|
}
|
||||||
|
(PORT_FW_CFG_DMA_LO, 4) => {
|
||||||
|
let addr = self.dma_address;
|
||||||
|
let addr_lo = (addr & 0xffff_ffff) as u32;
|
||||||
|
data.copy_from_slice(&addr_lo.to_be_bytes());
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug!(
|
||||||
|
"fw_cfg: read from unknown port {port:#x}: {size:#x} bytes and offset {offset:#x}."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||||
|
let port = offset + PORT_FW_CFG_BASE;
|
||||||
|
let size = data.size();
|
||||||
|
match (port, size) {
|
||||||
|
(PORT_FW_CFG_SELECTOR, 2) => {
|
||||||
|
let mut buf = [0u8; 2];
|
||||||
|
buf[..size].copy_from_slice(&data[..size]);
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
let val = u16::from_le_bytes(buf);
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
let val = u16::from_be_bytes(buf);
|
||||||
|
self.selector = val;
|
||||||
|
self.data_offset = 0;
|
||||||
|
}
|
||||||
|
(PORT_FW_CFG_DATA, 1) => error!("fw_cfg: data register is read-only."),
|
||||||
|
(PORT_FW_CFG_DMA_HI, 4) => {
|
||||||
|
let mut buf = [0u8; 4];
|
||||||
|
buf[..size].copy_from_slice(&data[..size]);
|
||||||
|
let val = u32::from_be_bytes(buf);
|
||||||
|
self.dma_address &= 0xffff_ffff;
|
||||||
|
self.dma_address |= (val as u64) << 32;
|
||||||
|
}
|
||||||
|
(PORT_FW_CFG_DMA_LO, 4) => {
|
||||||
|
let mut buf = [0u8; 4];
|
||||||
|
buf[..size].copy_from_slice(&data[..size]);
|
||||||
|
let val = u32::from_be_bytes(buf);
|
||||||
|
self.dma_address &= !0xffff_ffff;
|
||||||
|
self.dma_address |= val as u64;
|
||||||
|
self.do_dma();
|
||||||
|
}
|
||||||
|
_ => debug!(
|
||||||
|
"fw_cfg: write to unknown port {port:#x}: {size:#x} bytes and offset {offset:#x} ."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use vmm_sys_util::tempfile::TempFile;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
const SELECTOR_OFFSET: u64 = 0;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
const SELECTOR_OFFSET: u64 = 8;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
const DATA_OFFSET: u64 = 1;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
const DATA_OFFSET: u64 = 0;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
const DMA_OFFSET: u64 = 4;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
const DMA_OFFSET: u64 = 16;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_signature() {
|
||||||
|
let gm = GuestMemoryAtomic::new(
|
||||||
|
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut fw_cfg = FwCfg::new(gm);
|
||||||
|
|
||||||
|
let mut data = vec![0u8];
|
||||||
|
|
||||||
|
let mut sig_iter = FW_CFG_DMA_SIGNATURE.into_iter();
|
||||||
|
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_SIGNATURE as u8, 0]);
|
||||||
|
loop {
|
||||||
|
if let Some(char) = sig_iter.next() {
|
||||||
|
fw_cfg.read(0, DATA_OFFSET, &mut data);
|
||||||
|
assert_eq!(data[0], char);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_kernel_cmdline() {
|
||||||
|
let gm = GuestMemoryAtomic::new(
|
||||||
|
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut fw_cfg = FwCfg::new(gm);
|
||||||
|
|
||||||
|
let cmdline = *b"cmdline\0";
|
||||||
|
|
||||||
|
fw_cfg.add_kernel_cmdline(CString::from_vec_with_nul(cmdline.to_vec()).unwrap());
|
||||||
|
|
||||||
|
let mut data = vec![0u8];
|
||||||
|
|
||||||
|
let mut cmdline_iter = cmdline.into_iter();
|
||||||
|
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_CMDLINE_DATA as u8, 0]);
|
||||||
|
loop {
|
||||||
|
if let Some(char) = cmdline_iter.next() {
|
||||||
|
fw_cfg.read(0, DATA_OFFSET, &mut data);
|
||||||
|
assert_eq!(data[0], char);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_initram_fs() {
|
||||||
|
let gm = GuestMemoryAtomic::new(
|
||||||
|
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut fw_cfg = FwCfg::new(gm);
|
||||||
|
|
||||||
|
let temp = TempFile::new().unwrap();
|
||||||
|
let mut temp_file = temp.as_file();
|
||||||
|
|
||||||
|
let initram_content = b"this is the initramfs";
|
||||||
|
let written = temp_file.write(initram_content);
|
||||||
|
assert_eq!(written.unwrap(), 21);
|
||||||
|
let _ = fw_cfg.add_initramfs_data(temp_file);
|
||||||
|
|
||||||
|
let mut data = vec![0u8];
|
||||||
|
|
||||||
|
let mut initram_iter = (*initram_content).into_iter();
|
||||||
|
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_INITRD_DATA as u8, 0]);
|
||||||
|
loop {
|
||||||
|
if let Some(char) = initram_iter.next() {
|
||||||
|
fw_cfg.read(0, DATA_OFFSET, &mut data);
|
||||||
|
assert_eq!(data[0], char);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dma() {
|
||||||
|
let code = [
|
||||||
|
0xba, 0xf8, 0x03, 0x00, 0xd8, 0x04, b'0', 0xee, 0xb0, b'\n', 0xee, 0xf4,
|
||||||
|
];
|
||||||
|
|
||||||
|
let content = FwCfgContent::Bytes(code.to_vec());
|
||||||
|
|
||||||
|
let mem_size = 0x1000;
|
||||||
|
let load_addr = GuestAddress(0x1000);
|
||||||
|
let mem: GuestMemoryMmap<AtomicBitmap> =
|
||||||
|
GuestMemoryMmap::from_ranges(&[(load_addr, mem_size)]).unwrap();
|
||||||
|
|
||||||
|
// Note: In firmware we would just allocate FwCfgDmaAccess struct
|
||||||
|
// and use address of struct (&) as dma address
|
||||||
|
let mut access_control = AccessControl(0);
|
||||||
|
// bit 1 = read access
|
||||||
|
access_control.set_read(true);
|
||||||
|
// length of data to access
|
||||||
|
let length_be = (code.len() as u32).to_be();
|
||||||
|
// guest address for data
|
||||||
|
let code_address = 0x1900_u64;
|
||||||
|
let address_be = code_address.to_be();
|
||||||
|
let mut access = FwCfgDmaAccess {
|
||||||
|
control_be: access_control.0.to_be(), // bit(1) = read bit
|
||||||
|
length_be,
|
||||||
|
address_be,
|
||||||
|
};
|
||||||
|
// access address is where to put the code
|
||||||
|
let access_address = GuestAddress(load_addr.0);
|
||||||
|
let address_bytes = access_address.0.to_be_bytes();
|
||||||
|
let dma_lo: [u8; 4] = address_bytes[0..4].try_into().unwrap();
|
||||||
|
let dma_hi: [u8; 4] = address_bytes[4..8].try_into().unwrap();
|
||||||
|
|
||||||
|
// writing the FwCfgDmaAccess to mem (this would just be self.dma_access.as_ref() in guest)
|
||||||
|
let _ = mem.write(access.as_mut_bytes(), access_address);
|
||||||
|
let mem_m = GuestMemoryAtomic::new(mem.clone());
|
||||||
|
let mut fw_cfg = FwCfg::new(mem_m);
|
||||||
|
let cfg_item = FwCfgItem {
|
||||||
|
name: "code".to_string(),
|
||||||
|
content,
|
||||||
|
};
|
||||||
|
let _ = fw_cfg.add_item(cfg_item);
|
||||||
|
|
||||||
|
let mut data = [0u8; 12];
|
||||||
|
|
||||||
|
let _ = mem.read(&mut data, GuestAddress(code_address));
|
||||||
|
assert_ne!(data, code);
|
||||||
|
|
||||||
|
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_FILE_FIRST as u8, 0]);
|
||||||
|
fw_cfg.write(0, DMA_OFFSET, &dma_lo);
|
||||||
|
fw_cfg.write(0, DMA_OFFSET + 4, &dma_hi);
|
||||||
|
let _ = mem.read(&mut data, GuestAddress(code_address));
|
||||||
|
assert_eq!(data, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,8 +12,8 @@ use std::{io, result};
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
|
|
||||||
use crate::{read_le_u32, write_le_u32};
|
use crate::{read_le_u32, write_le_u32};
|
||||||
@@ -28,10 +28,10 @@ const GPIORIE: u64 = 0x414; // Raw Interrupt Status Register
|
|||||||
const GPIOMIS: u64 = 0x418; // Masked Interrupt Status Register
|
const GPIOMIS: u64 = 0x418; // Masked Interrupt Status Register
|
||||||
const GPIOIC: u64 = 0x41c; // Interrupt Clear Register
|
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.
|
||||||
// These 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
|
||||||
const GPIO_ID_LOW: u64 = 0xfe0;
|
const GPIO_ID_LOW: u64 = 0xfe0;
|
||||||
@@ -43,11 +43,11 @@ const N_GPIOS: u32 = 8;
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Bad Write Offset: {0}")]
|
#[error("Bad Write Offset: {0}")]
|
||||||
BadWriteOffset(u64),
|
BadWriteOffset(u64),
|
||||||
#[error("GPIO interrupt disabled by guest driver.")]
|
#[error("GPIO interrupt disabled by guest driver")]
|
||||||
GpioInterruptDisabled,
|
GpioInterruptDisabled,
|
||||||
#[error("Could not trigger GPIO interrupt: {0}.")]
|
#[error("Could not trigger GPIO interrupt")]
|
||||||
GpioInterruptFailure(io::Error),
|
GpioInterruptFailure(#[source] io::Error),
|
||||||
#[error("Invalid GPIO Input key triggered: {0}.")]
|
#[error("Invalid GPIO Input key triggered: {0}")]
|
||||||
GpioTriggerKeyFailure(u32),
|
GpioTriggerKeyFailure(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
mod cmos;
|
mod cmos;
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
mod debug_port;
|
mod debug_port;
|
||||||
|
#[cfg(feature = "fw_cfg")]
|
||||||
|
pub mod fw_cfg;
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
mod fwdebug;
|
mod fwdebug;
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
@@ -22,6 +24,8 @@ mod uart_pl011;
|
|||||||
pub use self::cmos::Cmos;
|
pub use self::cmos::Cmos;
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub use self::debug_port::DebugPort;
|
pub use self::debug_port::DebugPort;
|
||||||
|
#[cfg(feature = "fw_cfg")]
|
||||||
|
pub use self::fw_cfg::FwCfg;
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub use self::fwdebug::FwDebugDevice;
|
pub use self::fwdebug::FwDebugDevice;
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
|||||||
@@ -4,16 +4,18 @@
|
|||||||
|
|
||||||
//! ARM PL031 Real Time Clock
|
//! ARM PL031 Real Time Clock
|
||||||
//!
|
//!
|
||||||
//! This module implements a PL031 Real Time Clock (RTC) that provides to provides long time base counter.
|
//! This module implements part of a PL031 Real Time Clock (RTC):
|
||||||
//! This is achieved by generating an interrupt signal after counting for a programmed number of cycles of
|
//! * provide a clock value via RTCDR
|
||||||
//! a real-time clock input.
|
//! * no alarm is implemented through the match register
|
||||||
|
//! * no interrupt is generated
|
||||||
|
//! * RTC cannot be disabled via RTCCR
|
||||||
|
//! * no test registers
|
||||||
//!
|
//!
|
||||||
|
use std::result;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{io, result};
|
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
|
||||||
use crate::{read_le_u32, write_le_u32};
|
use crate::{read_le_u32, write_le_u32};
|
||||||
@@ -29,11 +31,11 @@ const RTCIMSC: u64 = 0x10; // Interrupt Mask Set or Clear Register.
|
|||||||
const RTCRIS: u64 = 0x14; // Raw Interrupt Status.
|
const RTCRIS: u64 = 0x14; // Raw Interrupt Status.
|
||||||
const RTCMIS: u64 = 0x18; // Masked Interrupt Status.
|
const RTCMIS: u64 = 0x18; // Masked Interrupt Status.
|
||||||
const RTCICR: u64 = 0x1c; // Interrupt Clear Register.
|
const RTCICR: u64 = 0x1c; // Interrupt Clear Register.
|
||||||
// From 0x020 to 0xFDC => reserved space.
|
// From 0x020 to 0xFDC => reserved space.
|
||||||
// From 0xFE0 to 0x1000 => Peripheral and PrimeCell Identification Registers which are Read Only registers.
|
// From 0xFE0 to 0x1000 => Peripheral and PrimeCell Identification Registers which are Read Only registers.
|
||||||
// AMBA standard devices have CIDs (Cell IDs) and PIDs (Peripheral IDs). The linux kernel will look for these in order to assert the identity
|
// AMBA standard devices have CIDs (Cell IDs) and PIDs (Peripheral IDs). The linux kernel will look for these in order to assert the identity
|
||||||
// of these devices (i.e look at the `amba_device_try_add` function).
|
// of these devices (i.e look at the `amba_device_try_add` function).
|
||||||
// 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 PL031_ID: [u8; 8] = [0x31, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
const PL031_ID: [u8; 8] = [0x31, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
||||||
// We are only interested in the margins.
|
// We are only interested in the margins.
|
||||||
const AMBA_ID_LOW: u64 = 0xFE0;
|
const AMBA_ID_LOW: u64 = 0xFE0;
|
||||||
@@ -45,8 +47,6 @@ pub const NANOS_PER_SECOND: u64 = 1_000_000_000;
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Bad Write Offset: {0}")]
|
#[error("Bad Write Offset: {0}")]
|
||||||
BadWriteOffset(u64),
|
BadWriteOffset(u64),
|
||||||
#[error("Failed to trigger interrupt: {0}")]
|
|
||||||
InterruptFailure(io::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
@@ -107,31 +107,20 @@ pub struct Rtc {
|
|||||||
match_value: u32,
|
match_value: u32,
|
||||||
// Writes to this register load an update value into the RTC.
|
// Writes to this register load an update value into the RTC.
|
||||||
load: u32,
|
load: u32,
|
||||||
imsc: u32,
|
|
||||||
ris: u32,
|
|
||||||
interrupt: Arc<dyn InterruptSourceGroup>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rtc {
|
impl Rtc {
|
||||||
/// Constructs an AMBA PL031 RTC device.
|
/// Constructs an AMBA PL031 RTC device.
|
||||||
pub fn new(interrupt: Arc<dyn InterruptSourceGroup>) -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
// This is used only for duration measuring purposes.
|
// This is used only for duration measuring purposes.
|
||||||
previous_now: Instant::now(),
|
previous_now: Instant::now(),
|
||||||
tick_offset: get_time(ClockType::Real) as i64,
|
tick_offset: get_time(ClockType::Real) as i64,
|
||||||
match_value: 0,
|
match_value: 0,
|
||||||
load: 0,
|
load: 0,
|
||||||
imsc: 0,
|
|
||||||
ris: 0,
|
|
||||||
interrupt,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trigger_interrupt(&mut self) -> Result<()> {
|
|
||||||
self.interrupt.trigger(0).map_err(Error::InterruptFailure)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_time(&self) -> u32 {
|
fn get_time(&self) -> u32 {
|
||||||
let ts = (self.tick_offset as i128)
|
let ts = (self.tick_offset as i128)
|
||||||
+ (Instant::now().duration_since(self.previous_now).as_nanos() as i128);
|
+ (Instant::now().duration_since(self.previous_now).as_nanos() as i128);
|
||||||
@@ -155,16 +144,8 @@ impl Rtc {
|
|||||||
// we want to terminate the execution of the process.
|
// we want to terminate the execution of the process.
|
||||||
self.tick_offset = seconds_to_nanoseconds(i64::from(val)).unwrap();
|
self.tick_offset = seconds_to_nanoseconds(i64::from(val)).unwrap();
|
||||||
}
|
}
|
||||||
RTCIMSC => {
|
RTCIMSC => (),
|
||||||
self.imsc = val & 1;
|
RTCICR => (),
|
||||||
self.trigger_interrupt()?;
|
|
||||||
}
|
|
||||||
RTCICR => {
|
|
||||||
// As per above mentioned doc, the interrupt is cleared by writing any data value to
|
|
||||||
// the Interrupt Clear Register.
|
|
||||||
self.ris = 0;
|
|
||||||
self.trigger_interrupt()?;
|
|
||||||
}
|
|
||||||
RTCCR => (), // ignore attempts to turn off the timer.
|
RTCCR => (), // ignore attempts to turn off the timer.
|
||||||
o => {
|
o => {
|
||||||
return Err(Error::BadWriteOffset(o));
|
return Err(Error::BadWriteOffset(o));
|
||||||
@@ -174,6 +155,12 @@ impl Rtc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Rtc {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BusDevice for Rtc {
|
impl BusDevice for Rtc {
|
||||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||||
let mut read_ok = true;
|
let mut read_ok = true;
|
||||||
@@ -189,10 +176,10 @@ impl BusDevice for Rtc {
|
|||||||
self.match_value
|
self.match_value
|
||||||
}
|
}
|
||||||
RTCLR => self.load,
|
RTCLR => self.load,
|
||||||
RTCCR => 1, // RTC is always enabled.
|
RTCCR => 1, // RTC is always enabled.
|
||||||
RTCIMSC => self.imsc,
|
RTCIMSC => 0, // Interrupt is always disabled.
|
||||||
RTCRIS => self.ris,
|
RTCRIS => 0,
|
||||||
RTCMIS => self.ris & self.imsc,
|
RTCMIS => 0,
|
||||||
_ => {
|
_ => {
|
||||||
read_ok = false;
|
read_ok = false;
|
||||||
0
|
0
|
||||||
@@ -230,9 +217,6 @@ impl BusDevice for Rtc {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u64, write_be_u16,
|
read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u64, write_be_u16,
|
||||||
@@ -366,45 +350,9 @@ mod tests {
|
|||||||
assert!(seconds_to_nanoseconds(9_223_372_037).is_none());
|
assert!(seconds_to_nanoseconds(9_223_372_037).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TestInterrupt {
|
|
||||||
event_fd: EventFd,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InterruptSourceGroup for TestInterrupt {
|
|
||||||
fn trigger(&self, _index: InterruptIndex) -> result::Result<(), std::io::Error> {
|
|
||||||
self.event_fd.write(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(
|
|
||||||
&self,
|
|
||||||
_index: InterruptIndex,
|
|
||||||
_config: InterruptSourceConfig,
|
|
||||||
_masked: bool,
|
|
||||||
_set_gsi: bool,
|
|
||||||
) -> result::Result<(), std::io::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
|
||||||
Some(self.event_fd.try_clone().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestInterrupt {
|
|
||||||
fn new(event_fd: EventFd) -> Self {
|
|
||||||
TestInterrupt { event_fd }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rtc_read_write_and_event() {
|
fn test_rtc_read_write_and_event() {
|
||||||
let intr_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();
|
let mut rtc = Rtc::new();
|
||||||
|
|
||||||
let mut rtc = Rtc::new(Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())));
|
|
||||||
let mut data = [0; 4];
|
let mut data = [0; 4];
|
||||||
|
|
||||||
// Read and write to the MR register.
|
// Read and write to the MR register.
|
||||||
@@ -427,15 +375,13 @@ mod tests {
|
|||||||
assert_eq!((v / NANOS_PER_SECOND) as u32, v_read);
|
assert_eq!((v / NANOS_PER_SECOND) as u32, v_read);
|
||||||
|
|
||||||
// Read and write to IMSC register.
|
// Read and write to IMSC register.
|
||||||
// Test with non zero value.
|
// Test with non zero value. Our device ignores the write.
|
||||||
let non_zero = 1;
|
let non_zero = 1;
|
||||||
write_le_u32(&mut data, non_zero);
|
write_le_u32(&mut data, non_zero);
|
||||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &data);
|
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &data);
|
||||||
// The interrupt line should be on.
|
|
||||||
assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() == 1);
|
|
||||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
|
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
|
||||||
let v = read_le_u32(&data);
|
let v = read_le_u32(&data);
|
||||||
assert_eq!(non_zero & 1, v);
|
assert_eq!(0, v);
|
||||||
|
|
||||||
// Now test with 0.
|
// Now test with 0.
|
||||||
write_le_u32(&mut data, 0);
|
write_le_u32(&mut data, 0);
|
||||||
@@ -447,8 +393,6 @@ mod tests {
|
|||||||
// Read and write to the ICR register.
|
// Read and write to the ICR register.
|
||||||
write_le_u32(&mut data, 1);
|
write_le_u32(&mut data, 1);
|
||||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCICR, &data);
|
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCICR, &data);
|
||||||
// The interrupt line should be on.
|
|
||||||
assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() > 1);
|
|
||||||
let v_before = read_le_u32(&data);
|
let v_before = read_le_u32(&data);
|
||||||
|
|
||||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCICR, &mut data);
|
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCICR, &mut data);
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ use std::sync::{Arc, Barrier};
|
|||||||
use std::{io, result};
|
use std::{io, result};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
use vmm_sys_util::errno::Result;
|
use vmm_sys_util::errno::Result;
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ use std::{io, result};
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
|
|
||||||
use crate::{read_le_u32, write_le_u32};
|
use crate::{read_le_u32, write_le_u32};
|
||||||
@@ -51,14 +51,14 @@ const AMBA_ID_HIGH: u64 = 0x401;
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("pl011_write: Bad Write Offset: {0}")]
|
#[error("pl011_write: Bad Write Offset: {0}")]
|
||||||
BadWriteOffset(u64),
|
BadWriteOffset(u64),
|
||||||
#[error("pl011: DMA not implemented.")]
|
#[error("pl011: DMA not implemented")]
|
||||||
DmaNotImplemented,
|
DmaNotImplemented,
|
||||||
#[error("Failed to trigger interrupt: {0}")]
|
#[error("Failed to trigger interrupt")]
|
||||||
InterruptFailure(io::Error),
|
InterruptFailure(#[source] io::Error),
|
||||||
#[error("Failed to write: {0}")]
|
#[error("Failed to write")]
|
||||||
WriteAllFailure(io::Error),
|
WriteAllFailure(#[source] io::Error),
|
||||||
#[error("Failed to flush: {0}")]
|
#[error("Failed to flush")]
|
||||||
FlushFailure(io::Error),
|
FlushFailure(#[source] io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ extern crate event_monitor;
|
|||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
pub mod acpi;
|
pub mod acpi;
|
||||||
|
#[cfg(target_arch = "riscv64")]
|
||||||
|
pub mod aia;
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub mod debug_console;
|
pub mod debug_console;
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
@@ -22,14 +24,20 @@ pub mod gic;
|
|||||||
pub mod interrupt_controller;
|
pub mod interrupt_controller;
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub mod ioapic;
|
pub mod ioapic;
|
||||||
|
#[cfg(feature = "ivshmem")]
|
||||||
|
pub mod ivshmem;
|
||||||
pub mod legacy;
|
pub mod legacy;
|
||||||
#[cfg(feature = "pvmemcontrol")]
|
#[cfg(feature = "pvmemcontrol")]
|
||||||
pub mod pvmemcontrol;
|
pub mod pvmemcontrol;
|
||||||
pub mod pvpanic;
|
pub mod pvpanic;
|
||||||
|
// TODO: TPM is not yet supported
|
||||||
|
#[cfg(not(target_arch = "riscv64"))]
|
||||||
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};
|
#[cfg(feature = "ivshmem")]
|
||||||
|
pub use self::ivshmem::IvshmemDevice;
|
||||||
|
pub use self::pvpanic::{PVPANIC_DEVICE_MMIO_SIZE, PvPanicDevice};
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub struct AcpiNotificationFlags: u8 {
|
pub struct AcpiNotificationFlags: u8 {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const MINOR_VERSION: u64 = 0;
|
|||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
// device errors
|
// device errors
|
||||||
#[error("Guest gave us bad memory addresses: {0}")]
|
#[error("Guest gave us bad memory addresses")]
|
||||||
GuestMemory(#[source] GuestMemoryError),
|
GuestMemory(#[source] GuestMemoryError),
|
||||||
#[error("Guest sent us invalid request")]
|
#[error("Guest sent us invalid request")]
|
||||||
InvalidRequest,
|
InvalidRequest,
|
||||||
@@ -51,7 +51,7 @@ pub enum Error {
|
|||||||
InvalidArgument(u64),
|
InvalidArgument(u64),
|
||||||
#[error("Unknown function code: {0}")]
|
#[error("Unknown function code: {0}")]
|
||||||
UnknownFunctionCode(u64),
|
UnknownFunctionCode(u64),
|
||||||
#[error("Libc call fail: {0}")]
|
#[error("Libc call fail")]
|
||||||
LibcFail(#[source] std::io::Error),
|
LibcFail(#[source] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +137,8 @@ impl PvmemcontrolTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn as_register(self) -> PvmemcontrolTransportRegister {
|
unsafe fn as_register(self) -> PvmemcontrolTransportRegister {
|
||||||
self.payload.register
|
// SAFETY: We access initialized data.
|
||||||
|
unsafe { self.payload.register }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,7 +485,7 @@ impl PvmemcontrolBusDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_vma_anon_name(&self, addr: u64, length: u64, name: u64) -> result::Result<(), Error> {
|
fn set_vma_anon_name(&self, addr: u64, length: u64, name: u64) -> result::Result<(), Error> {
|
||||||
let name = (name != 0).then(|| CString::new(format!("pvmemcontrol-{}", name)).unwrap());
|
let name = (name != 0).then(|| CString::new(format!("pvmemcontrol-{name}")).unwrap());
|
||||||
let name_ptr = if let Some(name) = &name {
|
let name_ptr = if let Some(name) = &name {
|
||||||
name.as_ptr()
|
name.as_ptr()
|
||||||
} else {
|
} else {
|
||||||
@@ -519,7 +520,7 @@ impl PvmemcontrolBusDevice {
|
|||||||
ret_value: get_page_size().into(),
|
ret_value: get_page_size().into(),
|
||||||
arg0: MAJOR_VERSION.into(),
|
arg0: MAJOR_VERSION.into(),
|
||||||
arg1: MINOR_VERSION.into(),
|
arg1: MINOR_VERSION.into(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
FunctionCode::Dontneed => self.madvise(addr, length, libc::MADV_DONTNEED),
|
FunctionCode::Dontneed => self.madvise(addr, length, libc::MADV_DONTNEED),
|
||||||
FunctionCode::Remove => self.madvise(addr, length, libc::MADV_REMOVE),
|
FunctionCode::Remove => self.madvise(addr, length, libc::MADV_REMOVE),
|
||||||
@@ -698,17 +699,19 @@ impl PciDevice for PvmemcontrolPciDevice {
|
|||||||
reg_idx: usize,
|
reg_idx: usize,
|
||||||
offset: u64,
|
offset: u64,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
) -> Option<Arc<Barrier>> {
|
) -> (Vec<BarReprogrammingParams>, Option<Arc<Barrier>>) {
|
||||||
self.configuration
|
(
|
||||||
.write_config_register(reg_idx, offset, data);
|
self.configuration
|
||||||
None
|
.write_config_register(reg_idx, offset, data),
|
||||||
|
None,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
||||||
self.configuration.read_config_register(reg_idx)
|
self.configuration.read_config_register(reg_idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,14 +719,6 @@ impl PciDevice for PvmemcontrolPciDevice {
|
|||||||
Some(self.id.clone())
|
Some(self.id.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detect_bar_reprogramming(
|
|
||||||
&mut self,
|
|
||||||
reg_idx: usize,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Option<BarReprogrammingParams> {
|
|
||||||
self.configuration.detect_bar_reprogramming(reg_idx, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn allocate_bars(
|
fn allocate_bars(
|
||||||
&mut self,
|
&mut self,
|
||||||
_allocator: &Arc<Mutex<SystemAllocator>>,
|
_allocator: &Arc<Mutex<SystemAllocator>>,
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ use std::sync::{Arc, Barrier, Mutex};
|
|||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use pci::{
|
use pci::{
|
||||||
BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType,
|
BarReprogrammingParams, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable,
|
||||||
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
|
PciBarRegionType, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType,
|
||||||
PCI_CONFIGURATION_ID,
|
PciSubclass,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -31,9 +31,9 @@ const PVPANIC_CRASH_LOADED: u8 = 1 << 1;
|
|||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum PvPanicError {
|
pub enum PvPanicError {
|
||||||
#[error("Failed creating PvPanicDevice: {0}")]
|
#[error("Failed creating PvPanicDevice")]
|
||||||
CreatePvPanicDevice(#[source] anyhow::Error),
|
CreatePvPanicDevice(#[source] anyhow::Error),
|
||||||
#[error("Failed to retrieve PciConfigurationState: {0}")]
|
#[error("Failed to retrieve PciConfigurationState")]
|
||||||
RetrievePciConfigurationState(#[source] anyhow::Error),
|
RetrievePciConfigurationState(#[source] anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +88,11 @@ impl PvPanicDevice {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let command: [u8; 2] = [0x03, 0x01];
|
let command: [u8; 2] = [0x03, 0x01];
|
||||||
configuration.write_config_register(1, 0, &command);
|
let bar_reprogram = configuration.write_config_register(1, 0, &command);
|
||||||
|
assert!(
|
||||||
|
bar_reprogram.is_empty(),
|
||||||
|
"No bar reprogrammig is expected from writing to the COMMAND register"
|
||||||
|
);
|
||||||
|
|
||||||
let state: Option<PvPanicDeviceState> = snapshot
|
let state: Option<PvPanicDeviceState> = snapshot
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -156,24 +160,18 @@ impl PciDevice for PvPanicDevice {
|
|||||||
reg_idx: usize,
|
reg_idx: usize,
|
||||||
offset: u64,
|
offset: u64,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
) -> Option<Arc<Barrier>> {
|
) -> (Vec<BarReprogrammingParams>, Option<Arc<Barrier>>) {
|
||||||
self.configuration
|
(
|
||||||
.write_config_register(reg_idx, offset, data);
|
self.configuration
|
||||||
None
|
.write_config_register(reg_idx, offset, data),
|
||||||
|
None,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
||||||
self.configuration.read_reg(reg_idx)
|
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(
|
fn allocate_bars(
|
||||||
&mut self,
|
&mut self,
|
||||||
_allocator: &Arc<Mutex<SystemAllocator>>,
|
_allocator: &Arc<Mutex<SystemAllocator>>,
|
||||||
@@ -237,7 +235,7 @@ impl PciDevice for PvPanicDevice {
|
|||||||
data[0] = self.events;
|
data[0] = self.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_any(&mut self) -> &mut dyn Any {
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,15 +12,15 @@ 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 thiserror::Error;
|
use thiserror::Error;
|
||||||
use tpm::emulator::{BackendCmd, Emulator};
|
|
||||||
use tpm::TPM_CRB_BUFFER_MAX;
|
use tpm::TPM_CRB_BUFFER_MAX;
|
||||||
|
use tpm::emulator::{BackendCmd, Emulator};
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Emulator doesn't implement min required capabilities: {0}")]
|
#[error("Emulator doesn't implement min required capabilities")]
|
||||||
CheckCaps(#[source] anyhow::Error),
|
CheckCaps(#[source] anyhow::Error),
|
||||||
#[error("Failed to initialize tpm: {0}")]
|
#[error("Failed to initialize tpm")]
|
||||||
Init(#[source] anyhow::Error),
|
Init(#[source] anyhow::Error),
|
||||||
}
|
}
|
||||||
type Result<T> = anyhow::Result<T, Error>;
|
type Result<T> = anyhow::Result<T, Error>;
|
||||||
@@ -458,10 +458,9 @@ impl BusDevice for Tpm {
|
|||||||
CRB_CTRL_CANCEL => {
|
CRB_CTRL_CANCEL => {
|
||||||
if v == CRB_CANCEL_INVOKE
|
if v == CRB_CANCEL_INVOKE
|
||||||
&& (self.regs[CRB_CTRL_START as usize] & CRB_START_INVOKE != 0)
|
&& (self.regs[CRB_CTRL_START as usize] & CRB_START_INVOKE != 0)
|
||||||
|
&& let Err(e) = self.emulator.cancel_cmd()
|
||||||
{
|
{
|
||||||
if let Err(e) = self.emulator.cancel_cmd() {
|
error!("Failed to run cancel command. Error: {:?}", e);
|
||||||
error!("Failed to run cancel command. Error: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CRB_CTRL_START => {
|
CRB_CTRL_START => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# AMD SEV-SNP
|
# AMD SEV-SNP
|
||||||
|
|
||||||
### WARNING
|
### WARNING
|
||||||
|
|
||||||
This feature is only currently supported on MSHV.
|
This feature is only currently supported on MSHV.
|
||||||
|
|
||||||
AMD Secure Encrypted Virtualization & Secure Nested Paging (SEV-SNP) is an AMD
|
AMD Secure Encrypted Virtualization & Secure Nested Paging (SEV-SNP) is an AMD
|
||||||
@@ -9,8 +10,8 @@ malicious hypervisor-based attacks like data replay, memory-remapping and more
|
|||||||
in order to create an isolated execution environment. Here are some useful
|
in order to create an isolated execution environment. Here are some useful
|
||||||
links:
|
links:
|
||||||
|
|
||||||
* [SNP Homepage] (https://www.amd.com/en/processors/amd-secure-encrypted-virtualization)
|
- [SNP Homepage](https://www.amd.com/content/dam/amd/en/documents/epyc-business-docs/solution-briefs/amd-secure-encrypted-virtualization-solution-brief.pdf):
|
||||||
more information about SEV-SNP technical aspects, design and specification.
|
more information about SEV-SNP technical aspects, design and specification.
|
||||||
|
|
||||||
## Cloud Hypervisor support
|
## Cloud Hypervisor support
|
||||||
|
|
||||||
|
|||||||
13
docs/api.md
13
docs/api.md
@@ -50,17 +50,12 @@ for more details about the API payloads and responses.
|
|||||||
|
|
||||||
#### REST API 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, if enabled, is available as soon as the Cloud Hypervisor binary is started,
|
||||||
through a local UNIX socket.
|
through either a local UNIX socket as given in the Cloud Hypervisor option `--api-socket path=...`
|
||||||
By default, it is located at `/run/user/{user ID}/cloud-hypervisor.{Cloud Hypervisor PID}`.
|
or a fd with `--api-socket fd=...`.
|
||||||
For example, if you launched Cloud Hypervisor as user ID 1000 and its PID is
|
|
||||||
123456, the Cloud Hypervisor REST API will be available at `/run/user/1000/cloud-hypervisor.123456`.
|
|
||||||
|
|
||||||
The REST API default URL can be overridden through the Cloud Hypervisor
|
|
||||||
option `--api-socket`:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ ./target/debug/cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock
|
$ ./target/debug/cloud-hypervisor --api-socket path=/tmp/cloud-hypervisor.sock
|
||||||
Cloud Hypervisor Guest
|
Cloud Hypervisor Guest
|
||||||
API server: /tmp/cloud-hypervisor.sock
|
API server: /tmp/cloud-hypervisor.sock
|
||||||
vCPUs: 1
|
vCPUs: 1
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# `cloud-hypervisor` debug IO ports
|
# `cloud-hypervisor` debug IO ports
|
||||||
|
|
||||||
When running x86 guests, `cloud-hypervisor` provides different kinds of debug ports:
|
When running x86 guests, `cloud-hypervisor` provides different kinds of debug ports:
|
||||||
- [`0x80` debug port](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
|
|
||||||
- Debug console (by default at `0xe9`).
|
- [`0x80` debug port](https://web.archive.org/web/20211028033025/https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
|
||||||
|
- Debug console (by default at `0xe9`).
|
||||||
- Firmware debug port at `0x402`.
|
- Firmware debug port at `0x402`.
|
||||||
|
|
||||||
All of them can be used to trace user-defined guest events and all of them can
|
All of them can be used to trace user-defined guest events and all of them can
|
||||||
@@ -25,13 +26,13 @@ for debugging specific components of the guest software stack. When logging a
|
|||||||
write of one of those codes to the debug port, `cloud-hypervisor` adds a
|
write of one of those codes to the debug port, `cloud-hypervisor` adds a
|
||||||
pre-defined string to the logs.
|
pre-defined string to the logs.
|
||||||
|
|
||||||
| Code Range | Component | Log string |
|
| Code Range | Component | Log string |
|
||||||
| ---------------- | ----------- | ------------ |
|
| ---------------- | ---------- | ------------ |
|
||||||
| `0x00` to `0x1f` | Firmware | `Firmware` |
|
| `0x00` to `0x1f` | Firmware | `Firmware` |
|
||||||
| `0x20` to `0x3f` | Bootloader | `Bootloader` |
|
| `0x20` to `0x3f` | Bootloader | `Bootloader` |
|
||||||
| `0x40` to `0x5f` | Kernel | `Kernel` |
|
| `0x40` to `0x5f` | Kernel | `Kernel` |
|
||||||
| `0x60` to `0x7f` | Userspace | `Userspace` |
|
| `0x60` to `0x7f` | Userspace | `Userspace` |
|
||||||
| `0x80` to `0xff` | Custom | `Custom` |
|
| `0x80` to `0xff` | Custom | `Custom` |
|
||||||
|
|
||||||
One typical use case is guest boot time measurement and tracing. By writing
|
One typical use case is guest boot time measurement and tracing. By writing
|
||||||
different values to the debug I/O port at different boot process steps, the
|
different values to the debug I/O port at different boot process steps, the
|
||||||
@@ -71,8 +72,8 @@ cloud-hypervisor: 403.499628ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firm
|
|||||||
|
|
||||||
### Debug console port
|
### Debug console port
|
||||||
|
|
||||||
The debug console is inspired by QEMU and Bochs, which have a similar feature.
|
The debug console is inspired by QEMU and Bochs, which have a similar feature.
|
||||||
By default, the I/O port `0xe9` is used. This port can be configured like a
|
By default, the I/O port `0xe9` is used. This port can be configured like a
|
||||||
console. Thus, it can print to a tty, a file, or a pty, for example.
|
console. Thus, it can print to a tty, a file, or a pty, for example.
|
||||||
|
|
||||||
### Firmware debug port
|
### Firmware debug port
|
||||||
@@ -83,7 +84,7 @@ it. The firmware debug port only prints to stdout.
|
|||||||
## When do I need these ports?
|
## When do I need these ports?
|
||||||
|
|
||||||
The ports are on the one hand interesting for firmware or kernel developers, as
|
The ports are on the one hand interesting for firmware or kernel developers, as
|
||||||
they provide an easy way to print debug information from within a guest.
|
they provide an easy way to print debug information from within a guest.
|
||||||
Furthermore, you can patch "normal" software to measure certain events, such as
|
Furthermore, you can patch "normal" software to measure certain events, such as
|
||||||
the boot time of a guest.
|
the boot time of a guest.
|
||||||
|
|
||||||
@@ -91,6 +92,6 @@ the boot time of a guest.
|
|||||||
|
|
||||||
The `0x80` debug port and the port of the firmware debug device are always
|
The `0x80` debug port and the port of the firmware debug device are always
|
||||||
available. The debug console must be activated via the command line, but
|
available. The debug console must be activated via the command line, but
|
||||||
provides more configuration options.
|
provides more configuration options.
|
||||||
|
|
||||||
You can use different ports for different aspect of your logging messages.
|
You can use different ports for different aspect of your logging messages.
|
||||||
|
|||||||
77
docs/fw_cfg.md
Normal file
77
docs/fw_cfg.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# Firmware Configuration (fw_cfg) Device
|
||||||
|
|
||||||
|
The `fw_cfg` device is a QEMU-compatible device that allows the hypervisor to pass configuration and data to the guest operating system. This is particularly useful for firmware to access information like ACPI tables, kernel images, initramfs, kernel command lines, and other arbitrary data blobs.
|
||||||
|
|
||||||
|
Cloud Hypervisor implements the `fw_cfg` device with DMA-enabled access.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
The `fw_cfg` device serves as a generic information channel between the VMM and the guest. It can be used to:
|
||||||
|
|
||||||
|
* Load the kernel, initramfs, and kernel command line for direct kernel boot with firmware.
|
||||||
|
* Provide ACPI tables to the guest firmware or OS.
|
||||||
|
* Pass custom configuration files or data blobs (e.g., attestation data, SEV-SNP launch secrets) to the guest.
|
||||||
|
* Supply an E820 memory map to the guest.
|
||||||
|
|
||||||
|
## Enabling `fw_cfg`
|
||||||
|
|
||||||
|
The `fw_cfg` device is enabled via the `fw_cfg` feature flag when building Cloud Hypervisor:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build --features fw_cfg
|
||||||
|
```
|
||||||
|
|
||||||
|
## Guest Kernel Configuration
|
||||||
|
|
||||||
|
For the guest Linux kernel to recognize and use the `fw_cfg` device via sysfs, the following kernel configuration option must be enabled:
|
||||||
|
|
||||||
|
* `CONFIG_FW_CFG_SYSFS=y`
|
||||||
|
|
||||||
|
This option allows the kernel to expose `fw_cfg` entries under `/sys/firmware/qemu_fw_cfg/by_name/`.
|
||||||
|
|
||||||
|
## Command Line Options
|
||||||
|
|
||||||
|
The `fw_cfg` device is configured using the `--fw-cfg-config` command-line option.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
* `e820=on|off`: (Default: `on`) Whether to add an E820 memory map entry to `fw_cfg`.
|
||||||
|
* `kernel=on|off`: (Default: `on`) Whether to add the kernel image (specified by `--kernel`) to `fw_cfg`.
|
||||||
|
* `cmdline=on|off`: (Default: `on`) Whether to add the kernel command line (specified by `--cmdline`) to `fw_cfg`.
|
||||||
|
* `initramfs=on|off`: (Default: `on`) Whether to add the initramfs image (specified by `--initramfs`) to `fw_cfg`.
|
||||||
|
* `acpi_table=on|off`: (Default: `on`) Whether to add generated ACPI tables to `fw_cfg`.
|
||||||
|
* `items=[... : ...]`: A list of custom key-value pairs to be exposed via `fw_cfg`.
|
||||||
|
* `name=<guest_sysfs_path>`: The path under which the item will appear in the guest's sysfs (e.g., `opt/org.example/my-data`).
|
||||||
|
* `file=<host_file_path>`: The path to the file on the host whose content will be provided to the guest for this item.
|
||||||
|
|
||||||
|
**Example Usage:**
|
||||||
|
|
||||||
|
1. **Direct kernel boot with custom `fw_cfg` entries:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cloud-hypervisor \
|
||||||
|
--kernel /path/to/vmlinux \
|
||||||
|
--cmdline "console=hvc0 root=/dev/vda1" \
|
||||||
|
--disk path=/path/to/rootfs.img \
|
||||||
|
--fw-cfg-config initramfs=off,items=[name=opt/org.mycorp/setup_info,file=/tmp/guest_setup.txt] \
|
||||||
|
...
|
||||||
|
```
|
||||||
|
In the guest, `/tmp/guest_setup.txt` from the host will be accessible at `/sys/firmware/qemu_fw_cfg/by_name/opt/org.mycorp/setup_info/raw`.
|
||||||
|
|
||||||
|
2. **Disabling `fw_cfg` explicitly:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cloud-hypervisor \
|
||||||
|
--fw-cfg-config disable \
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessing `fw_cfg` Items in the Guest
|
||||||
|
|
||||||
|
If `CONFIG_FW_CFG_SYSFS` is enabled in the guest kernel, items added to `fw_cfg` can be accessed via sysfs.
|
||||||
|
|
||||||
|
For example, an item added with `name=opt/org.example/my-data` will be available at:
|
||||||
|
`/sys/firmware/qemu_fw_cfg/by_name/opt/org.example/my-data/raw`
|
||||||
|
|
||||||
|
The `raw` file contains the binary content of the host file provided.
|
||||||
|
|
||||||
|
Standard items like kernel, initramfs, cmdline, and ACPI tables also have predefined names (e.g., `etc/kernel`, `etc/cmdline`) if they are enabled to be passed via `fw_cfg`.
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
# Intel SGX
|
|
||||||
|
|
||||||
Intel® Software Guard Extensions (Intel® SGX) is an Intel technology designed
|
|
||||||
to increase the security of application code and data. Cloud Hypervisor supports
|
|
||||||
SGX virtualization through KVM. Because SGX is built on hardware features that
|
|
||||||
cannot be emulated in software, virtualizing SGX requires support in KVM and in
|
|
||||||
the host kernel. The required Linux and KVM changes can be found in the
|
|
||||||
[KVM SGX Tree](https://github.com/intel/kvm-sgx).
|
|
||||||
|
|
||||||
Utilizing SGX in the guest requires a kernel/OS with SGX support, e.g. a kernel
|
|
||||||
built using the [SGX Linux Development Tree](https://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-sgx.git)
|
|
||||||
or the [KVM SGX Tree](https://github.com/intel/kvm-sgx). Running KVM SGX as the
|
|
||||||
guest kernel allows nested virtualization of SGX.
|
|
||||||
|
|
||||||
For more information about SGX, please refer to the [SGX Homepage](https://software.intel.com/sgx).
|
|
||||||
|
|
||||||
For more information about SGX SDK and how to test SGX, please refer to the
|
|
||||||
following [instructions](https://github.com/intel/linux-sgx).
|
|
||||||
|
|
||||||
## Cloud Hypervisor support
|
|
||||||
|
|
||||||
Assuming the host exposes `/dev/sgx_vepc`, we can pass SGX enclaves through
|
|
||||||
the guest.
|
|
||||||
|
|
||||||
In order to use SGX enclaves within a Cloud Hypervisor VM, we must define one
|
|
||||||
or several Enclave Page Cache (EPC) sections. Here is an example of a VM being
|
|
||||||
created with 2 EPC sections, the first one being 64MiB with pre-allocated
|
|
||||||
memory, the second one being 32MiB with no pre-allocated memory.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./cloud-hypervisor \
|
|
||||||
--cpus boot=1 \
|
|
||||||
--memory size=1G \
|
|
||||||
--disk path=focal-server-cloudimg-amd64.raw \
|
|
||||||
--kernel vmlinux \
|
|
||||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
|
||||||
--sgx-epc id=epc0,size=64M,prefault=on id=epc1,size=32M,prefault=off
|
|
||||||
```
|
|
||||||
|
|
||||||
Once booted, and assuming your guest kernel contains the patches from the
|
|
||||||
[KVM SGX Tree](https://github.com/intel/kvm-sgx), you can validate SGX devices
|
|
||||||
have been correctly created under `/dev/sgx`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ls /dev/sgx*
|
|
||||||
/dev/sgx_enclave /dev/sgx_provision /dev/sgx_vepc
|
|
||||||
```
|
|
||||||
|
|
||||||
From this point, it is possible to run any SGX application from the guest, as
|
|
||||||
it will access `/dev/sgx_enclave` device to create dedicated SGX enclaves.
|
|
||||||
|
|
||||||
Note: There is only one contiguous SGX EPC region, which contains all SGX EPC
|
|
||||||
sections. This region is exposed through ACPI and marked as reserved through
|
|
||||||
the e820 table. It is treated as yet another device, which means it should
|
|
||||||
appear at the end of the guest address space.
|
|
||||||
@@ -4,21 +4,21 @@ 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. Here are some useful links:
|
host platform. Here are some useful links:
|
||||||
|
|
||||||
* [TDX Homepage](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-trust-domain-extensions.html):
|
- [TDX Homepage](https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html):
|
||||||
more information about TDX technical aspects, design and specification
|
more information about TDX technical aspects, design and specification
|
||||||
|
|
||||||
* [KVM TDX tree](https://github.com/intel/tdx/tree/kvm): the required
|
- [KVM TDX tree](https://github.com/intel/tdx/tree/kvm): the required
|
||||||
Linux kernel changes for the host side
|
Linux kernel changes for the host side
|
||||||
|
|
||||||
* [Guest TDX tree](https://github.com/intel/tdx/tree/guest): the Linux
|
- [Guest TDX tree](https://github.com/intel/tdx/tree/guest): the Linux
|
||||||
kernel changes for the guest side
|
kernel changes for the guest side
|
||||||
|
|
||||||
* [EDK2 project](https://github.com/tianocore/edk2): the TDVF firmware
|
- [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
|
the TDShim firmware
|
||||||
|
|
||||||
* [TDX Tools](https://github.com/intel/tdx-tools): a collection of tools
|
- [TDX Linux](https://github.com/intel/tdx-linux): a collection of tools
|
||||||
and scripts to setup TDX environment for testing purpose (such as
|
and scripts to setup TDX environment for testing purpose (such as
|
||||||
installing required packages on the host, creating guest images, and
|
installing required packages on the host, creating guest images, and
|
||||||
building the custom Linux kernel for TDX host and guest)
|
building the custom Linux kernel for TDX host and guest)
|
||||||
@@ -27,17 +27,13 @@ more information about TDX technical aspects, design and specification
|
|||||||
|
|
||||||
It is required to use 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).
|
The host environment can also be setup with the [TDX Linux](https://github.com/intel/tdx-linux).
|
||||||
|
|
||||||
Cloud Hypervisor can run TDX VM (Trust Domain) by loading a TD firmware ([TDVF](https://github.com/tianocore/edk2)),
|
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).
|
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).
|
The custom Linux kernel for the guest can be built with the [TDX Linux](https://github.com/intel/tdx-linux).
|
||||||
|
|
||||||
> **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
|
||||||
|
|
||||||
@@ -110,6 +106,7 @@ direct kernel boot, which is useful for containers use cases.
|
|||||||
|
|
||||||
To build TDShim from source, it is required to install `Rust`, `NASM`,
|
To build TDShim from source, it is required to install `Rust`, `NASM`,
|
||||||
and `LLVM` first. The TDshim can be build as follows:
|
and `LLVM` first. The TDshim can be build as follows:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/confidential-containers/td-shim
|
git clone https://github.com/confidential-containers/td-shim
|
||||||
cd td-shim
|
cd td-shim
|
||||||
@@ -126,13 +123,14 @@ cargo image --release
|
|||||||
|
|
||||||
If debug logs from the TDShim is needed, here are the alternative
|
If debug logs from the TDShim is needed, here are the alternative
|
||||||
commands:
|
commands:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo image
|
cargo image
|
||||||
```
|
```
|
||||||
|
|
||||||
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).
|
or the [TDX Linux](https://github.com/intel/tdx-linux).
|
||||||
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.
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user